diff --git a/build.gradle.kts b/build.gradle.kts index aa4a7c1..77d62e4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,20 +51,21 @@ dependencyManagement { dependencies { implementation(libs.org.springframework.boot.spring.boot.starter.web) implementation(libs.org.springframework.boot.spring.boot.starter.security) + implementation(libs.org.springframework.boot.spring.boot.starter.mail) implementation(libs.org.springframework.boot.spring.boot.starter.oauth2.client) implementation(libs.org.twitter4j.twitter4j.core) implementation(libs.io.github.boostchicken.spring.data.dynamodb) implementation(libs.com.amazonaws.serverless.aws.serverless.java.container.springboot3) - testImplementation(libs.org.springframework.boot.spring.boot.starter.test) - testImplementation(libs.org.springframework.security.spring.security.test) + itestImplementation(libs.org.springframework.cloud.spring.cloud.starter.contract.stub.runner) itestImplementation(libs.org.springframework.boot.spring.boot.starter.test) itestImplementation(libs.io.rest.assured.rest.assured) + itestImplementation(libs.org.mockito.mockito.core) } group = "com.coderstower" -version = "0.0.6-SNAPSHOT" +version = "0.0.7-SNAPSHOT" description = "social-media-publisher" java { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a77c207..51e6152 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,9 +12,11 @@ com-amazonaws-serverless-aws-serverless-java-container-springboot3 = { module = io-github-boostchicken-spring-data-dynamodb = { module = "io.github.boostchicken:spring-data-dynamodb", version.ref = "io-github-boostchicken-spring-data-dynamodb" } org-springframework-boot-spring-boot-starter-oauth2-client = { module = "org.springframework.boot:spring-boot-starter-oauth2-client" } org-springframework-boot-spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security"} +org-springframework-boot-spring-boot-starter-mail = { module = "org.springframework.boot:spring-boot-starter-mail"} org-springframework-boot-spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test" } org-springframework-boot-spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" } org-springframework-security-spring-security-test = { module = "org.springframework.security:spring-security-test" } org-springframework-cloud-spring-cloud-starter-contract-stub-runner = { module = "org.springframework.cloud:spring-cloud-starter-contract-stub-runner" } io-rest-assured-rest-assured = { module = "io.rest-assured:rest-assured", version.ref = "io-rest-assured-rest-assured"} +org-mockito-mockito-core = { module = "org.mockito:mockito-core"} org-twitter4j-twitter4j-core = { module = "org.twitter4j:twitter4j-core", version.ref = "org-twitter4j-twitter4j-core" } diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java b/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java index 435cb5f..a4441e9 100644 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java @@ -6,8 +6,10 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Import; +import org.springframework.mail.MailSender; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; @@ -18,6 +20,9 @@ @ContextConfiguration(classes = OverriddenConfiguration.class) @ExtendWith(ITestExtension.class) public abstract class MockedEdgesConfig { + @MockBean + protected MailSender mailSender; + @RegisterExtension static WireMockExtension wm1 = WireMockExtension.newInstance() .options( diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java b/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java index 3c0cb47..86c6165 100644 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java @@ -4,6 +4,9 @@ import com.coderstower.socialmediapubisher.extesion.ITestHandler; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import org.junit.jupiter.api.Test; +import org.springframework.mail.SimpleMailMessage; + +import static org.mockito.Mockito.*; import static io.restassured.RestAssured.given; @@ -12,7 +15,7 @@ public class PostNextToLinkedInTest extends MockedEdgesConfig { @Test public void publishNextPostSuccessful(WireMockRuntimeInfo wireMockRuntimeInfo, ITestHandler iTestHandler) { - iTestHandler.loadWiremockMocks(wireMockRuntimeInfo, "testcases/post/next/linkedin/wiremock/"); + iTestHandler.loadWiremockMocks(wireMockRuntimeInfo, "testcases/post/next/linkedin/success/wiremock/"); var response = given(). port(port). @@ -23,6 +26,36 @@ public void publishNextPostSuccessful(WireMockRuntimeInfo wireMockRuntimeInfo, I .body() .asString(); - iTestHandler.validateJSONResponse("testcases/post/next/linkedin/response.json", response); + iTestHandler.validateJSONResponse("testcases/post/next/linkedin/success/response.json", response); + + SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); + simpleMailMessage.setTo("receiver@mail.com"); + simpleMailMessage.setFrom("sender@mail.com"); + simpleMailMessage.setSubject("SocialMediaPublisher: Success publishing group group1"); + simpleMailMessage.setText("Post(id=post1, name=Post 1, description=This is a new post 1, tags=[java, code], url=http://coders.com/post1, publishedDate=2020-03-03T05:06:08.000000001, publications=[Publication(id=linkedIn123, status=SUCCESS, publisher=linkedin, credentialId=id132, publishedDate=2020-03-03T05:06:08.000000001)], group=group1)"); + + verify(mailSender).send(simpleMailMessage); + } + + @Test + public void publishNextPostFail(WireMockRuntimeInfo wireMockRuntimeInfo, ITestHandler iTestHandler) { + iTestHandler.loadWiremockMocks(wireMockRuntimeInfo, "testcases/post/next/linkedin/failureExpiredCredentials/wiremock/"); + + var response = given(). + port(port). + post("/posts/group1/next"). + then(). + statusCode(401). + extract() + .body() + .asString(); + + SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); + simpleMailMessage.setTo("receiver@mail.com"); + simpleMailMessage.setFrom("sender@mail.com"); + simpleMailMessage.setSubject("SocialMediaPublisher: Error publishing group group1"); + simpleMailMessage.setText("Unauthorized for linkedin id132. Please login again here: http://localhost:8080/oauth2/linkedin/credentials"); + + verify(mailSender).send(simpleMailMessage); } } diff --git a/src/itest/resources/testcases/post/next/linkedin/failureExpiredCredentials/wiremock/mappings/getOauth2Credentials.json b/src/itest/resources/testcases/post/next/linkedin/failureExpiredCredentials/wiremock/mappings/getOauth2Credentials.json new file mode 100644 index 0000000..25abf9a --- /dev/null +++ b/src/itest/resources/testcases/post/next/linkedin/failureExpiredCredentials/wiremock/mappings/getOauth2Credentials.json @@ -0,0 +1,44 @@ +{ + "request": { + "method": "POST", + "url": "/", + "headers": { + "X-Amz-Target": { + "equalTo": "DynamoDB_20120810.Scan" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "TableName": "Oauth2Credentials" + } + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "Count": 1, + "Items": [ + { + "id": { + "S": "id132" + }, + "accessToken": { + "S": "accessToken132" + }, + "expirationDate": { + "S": "2019-04-28T16:24:12" + }, + "allowedGroups": { + "L": [ + { + "S": "group1" + } + ] + } + } + ] + } + } +} diff --git a/src/itest/resources/testcases/post/next/linkedin/response.json b/src/itest/resources/testcases/post/next/linkedin/success/response.json similarity index 100% rename from src/itest/resources/testcases/post/next/linkedin/response.json rename to src/itest/resources/testcases/post/next/linkedin/success/response.json diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getOauth2Credentials.json b/src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/getOauth2Credentials.json similarity index 100% rename from src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getOauth2Credentials.json rename to src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/getOauth2Credentials.json diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getPosts.json b/src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/getPosts.json similarity index 100% rename from src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getPosts.json rename to src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/getPosts.json diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getUserInfo.json b/src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/getUserInfo.json similarity index 100% rename from src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getUserInfo.json rename to src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/getUserInfo.json diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/postOnLinkedIn.json b/src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/postOnLinkedIn.json similarity index 100% rename from src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/postOnLinkedIn.json rename to src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/postOnLinkedIn.json diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/putPost.json b/src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/putPost.json similarity index 100% rename from src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/putPost.json rename to src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/putPost.json diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/updatePost.json b/src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/updatePost.json similarity index 100% rename from src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/updatePost.json rename to src/itest/resources/testcases/post/next/linkedin/success/wiremock/mappings/updatePost.json diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/MailProperties.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/MailProperties.java new file mode 100644 index 0000000..d5cb58a --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/MailProperties.java @@ -0,0 +1,11 @@ +package com.coderstower.socialmediapubisher.application.factory; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class MailProperties { + private final String senderEmail; + private final String receiverEmail; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/PostProperties.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/PostProperties.java new file mode 100644 index 0000000..80caaa5 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/PostProperties.java @@ -0,0 +1,11 @@ +package com.coderstower.socialmediapubisher.application.factory; + +import lombok.Builder; +import lombok.Data; +import org.springframework.web.util.UriTemplate; + +@Data +@Builder +public class PostProperties { + private final MailProperties mail; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java index def7c84..b737631 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java @@ -13,4 +13,5 @@ public class SocialMediaPublisherProperties { private final Map principalNamesAllowed; private final CredentialsProperties credentials; private final LinkedInProperties linkedIn; + private final PostProperties post; } diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java index a36d73d..6718085 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java @@ -1,17 +1,15 @@ package com.coderstower.socialmediapubisher.application.factory; +import com.coderstower.socialmediapubisher.application.socialmedia.twitter.TwitterPublisher; import com.coderstower.socialmediapubisher.domain.post.PostPublisher; import com.coderstower.socialmediapubisher.domain.post.repository.PostRepository; import com.coderstower.socialmediapubisher.domain.post.socialmedia.SocialMediaPublisher; -import com.coderstower.socialmediapubisher.domain.security.OAuth2CredentialsManager; import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1CredentialsRepository; -import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; -import com.coderstower.socialmediapubisher.application.socialmedia.linkedin.LinkedInPublisher; -import com.coderstower.socialmediapubisher.application.socialmedia.twitter.TwitterPublisher; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.mail.MailSender; import org.springframework.web.client.RestTemplate; import twitter4j.Twitter; import twitter4j.TwitterFactory; @@ -29,8 +27,8 @@ public Clock clock() { } @Bean - public PostPublisher postPublisher(List socialMediaPublishers, PostRepository postRepository, Clock clock) { - return new PostPublisher(socialMediaPublishers, postRepository, clock); + public PostPublisher postPublisher(List socialMediaPublishers, PostRepository postRepository, Clock clock, MailSender mailSender, SocialMediaPublisherProperties socialMediaPublisherProperties) { + return new PostPublisher(socialMediaPublishers, postRepository, clock, mailSender, socialMediaPublisherProperties.getPost().getMail().getSenderEmail(), socialMediaPublisherProperties.getPost().getMail().getReceiverEmail()); } @Bean diff --git a/src/main/java/com/coderstower/socialmediapubisher/domain/post/PostPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/domain/post/PostPublisher.java index fc3213e..5073215 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/domain/post/PostPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/post/PostPublisher.java @@ -5,39 +5,70 @@ import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; import com.coderstower.socialmediapubisher.domain.post.socialmedia.SocialMediaPublisher; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.MailSender; +import org.springframework.mail.SimpleMailMessage; +import java.io.PrintWriter; +import java.io.StringWriter; import java.time.Clock; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; - +@Slf4j public class PostPublisher { private final List socialMediaPublishers; private final PostRepository postRepository; private final Clock clock; + private final MailSender mailSender; + private final String senderEmail; + private final String receiverEmail; - public PostPublisher(List socialMediaPublishers, PostRepository postRepository, Clock clock) { + public PostPublisher(List socialMediaPublishers, PostRepository postRepository, Clock clock, MailSender mailSender, String senderEmail, String receiverEmail) { this.socialMediaPublishers = socialMediaPublishers; this.postRepository = postRepository; this.clock = clock; + this.mailSender = mailSender; + this.senderEmail = senderEmail; + this.receiverEmail = receiverEmail; } public Post publishNext(String group) { - ping(socialMediaPublishers); + try{ + ping(socialMediaPublishers); + + Post nextPost = postRepository.getNextToPublish(group) + .orElseThrow(() -> new IllegalStateException("There is not next post to publish")); + + List publishedPosts = publish(socialMediaPublishers, nextPost); - Post nextPost = postRepository.getNextToPublish(group) - .orElseThrow(() -> new IllegalStateException("There is not next post to publish")); + if (publishedWellOK(publishedPosts)) { + Post toUpdate = nextPost.updateLastDatePublished(LocalDateTime.now(clock)); - List publishedPosts = publish(socialMediaPublishers, nextPost); + Post postUpdated = postRepository.update(toUpdate) + .updatePublications(publishedPosts); - if (publishedWellOK(publishedPosts)) { - Post toUpdate = nextPost.updateLastDatePublished(LocalDateTime.now(clock)); + SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); + simpleMailMessage.setFrom(senderEmail); + simpleMailMessage.setTo(receiverEmail); + simpleMailMessage.setSubject("SocialMediaPublisher: Success publishing group " + group); + simpleMailMessage.setText(postUpdated.toString()); + mailSender.send(simpleMailMessage); + + return postUpdated; + } else { + throw new IllegalStateException("Error publishing the post: " + nextPost + .updatePublications(publishedPosts)); + } + } catch (Exception e) { + SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); + simpleMailMessage.setFrom(senderEmail); + simpleMailMessage.setTo(receiverEmail); + simpleMailMessage.setSubject("SocialMediaPublisher: Error publishing group " + group); + simpleMailMessage.setText(e.getMessage()); + mailSender.send(simpleMailMessage); - return postRepository.update(toUpdate) - .updatePublications(publishedPosts); - } else { - throw new IllegalStateException("Error publishing the post: " + nextPost - .updatePublications(publishedPosts)); + throw e; } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6ec0ce7..5e0dc2c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,8 +8,23 @@ spring: profiles: group: itest: linkedin + mail: + properties: + "[mail.smtp.auth]": true + "[mail.smtp.starttls.enable]": true + "[mail.smtp.connectiontimeout]": 5000 + "[mail.smtp.timeout]": 3000 + "[mail.smtp.writetimeout]": 5000 + host: smtp.gmail.com + password: xxxxx + username: yyyyy + port: 587 social-media-publisher: + post: + mail: + sender-email: sender@mail.com + receiver-email: receiver@mail.com linked-in: base-url: https://api.linkedin.com credentials: