diff --git a/pom.xml b/pom.xml index 9b8b3ea..c6d195c 100644 --- a/pom.xml +++ b/pom.xml @@ -92,14 +92,48 @@ - de.flapdoodle.embed - de.flapdoodle.embed.mongo + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.springframework.security + spring-security-test + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-junit-jupiter test - + + + flapdoodle + + false + + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + test + + + + + diff --git a/src/main/java/de/wirvsvirus/testresult/backend/rest/TestResultController.java b/src/main/java/de/wirvsvirus/testresult/backend/rest/TestResultController.java index 187a2fe..b37d137 100644 --- a/src/main/java/de/wirvsvirus/testresult/backend/rest/TestResultController.java +++ b/src/main/java/de/wirvsvirus/testresult/backend/rest/TestResultController.java @@ -39,7 +39,7 @@ public Optional getTestResult(@PathVariable("id") String id) { public TestResult addTestResult(@PathVariable("id") String id, @RequestBody TestResult testResult) throws FalseInformedException { testResult.setId(id); - +//TODO logic should be moved to service Optional previousResultOptional = testResultService.getTestResult(id); if (!previousResultOptional.isPresent()) { return informNegatives(testResult); @@ -66,7 +66,7 @@ public class ErrorResult{ TestResult result; String comment; } - + //TODO saving should not depend on notification, we also get updates on the status that do not trigger notification private TestResult informNegatives(TestResult testResult) { TestResult saveResult; try { diff --git a/src/main/java/de/wirvsvirus/testresult/backend/service/TestResultPushService.java b/src/main/java/de/wirvsvirus/testresult/backend/service/TestResultPushService.java index ba36917..79134a0 100644 --- a/src/main/java/de/wirvsvirus/testresult/backend/service/TestResultPushService.java +++ b/src/main/java/de/wirvsvirus/testresult/backend/service/TestResultPushService.java @@ -1,8 +1,5 @@ package de.wirvsvirus.testresult.backend.service; -import javax.mail.internet.AddressException; -import javax.mail.internet.InternetAddress; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -17,9 +14,11 @@ @Component @Slf4j public class TestResultPushService { - private static final String MOBILE_PATTERN = "[+491|01]\\d+"; + private static final String MOBILE_PATTERN = "^(\\+491|01|00491)\\d+"; private static final String MOBILE_CLEANUP_PATTERN = "[^(\\d|+)]"; + private static final String EMAIL_REGEX = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; + private static final String FROM_VALUE = "Krankenhaus"; private static final String NEGATIV_TEXT = "Wir freuen uns Ihnen mitteilen zu können, dass ihr COVID-19 Testergebnis negativ ist. Der Virus konnte bei Ihnen nicht festegestellt werden."; @@ -42,7 +41,7 @@ public boolean executePush(TestResult testProcess) { boolean pushDone=false; - if(isValidEmailAddress(contact)) { + if(contact.matches(EMAIL_REGEX)) { message.setContact(contact); try { emailService.sendMail(message); @@ -50,7 +49,7 @@ public boolean executePush(TestResult testProcess) { } catch (MailSendingException e) { log.debug("sending mail failed",e); } - }else if (contact.replaceAll(MOBILE_CLEANUP_PATTERN, "").matches(MOBILE_PATTERN)) { + }else if (contact.replaceAll("\\s", "").matches(MOBILE_PATTERN)) { try { message.setContact(contact.replaceAll(MOBILE_CLEANUP_PATTERN, "")); smsService.sendNegativeResultSms(message); @@ -68,15 +67,4 @@ private PushMessage createMessage() { message.setText(NEGATIV_TEXT); return message; } - - public static boolean isValidEmailAddress(String email) { - boolean result = true; - try { - InternetAddress emailAddr = new InternetAddress(email); - emailAddr.validate(); - } catch (AddressException ex) { - result = false; - } - return result; - } } diff --git a/src/test/java/de/wirvsvirus/testresult/backend/rest/TestResultControllerIT.java b/src/test/java/de/wirvsvirus/testresult/backend/rest/TestResultControllerIT.java new file mode 100644 index 0000000..dc24804 --- /dev/null +++ b/src/test/java/de/wirvsvirus/testresult/backend/rest/TestResultControllerIT.java @@ -0,0 +1,69 @@ +package de.wirvsvirus.testresult.backend.rest; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +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.status; + +import org.bson.Document; +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.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import de.wirvsvirus.testresult.backend.persistence.TestResultRepo; + +@AutoConfigureMockMvc +@SpringBootTest +class TestResultControllerIT { + + @Autowired + public MockMvc mvc; + + @Autowired + TestResultRepo repo; + + private static final String RESULTID = "123"; + + @BeforeEach + public void cleanUp() { + repo.deleteAll(); + } + + @Test + public void postAndGetTestResult() throws Exception { + + Document postBody = new Document(); + postBody.put("status", "NEGATIVE"); + + mvc.perform(post("/tests/" + RESULTID) + .with(user("user").password("password").roles("POSTUSER")) + .contentType(MediaType.APPLICATION_JSON) + .content(postBody.toJson())) + .andExpect(status().isOk()); + + mvc.perform(get("/tests/" + RESULTID)) + .andExpect(status().isOk()) + .andDo(r -> { + Document getResult = Document.parse(r.getResponse().getContentAsString()); + assertAll("content contains all the values of a new record", + () -> assertTrue(getResult.containsKey("id")), + () -> assertEquals("123", getResult.getString("id")), + + () -> assertTrue(getResult.containsKey("status")), + () -> assertEquals("NEGATIVE", getResult.getString("status")), + + () -> assertTrue(getResult.containsKey("contact")), () -> assertNull(getResult.get("contact")), + + () -> assertTrue(getResult.containsKey("notified")), + () -> assertEquals(Boolean.FALSE, getResult.getBoolean("notified"))); + }); + } +} \ No newline at end of file diff --git a/src/test/java/de/wirvsvirus/testresult/backend/rest/TestResultControllerNotifictaionTest.java b/src/test/java/de/wirvsvirus/testresult/backend/rest/TestResultControllerNotifictaionTest.java new file mode 100644 index 0000000..274a325 --- /dev/null +++ b/src/test/java/de/wirvsvirus/testresult/backend/rest/TestResultControllerNotifictaionTest.java @@ -0,0 +1,126 @@ +package de.wirvsvirus.testresult.backend.rest; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import de.wirvsvirus.testresult.backend.exceptions.FalseInformedException; +import de.wirvsvirus.testresult.backend.model.TestResult; +import de.wirvsvirus.testresult.backend.model.TestResult.Result; +import de.wirvsvirus.testresult.backend.service.TestResultPushService; +import de.wirvsvirus.testresult.backend.service.TestResultService; + +@ExtendWith(MockitoExtension.class) +public class TestResultControllerNotifictaionTest { + + @Mock + TestResultService testResultService; + @Mock + TestResultPushService pushService; + + @InjectMocks + TestResultController controller; + + @Test + public void pushNotifiactionTriggeredWhenIdUnknownSoFar() throws FalseInformedException { + TestResult tr = new TestResult(); + tr.setContact("some@mail.com"); + tr.setNotified(false); + tr.setStatus(Result.NEGATIVE); + + when(testResultService.getTestResult(eq("123"))).thenReturn(Optional.empty()); + when(pushService.executePush(any())).thenReturn(Boolean.TRUE); + + controller.addTestResult("123", tr); + //check existing + verify(testResultService,times(1)).getTestResult(eq("123")); + //do push + verify(pushService, times(1)).executePush(any()); + //save testresult + verify(testResultService,times(1)).createTestProcess(any()); + } + @Test + public void pushNotifiactionTriggeredWhenStatusIsTheSameButNotYetNotified() throws FalseInformedException { + + TestResult existing = new TestResult(); + existing.setContact("some@mail.com"); + existing.setNotified(false); + existing.setStatus(Result.NEGATIVE); + + TestResult tr = new TestResult(); + tr.setContact("some@mail.com"); + tr.setStatus(Result.NEGATIVE); + + when(testResultService.getTestResult(eq("123"))).thenReturn(Optional.of(existing)); + when(pushService.executePush(any())).thenReturn(Boolean.TRUE); + + controller.addTestResult("123", tr); + //check existing + verify(testResultService,times(1)).getTestResult(eq("123")); + //do push + verify(pushService, times(1)).executePush(any()); + //save testresult + verify(testResultService,times(1)).createTestProcess(any()); + } + + + @Test + public void pushNotifiactionNotTriggeredWhenAlreadyNotified() throws FalseInformedException { + + TestResult existing = new TestResult(); + existing.setContact("some@mail.com"); + existing.setNotified(true); + existing.setStatus(Result.NEGATIVE); + + TestResult tr = new TestResult(); + tr.setContact("some@mail.com"); + tr.setNotified(false); + tr.setStatus(Result.NEGATIVE); + + when(testResultService.getTestResult(eq("123"))).thenReturn(Optional.of(existing)); + + controller.addTestResult("123", tr); + //check existing + verify(testResultService,times(1)).getTestResult(eq("123")); + //do push + verify(pushService, never()).executePush(any()); + //save testresult + verify(testResultService,never()).createTestProcess(any()); + } + + @Test + public void notifyWhenSwitchingFromPendingToNegative() throws FalseInformedException { + + TestResult existing = new TestResult(); + existing.setContact("some@mail.com"); + existing.setNotified(false); + existing.setStatus(Result.PENDING); + + TestResult tr = new TestResult(); + tr.setContact("some@mail.com"); + tr.setNotified(false); + tr.setStatus(Result.NEGATIVE); + + when(testResultService.getTestResult(eq("123"))).thenReturn(Optional.of(existing)); + + controller.addTestResult("123", tr); + //check existing + verify(testResultService,times(1)).getTestResult(eq("123")); + //do push + verify(pushService, times(1)).executePush(any()); + //save testresult + verify(testResultService,times(1)).createTestProcess(any()); + } + +} diff --git a/src/test/java/de/wirvsvirus/testresult/backend/service/TestResultPushServiceTest.java b/src/test/java/de/wirvsvirus/testresult/backend/service/TestResultPushServiceTest.java new file mode 100644 index 0000000..3bc00c6 --- /dev/null +++ b/src/test/java/de/wirvsvirus/testresult/backend/service/TestResultPushServiceTest.java @@ -0,0 +1,105 @@ +package de.wirvsvirus.testresult.backend.service; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import de.wirvsvirus.testresult.backend.model.TestResult; +import de.wirvsvirus.testresult.backend.model.TestResult.Result; + +@ExtendWith(MockitoExtension.class) +public class TestResultPushServiceTest { + + @Mock + private SmsServiceProvider smsService; + + @Mock + private EmailService emailService; + + @InjectMocks + TestResultPushService service; + + @Test + public void sendEmailForNegativeResult() { + + TestResult tr = new TestResult(); + tr.setContact("mail@test.com"); + tr.setStatus(Result.NEGATIVE); + boolean sentFlag = service.executePush(tr); + assertAll("push is done and flag indicates it", () -> assertTrue(sentFlag), + () -> verify(emailService, times(1)).sendMail(any()), + () -> verify(smsService, never()).sendNegativeResultSms(any())); + } + + @ParameterizedTest + @EnumSource(Result.class) + public void doNotSendEmailSentForNonNegativeResult(Result nonNegative) { + + if (nonNegative.equals(Result.NEGATIVE)) + return; + + TestResult tr = new TestResult(); + tr.setContact("mail@test.com"); + tr.setStatus(nonNegative); + boolean sentFlag = service.executePush(tr); + assertAll("push is done and flag indicates it", () -> assertFalse(sentFlag), + () -> verify(emailService, never()).sendMail(any()), + () -> verify(smsService, never()).sendNegativeResultSms(any())); + } + + @ParameterizedTest + @ValueSource(strings = { "004915555", "015555", "+491555" }) + public void sendSmsForNegativeResultAndValidNumbers(String validNumber) { + + TestResult tr = new TestResult(); + tr.setContact(validNumber); + tr.setStatus(Result.NEGATIVE); + boolean sentFlag = service.executePush(tr); + assertAll("push is done and flag indicates it", () -> assertTrue(sentFlag), + () -> verify(emailService, never()).sendMail(any()), + () -> verify(smsService, times(1)).sendNegativeResultSms(any())); + } + + @ParameterizedTest + @EnumSource(Result.class) + public void doNotSendSmsForNonNegativeResult(Result nonNegative) { + + if (nonNegative.equals(Result.NEGATIVE)) + return; + + TestResult tr = new TestResult(); + tr.setContact("017555"); + tr.setStatus(nonNegative); + boolean sentFlag = service.executePush(tr); + assertAll("push is done and flag indicates it", () -> assertFalse(sentFlag), + () -> verify(emailService, never()).sendMail(any()), + () -> verify(smsService, never()).sendNegativeResultSms(any())); + } + + @ParameterizedTest + @ValueSource(strings = { "+4855523", "07555", "foobar@bla", "foo.de", "-12314", "0047123123" }) + public void doNotSendNotificationForInvalidContactPatterns(String invalidContact) { + + TestResult tr = new TestResult(); + tr.setContact(invalidContact); + tr.setStatus(Result.NEGATIVE); + boolean sentFlag = service.executePush(tr); + assertAll("push is done and flag indicates it", () -> assertFalse(sentFlag), + () -> verify(emailService, never()).sendMail(any()), + () -> verify(smsService, never()).sendNegativeResultSms(any())); + } + +} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 0000000..da4b6ae --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,6 @@ +post: + user: user + password: password +admin: + user: admin + password: adminpassword \ No newline at end of file