From 9c1529f1cf65697beaa108983dcc024650806f91 Mon Sep 17 00:00:00 2001 From: Robin Wilkins Date: Wed, 20 Sep 2023 16:32:39 +0100 Subject: [PATCH] Add Email functionality Adds a new endpoint which will send an email. Also add test email infrastructure, using Docker, maildev, and greenmail. --- Dockerfile | 4 +- build.gradle | 3 ++ compose.yml | 38 +++++++++++++++++++ .../controller/GreetingController.java | 21 +++++++--- .../service/EmailGreetingService.java | 24 ++++++++++++ .../showcase/service/GreetingService.java | 5 +++ src/main/resources/application.yml | 4 ++ .../controller/GreetingControllerTest.java | 27 +++++++++++-- .../service/EmailGreetingServiceTest.java | 33 ++++++++++++++++ 9 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 compose.yml create mode 100644 src/main/java/com/wilkins/showcase/service/EmailGreetingService.java create mode 100644 src/main/java/com/wilkins/showcase/service/GreetingService.java create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/com/wilkins/showcase/service/EmailGreetingServiceTest.java diff --git a/Dockerfile b/Dockerfile index f7523d9..2417728 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,4 @@ -FROM openjdk:17-alpine -RUN addgroup -S spring && adduser -S spring -G spring -USER spring:spring +FROM eclipse-temurin:17-jdk-jammy ARG JAR_FILE=build/libs/showcase.jar COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index 393e73f..8f805fe 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,9 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + + implementation 'org.springframework.boot:spring-boot-starter-mail' + testImplementation('org.springframework.boot:spring-boot-starter-test') } diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..07c54a9 --- /dev/null +++ b/compose.yml @@ -0,0 +1,38 @@ +services: + showcase: + build: + context: . + ports: + - "8080:8080" + - "8000:8000" + environment: + - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000 + + smtp-client: + image: maildev/maildev:2.1.0 + command: + - "--web" + - "1080" + - "--smtp" + - "1025" + - "--auto-relay" + - "--verbose" + environment: + - MAILDEV_OUTGOING_HOST=smtp-server + - MAILDEV_OUTGOING_PORT=1025 + expose: + - "1025" + - "1080" + ports: + - "10000:1080" + - "10001:1025" + + smtp-server: + image: greenmail/standalone:2.0.0 + environment: + - GREENMAIL_OPTS=-Dgreenmail.smtp.hostname=0.0.0.0 -Dgreenmail.smtp.port=1025 -Dgreenmail.imap.hostname=0.0.0.0 -Dgreenmail.imap.port=1143 -Dgreenmail.auth.disabled -Dgreenmail.startup.timeout=10000 + expose: + - "1025" + - "1143" +# ports: +# - "20000:1143" \ No newline at end of file diff --git a/src/main/java/com/wilkins/showcase/controller/GreetingController.java b/src/main/java/com/wilkins/showcase/controller/GreetingController.java index 7e35380..827aa70 100644 --- a/src/main/java/com/wilkins/showcase/controller/GreetingController.java +++ b/src/main/java/com/wilkins/showcase/controller/GreetingController.java @@ -1,26 +1,37 @@ package com.wilkins.showcase.controller; +import com.wilkins.showcase.service.GreetingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/greetings") -public class GreetingController { +public record GreetingController(GreetingService greetingService) { private static final Logger log = LoggerFactory.getLogger(GreetingController.class); @GetMapping public JsonGreeting getGreeting(@RequestParam(name = "salutation", required = false, defaultValue = "hello") String salutationParam, @RequestParam(name = "name", required = false, defaultValue = "world") String nameParam) { + log.info("A greeting was requested"); + + var greeting = JsonGreeting.of(salutationParam, nameParam); + + log.info("Greeting returned: {}", greeting); + return greeting; + } + + @PostMapping + public JsonGreeting postGreeting(@RequestParam(name = "salutation", required = false, defaultValue = "hello") String salutationParam, + @RequestParam(name = "name", required = false, defaultValue = "world") String nameParam) { log.info("A greeting was requested"); var greeting = JsonGreeting.of(salutationParam, nameParam); + greetingService.sendGreeting(salutationParam, nameParam); + log.info("Greeting returned: {}", greeting); return greeting; diff --git a/src/main/java/com/wilkins/showcase/service/EmailGreetingService.java b/src/main/java/com/wilkins/showcase/service/EmailGreetingService.java new file mode 100644 index 0000000..99f5bee --- /dev/null +++ b/src/main/java/com/wilkins/showcase/service/EmailGreetingService.java @@ -0,0 +1,24 @@ +package com.wilkins.showcase.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.mail.MailSender; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.stereotype.Component; + +@Component +public record EmailGreetingService(MailSender mailSender) implements GreetingService { + private static final Logger log = LoggerFactory.getLogger(EmailGreetingService.class); + + @Override + public void sendGreeting(String salutation, String name) { + log.info("Sending email"); + var message = new SimpleMailMessage(); + message.setFrom("showcase@cleancoders.net"); + message.setTo("test@cleancoders.net"); + message.setSubject("test email"); + message.setText("%s %s".formatted(salutation, name)); + mailSender.send(message); + log.info("Email sent"); + } +} diff --git a/src/main/java/com/wilkins/showcase/service/GreetingService.java b/src/main/java/com/wilkins/showcase/service/GreetingService.java new file mode 100644 index 0000000..8adc2a7 --- /dev/null +++ b/src/main/java/com/wilkins/showcase/service/GreetingService.java @@ -0,0 +1,5 @@ +package com.wilkins.showcase.service; + +public interface GreetingService { + void sendGreeting(String salutation, String name); +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..4925b97 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,4 @@ +spring: + mail: + host: smtp-client + port: 1025 \ No newline at end of file diff --git a/src/test/java/com/wilkins/showcase/controller/GreetingControllerTest.java b/src/test/java/com/wilkins/showcase/controller/GreetingControllerTest.java index 49b6a05..c8720f7 100644 --- a/src/test/java/com/wilkins/showcase/controller/GreetingControllerTest.java +++ b/src/test/java/com/wilkins/showcase/controller/GreetingControllerTest.java @@ -1,21 +1,31 @@ package com.wilkins.showcase.controller; +import com.wilkins.showcase.service.GreetingService; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.hamcrest.CoreMatchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest public class GreetingControllerTest { - @Autowired MockMvc mockMvc; + GreetingService greetingService = mock(GreetingService.class); + + @BeforeEach + void beforeEach() { + var controller = new GreetingController(greetingService); + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + @Test void returnsGreeting() throws Exception { mockMvc.perform(get("/greetings")) @@ -24,4 +34,13 @@ void returnsGreeting() throws Exception { .andExpect(jsonPath("$.name", is("world"))); } + @Test + void emailsGreeting() throws Exception { + mockMvc.perform(post("/greetings")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.salutation", is("hello"))) + .andExpect(jsonPath("$.name", is("world"))); + verify(greetingService).sendGreeting("hello", "world"); + } + } diff --git a/src/test/java/com/wilkins/showcase/service/EmailGreetingServiceTest.java b/src/test/java/com/wilkins/showcase/service/EmailGreetingServiceTest.java new file mode 100644 index 0000000..1fd23b5 --- /dev/null +++ b/src/test/java/com/wilkins/showcase/service/EmailGreetingServiceTest.java @@ -0,0 +1,33 @@ +package com.wilkins.showcase.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mail.MailSender; +import org.springframework.mail.SimpleMailMessage; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class EmailGreetingServiceTest { + + GreetingService underTest; + MailSender mailSender = mock(MailSender.class); + + @BeforeEach + void beforeEach() { + underTest = new EmailGreetingService(mailSender); + } + + @Test + void canEmailGreeting() { + var expectedMessage = new SimpleMailMessage(); + expectedMessage.setFrom("showcase@cleancoders.net"); + expectedMessage.setTo("test@cleancoders.net"); + expectedMessage.setSubject("test email"); + expectedMessage.setText("%s %s".formatted("hello", "world")); + + underTest.sendGreeting("hello", "world"); + + verify(mailSender).send(expectedMessage); + } +} \ No newline at end of file