diff --git a/rest-playground.http b/rest-playground.http index 76f89c52..4e9f2858 100644 --- a/rest-playground.http +++ b/rest-playground.http @@ -1 +1 @@ -GET localhost:8080/api/email \ No newline at end of file +GET localhost:8080/api/emails \ No newline at end of file diff --git a/src/main/java/de/gessnerfl/fakesmtp/model/Email.java b/src/main/java/de/gessnerfl/fakesmtp/model/Email.java index 20fa247d..f9a721ed 100644 --- a/src/main/java/de/gessnerfl/fakesmtp/model/Email.java +++ b/src/main/java/de/gessnerfl/fakesmtp/model/Email.java @@ -1,10 +1,11 @@ package de.gessnerfl.fakesmtp.model; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; -import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.*; import static java.util.Comparator.comparing; @@ -33,7 +34,8 @@ public class Email { @Column(name = "received_on", nullable = false) @Basic(optional = false) @Temporal(TemporalType.TIMESTAMP) - private LocalDateTime receivedOn; + @JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone="UTC") + private ZonedDateTime receivedOn; @Lob @Column(name = "raw_data", nullable = false) @@ -85,11 +87,11 @@ public void setSubject(String subject) { this.subject = subject; } - public LocalDateTime getReceivedOn() { + public ZonedDateTime getReceivedOn() { return receivedOn; } - public void setReceivedOn(LocalDateTime receivedOn) { + public void setReceivedOn(ZonedDateTime receivedOn) { this.receivedOn = receivedOn; } diff --git a/src/main/java/de/gessnerfl/fakesmtp/model/query/ExpressionValueHelper.java b/src/main/java/de/gessnerfl/fakesmtp/model/query/ExpressionValueHelper.java index c2ea8363..75c80759 100644 --- a/src/main/java/de/gessnerfl/fakesmtp/model/query/ExpressionValueHelper.java +++ b/src/main/java/de/gessnerfl/fakesmtp/model/query/ExpressionValueHelper.java @@ -2,18 +2,18 @@ import jakarta.persistence.criteria.Path; -import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; public class ExpressionValueHelper { private ExpressionValueHelper(){} public static Object convertDateIfApplicable(Path path, Object value) { - return path.getJavaType().isAssignableFrom(LocalDateTime.class) ? parseDate(value) : value; + return path.getJavaType().isAssignableFrom(ZonedDateTime.class) ? parseDate(value) : value; } private static Object parseDate(Object value) { String dateString = value.toString(); - return LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + return ZonedDateTime.parse(dateString, DateTimeFormatter.ISO_OFFSET_DATE_TIME); } } diff --git a/src/main/java/de/gessnerfl/fakesmtp/util/TimestampProvider.java b/src/main/java/de/gessnerfl/fakesmtp/util/TimestampProvider.java index 33902b09..3f1d1f6e 100644 --- a/src/main/java/de/gessnerfl/fakesmtp/util/TimestampProvider.java +++ b/src/main/java/de/gessnerfl/fakesmtp/util/TimestampProvider.java @@ -2,13 +2,14 @@ import org.springframework.stereotype.Service; -import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; @Service public class TimestampProvider { - public LocalDateTime now(){ - return LocalDateTime.now(); + public ZonedDateTime now(){ + return ZonedDateTime.now(ZoneId.of("UTC")); } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 3f3ed978..0b7f93b4 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -45,6 +45,7 @@ spring: jackson: serialization: write-dates-as-timestamps: false + time-zone: "UTC" springdoc: swagger-ui: diff --git a/src/test/java/de/gessnerfl/fakesmtp/controller/EmailControllerUtil.java b/src/test/java/de/gessnerfl/fakesmtp/controller/EmailControllerUtil.java index 651be65c..d11493c3 100644 --- a/src/test/java/de/gessnerfl/fakesmtp/controller/EmailControllerUtil.java +++ b/src/test/java/de/gessnerfl/fakesmtp/controller/EmailControllerUtil.java @@ -7,7 +7,8 @@ import org.apache.commons.lang3.RandomStringUtils; import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; class EmailControllerUtil { @@ -15,7 +16,7 @@ private EmailControllerUtil() { } public static Email prepareRandomEmail(int minusMinutes) { var randomToken = RandomStringUtils.randomAlphanumeric(6); - var receivedOn = LocalDateTime.now().minusMinutes(minusMinutes); + var receivedOn = getUtcNow().minusMinutes(minusMinutes); var content = new EmailContent(); content.setContentType(ContentType.PLAIN); @@ -31,7 +32,7 @@ public static Email prepareRandomEmail(int minusMinutes) { public static Email prepareEmail(String subject, String toAdress, int minusMinutes) { var randomToken = RandomStringUtils.randomAlphanumeric(6); - var receivedOn = LocalDateTime.now().minusMinutes(minusMinutes); + var receivedOn = getUtcNow().minusMinutes(minusMinutes); var content = new EmailContent(); content.setContentType(ContentType.PLAIN); @@ -47,7 +48,7 @@ public static Email prepareEmail(String subject, String toAdress, int minusMinut public static Email prepareEmail(String subject, String toAdress, int minusMinutes, String messageId) { var randomToken = RandomStringUtils.randomAlphanumeric(6); - var receivedOn = LocalDateTime.now().minusMinutes(minusMinutes); + var receivedOn = getUtcNow().minusMinutes(minusMinutes); var content = new EmailContent(); content.setContentType(ContentType.PLAIN); @@ -61,12 +62,16 @@ public static Email prepareEmail(String subject, String toAdress, int minusMinut receivedOn, "sender@example.com", toAdress, messageId); } + private static ZonedDateTime getUtcNow() { + return ZonedDateTime.now(ZoneId.of("UTC")); + } + public static Email prepareEmail( EmailAttachment emailAttachment, EmailContent emailContent, String subject, String rawData, - LocalDateTime receivedOn, + ZonedDateTime receivedOn, String fromAddress, String toAdress, String messageId) { diff --git a/src/test/java/de/gessnerfl/fakesmtp/controller/EmailRestControllerMVCIntegrationTest.java b/src/test/java/de/gessnerfl/fakesmtp/controller/EmailRestControllerMVCIntegrationTest.java index cf02c362..e03e4a43 100644 --- a/src/test/java/de/gessnerfl/fakesmtp/controller/EmailRestControllerMVCIntegrationTest.java +++ b/src/test/java/de/gessnerfl/fakesmtp/controller/EmailRestControllerMVCIntegrationTest.java @@ -21,12 +21,13 @@ import org.springframework.test.web.servlet.MockMvc; import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; -import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import static org.hamcrest.MatcherAssert.*; @@ -375,7 +376,7 @@ void shouldSearchEmailsByToAddressContainsOrSubjectEqual() throws Exception { @Test void shouldSearchEmailsByReceivedOnGreaterThanOrEqualDates() throws Exception { - final var now = LocalDateTime.now(); + final var now = getUtcNow(); final var startDate = now.minusMinutes(1); createRandomEmails(5, 5); @@ -383,7 +384,7 @@ void shouldSearchEmailsByReceivedOnGreaterThanOrEqualDates() throws Exception { createRandomEmails(5, 10); createRandomEmails(5, 15); - final var formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + final var formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; final var greaterThanOrEqualExpression = new GreaterThanOrEqualExpression("receivedOn", startDate.format(formatter)); final var searchRequest = SearchRequest.of(greaterThanOrEqualExpression); @@ -402,7 +403,7 @@ void shouldSearchEmailsByReceivedOnGreaterThanOrEqualDates() throws Exception { @Test void shouldSearchEmailsByReceivedOnGreaterThanDates() throws Exception { - final var now = LocalDateTime.now(); + final var now = getUtcNow(); final var startDate = now.minusMinutes(1); createRandomEmails(5, 5); @@ -410,7 +411,7 @@ void shouldSearchEmailsByReceivedOnGreaterThanDates() throws Exception { createRandomEmails(5, 10); createRandomEmails(5, 15); - final var formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + final var formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; final var greaterThanExpression = new GreaterThanExpression("receivedOn", startDate.format(formatter)); final var searchRequest = SearchRequest.of(greaterThanExpression); @@ -429,7 +430,7 @@ void shouldSearchEmailsByReceivedOnGreaterThanDates() throws Exception { @Test void shouldSearchEmailsByReceivedOnLessThanDates() throws Exception { - final var now = LocalDateTime.now(); + final var now = getUtcNow(); final var endDate = now.minusMinutes(20); createRandomEmails(5, 5); @@ -437,7 +438,7 @@ void shouldSearchEmailsByReceivedOnLessThanDates() throws Exception { final var email1 = createRandomEmail(25); createRandomEmails(5, 15); - final var formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + final var formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; final var lessThanExpression = new LessThanExpression("receivedOn", endDate.format(formatter)); final var searchRequest = SearchRequest.of(lessThanExpression); @@ -455,9 +456,13 @@ void shouldSearchEmailsByReceivedOnLessThanDates() throws Exception { assertEquals(List.of(email1), emailSearchResult.getContent()); } + private static ZonedDateTime getUtcNow() { + return ZonedDateTime.now(ZoneId.of("UTC")); + } + @Test void shouldSearchEmailsByReceivedOnLessThanOrEqualDates() throws Exception { - final var now = LocalDateTime.now(); + final var now = getUtcNow(); final var endDate = now.minusMinutes(20); createRandomEmails(5, 5); @@ -465,7 +470,7 @@ void shouldSearchEmailsByReceivedOnLessThanOrEqualDates() throws Exception { final var email1 = createRandomEmail(25); createRandomEmails(5, 15); - final var formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + final var formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; final var lessThanOrEqualExpression = new LessThanOrEqualExpression("receivedOn", endDate.format(formatter)); final var searchRequest = SearchRequest.of(lessThanOrEqualExpression); diff --git a/src/test/java/de/gessnerfl/fakesmtp/repository/EmailRepositoryIntegrationTest.java b/src/test/java/de/gessnerfl/fakesmtp/repository/EmailRepositoryIntegrationTest.java index ae39dbbd..a4330a28 100644 --- a/src/test/java/de/gessnerfl/fakesmtp/repository/EmailRepositoryIntegrationTest.java +++ b/src/test/java/de/gessnerfl/fakesmtp/repository/EmailRepositoryIntegrationTest.java @@ -14,7 +14,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import jakarta.transaction.Transactional; -import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.contains; @@ -76,7 +77,7 @@ void shouldNotDeleteAnyEmailWhenTheNumberOfEmailsDoesNotExceedTheRetentionLimitO private Email createRandomEmail(int minusMinutes) { var randomToken = RandomStringUtils.randomAlphanumeric(6); - var receivedOn = LocalDateTime.now().minusMinutes(minusMinutes); + var receivedOn = ZonedDateTime.now(ZoneId.of("UTC")).minusMinutes(minusMinutes); var content = new EmailContent(); content.setContentType(ContentType.PLAIN); diff --git a/src/test/java/de/gessnerfl/fakesmtp/smtp/server/EmailFactoryTest.java b/src/test/java/de/gessnerfl/fakesmtp/smtp/server/EmailFactoryTest.java index fdb20d1c..5cbd975a 100644 --- a/src/test/java/de/gessnerfl/fakesmtp/smtp/server/EmailFactoryTest.java +++ b/src/test/java/de/gessnerfl/fakesmtp/smtp/server/EmailFactoryTest.java @@ -15,7 +15,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; import static java.util.stream.Collectors.toList; import static org.hamcrest.MatcherAssert.*; @@ -38,7 +39,7 @@ class EmailFactoryTest { @ParameterizedTest @ValueSource(strings = {"mail-with-subject.eml", "mail-with-subject-without-content-type.eml", "multipart-mail-plain-only.eml"}) void shouldCreateMailPlainTextEmails(String testFilename) throws Exception { - var now = LocalDateTime.now(); + var now = getUtcNow(); var data = TestResourceUtil.getTestFileContentBytes(testFilename); var dataAsString = new String(data, StandardCharsets.UTF_8); var rawData = new RawData(SENDER, RECEIVER, data); @@ -50,7 +51,7 @@ void shouldCreateMailPlainTextEmails(String testFilename) throws Exception { assertPlainTextEmail(now, dataAsString, result); } - private void assertPlainTextEmail(LocalDateTime now, String dataAsString, Email result) { + private void assertPlainTextEmail(ZonedDateTime now, String dataAsString, Email result) { assertEquals(SENDER, result.getFromAddress()); assertEquals(RECEIVER, result.getToAddress()); assertEquals("This is the mail title", result.getSubject()); @@ -65,7 +66,7 @@ private void assertPlainTextEmail(LocalDateTime now, String dataAsString, Email @Test void shouldCreateEmailForEmlFileWithSubjectAndContentTypeHtml() throws Exception { - var now = LocalDateTime.now(); + var now = getUtcNow(); var testFilename = "mail-with-subject-and-content-type-html.eml"; var data = TestResourceUtil.getTestFileContentBytes(testFilename); var dataAsString = new String(data, StandardCharsets.UTF_8); @@ -89,7 +90,7 @@ void shouldCreateEmailForEmlFileWithSubjectAndContentTypeHtml() throws Exception @Test void shouldCreateEmailForEmlFileWithSubjectAndContentTypeHtmlAndEmbeddedImage() throws Exception { - var now = LocalDateTime.now(); + var now = getUtcNow(); var testFilename = "mail-with-subect-and-content-type-html-with-inline-image.eml"; var data = TestResourceUtil.getTestFileContentBytes(testFilename); var dataAsString = new String(data, StandardCharsets.UTF_8); @@ -118,7 +119,7 @@ void shouldCreateEmailForEmlFileWithSubjectAndContentTypeHtmlAndEmbeddedImage() @Test void shouldThrowExceptionWhenInlineImageIsBroken() throws Exception { - var now = LocalDateTime.now(); + var now = getUtcNow(); var testFilename = "mail-with-subect-and-content-type-html-with-broken-inline-image.eml"; var data = TestResourceUtil.getTestFileContentBytes(testFilename); var rawData = new RawData(SENDER, RECEIVER, data); @@ -134,7 +135,7 @@ void shouldThrowExceptionWhenInlineImageIsBroken() throws Exception { @Test void shouldCreateEmailForEmlFileWithoutSubjectAndContentTypePlain() throws Exception { - var now = LocalDateTime.now(); + var now = getUtcNow(); var testFilename = "mail-without-subject.eml"; var data = TestResourceUtil.getTestFileContentBytes(testFilename); var dataAsString = new String(data, StandardCharsets.UTF_8); @@ -158,7 +159,7 @@ void shouldCreateEmailForEmlFileWithoutSubjectAndContentTypePlain() throws Excep @Test void shouldCreateMailForPlainText() throws Exception { - var now = LocalDateTime.now(); + var now = getUtcNow(); var dataAsString = "this is just some dummy content"; var data = dataAsString.getBytes(StandardCharsets.UTF_8); var rawData = new RawData(SENDER, RECEIVER, data); @@ -181,7 +182,7 @@ void shouldCreateMailForPlainText() throws Exception { @Test void shouldCreateMailForMultipartWithContentTypeHtmlAndPlain() throws Exception { - var now = LocalDateTime.now(); + var now = getUtcNow(); var testFilename = "multipart-mail.eml"; var data = TestResourceUtil.getTestFileContentBytes(testFilename); var dataAsString = new String(data, StandardCharsets.UTF_8); @@ -206,7 +207,7 @@ void shouldCreateMailForMultipartWithContentTypeHtmlAndPlain() throws Exception @Test void shouldCreateMailForMultipartWithUnknownContentType() throws Exception { - var now = LocalDateTime.now(); + var now = getUtcNow(); var testFilename = "multipart-mail-unknown-content-type.eml"; var data = TestResourceUtil.getTestFileContentBytes(testFilename); var dataAsString = new String(data, StandardCharsets.UTF_8); @@ -231,7 +232,7 @@ void shouldCreateMailForMultipartWithUnknownContentType() throws Exception { @Test void shouldCreateMailForMultipartWithPlainAndHtmlContentAndAttachments() throws Exception { - var now = LocalDateTime.now(); + var now = getUtcNow(); var testFilename = "multipart-mail-html-and-plain-with-attachments.eml"; var data = TestResourceUtil.getTestFileContentBytes(testFilename); var dataAsString = new String(data, StandardCharsets.UTF_8); @@ -253,6 +254,10 @@ void shouldCreateMailForMultipartWithPlainAndHtmlContentAndAttachments() throws assertEquals(now, result.getReceivedOn()); assertThat(result.getAttachments(), hasSize(2)); assertThat(result.getAttachments().stream().map(EmailAttachment::getFilename).collect(toList()), containsInAnyOrder("customizing.css", "app-icon.png")); - } + } + + private static ZonedDateTime getUtcNow() { + return ZonedDateTime.now(ZoneId.of("UTC")); + } } \ No newline at end of file