Validate expiryDate is strictly after eventDate and harden rejection tests
Adds ExpiryDateBeforeEventException (400) when expiryDate <= eventDate, asserts DB row count unchanged after every rejection in integration tests, and replaces all hardcoded dates in EventServiceTest with TODAY-relative expressions derived from the fixed Clock. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package de.fete.adapter.in.web;
|
|||||||
|
|
||||||
import de.fete.application.service.EventExpiredException;
|
import de.fete.application.service.EventExpiredException;
|
||||||
import de.fete.application.service.EventNotFoundException;
|
import de.fete.application.service.EventNotFoundException;
|
||||||
|
import de.fete.application.service.ExpiryDateBeforeEventException;
|
||||||
import de.fete.application.service.ExpiryDateInPastException;
|
import de.fete.application.service.ExpiryDateInPastException;
|
||||||
import de.fete.application.service.InvalidTimezoneException;
|
import de.fete.application.service.InvalidTimezoneException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -47,6 +48,19 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
|
|||||||
return handleExceptionInternal(ex, problemDetail, headers, status, request);
|
return handleExceptionInternal(ex, problemDetail, headers, status, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Handles expiry date before event date. */
|
||||||
|
@ExceptionHandler(ExpiryDateBeforeEventException.class)
|
||||||
|
public ResponseEntity<ProblemDetail> handleExpiryDateBeforeEvent(
|
||||||
|
ExpiryDateBeforeEventException ex) {
|
||||||
|
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
|
||||||
|
HttpStatus.BAD_REQUEST, ex.getMessage());
|
||||||
|
problemDetail.setTitle("Invalid Expiry Date");
|
||||||
|
problemDetail.setType(URI.create("urn:problem-type:expiry-date-before-event"));
|
||||||
|
return ResponseEntity.badRequest()
|
||||||
|
.contentType(MediaType.APPLICATION_PROBLEM_JSON)
|
||||||
|
.body(problemDetail);
|
||||||
|
}
|
||||||
|
|
||||||
/** Handles expiry date validation failures. */
|
/** Handles expiry date validation failures. */
|
||||||
@ExceptionHandler(ExpiryDateInPastException.class)
|
@ExceptionHandler(ExpiryDateInPastException.class)
|
||||||
public ResponseEntity<ProblemDetail> handleExpiryDateInPast(
|
public ResponseEntity<ProblemDetail> handleExpiryDateInPast(
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ public class EventService implements CreateEventUseCase, GetEventUseCase {
|
|||||||
throw new ExpiryDateInPastException(command.expiryDate());
|
throw new ExpiryDateInPastException(command.expiryDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!command.expiryDate().isAfter(command.dateTime().toLocalDate())) {
|
||||||
|
throw new ExpiryDateBeforeEventException(command.expiryDate(), command.dateTime());
|
||||||
|
}
|
||||||
|
|
||||||
var event = new Event();
|
var event = new Event();
|
||||||
event.setEventToken(EventToken.generate());
|
event.setEventToken(EventToken.generate());
|
||||||
event.setOrganizerToken(OrganizerToken.generate());
|
event.setOrganizerToken(OrganizerToken.generate());
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package de.fete.application.service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
|
||||||
|
/** Thrown when an event's expiry date is not after the event date. */
|
||||||
|
public class ExpiryDateBeforeEventException extends RuntimeException {
|
||||||
|
|
||||||
|
/** Creates a new exception for the given dates. */
|
||||||
|
public ExpiryDateBeforeEventException(LocalDate expiryDate, OffsetDateTime dateTime) {
|
||||||
|
super("Expiry date " + expiryDate + " must be after event date " + dateTime.toLocalDate());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,7 +56,7 @@ class EventControllerIntegrationTest {
|
|||||||
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
.timezone("Europe/Berlin")
|
.timezone("Europe/Berlin")
|
||||||
.location("Berlin")
|
.location("Berlin")
|
||||||
.expiryDate(LocalDate.now().plusDays(30));
|
.expiryDate(LocalDate.of(2026, 6, 16));
|
||||||
|
|
||||||
var result = mockMvc.perform(post("/api/events")
|
var result = mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
@@ -92,7 +92,7 @@ class EventControllerIntegrationTest {
|
|||||||
.title("Minimal Event")
|
.title("Minimal Event")
|
||||||
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
.timezone("UTC")
|
.timezone("UTC")
|
||||||
.expiryDate(LocalDate.now().plusDays(30));
|
.expiryDate(LocalDate.of(2026, 6, 16));
|
||||||
|
|
||||||
var result = mockMvc.perform(post("/api/events")
|
var result = mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
@@ -115,10 +115,12 @@ class EventControllerIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventMissingTitleReturns400() throws Exception {
|
void createEventMissingTitleReturns400() throws Exception {
|
||||||
|
long countBefore = jpaRepository.count();
|
||||||
|
|
||||||
var request = new CreateEventRequest()
|
var request = new CreateEventRequest()
|
||||||
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
.timezone("Europe/Berlin")
|
.timezone("Europe/Berlin")
|
||||||
.expiryDate(LocalDate.now().plusDays(30));
|
.expiryDate(LocalDate.of(2026, 6, 16));
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
@@ -127,14 +129,18 @@ class EventControllerIntegrationTest {
|
|||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
.andExpect(jsonPath("$.title").value("Validation Failed"))
|
.andExpect(jsonPath("$.title").value("Validation Failed"))
|
||||||
.andExpect(jsonPath("$.fieldErrors").isArray());
|
.andExpect(jsonPath("$.fieldErrors").isArray());
|
||||||
|
|
||||||
|
assertThat(jpaRepository.count()).isEqualTo(countBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventMissingDateTimeReturns400() throws Exception {
|
void createEventMissingDateTimeReturns400() throws Exception {
|
||||||
|
long countBefore = jpaRepository.count();
|
||||||
|
|
||||||
var request = new CreateEventRequest()
|
var request = new CreateEventRequest()
|
||||||
.title("No Date")
|
.title("No Date")
|
||||||
.timezone("Europe/Berlin")
|
.timezone("Europe/Berlin")
|
||||||
.expiryDate(LocalDate.now().plusDays(30));
|
.expiryDate(LocalDate.of(2026, 6, 16));
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
@@ -142,10 +148,14 @@ class EventControllerIntegrationTest {
|
|||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
.andExpect(jsonPath("$.fieldErrors").isArray());
|
.andExpect(jsonPath("$.fieldErrors").isArray());
|
||||||
|
|
||||||
|
assertThat(jpaRepository.count()).isEqualTo(countBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventMissingExpiryDateReturns400() throws Exception {
|
void createEventMissingExpiryDateReturns400() throws Exception {
|
||||||
|
long countBefore = jpaRepository.count();
|
||||||
|
|
||||||
var request = new CreateEventRequest()
|
var request = new CreateEventRequest()
|
||||||
.title("No Expiry")
|
.title("No Expiry")
|
||||||
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
@@ -157,10 +167,14 @@ class EventControllerIntegrationTest {
|
|||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
.andExpect(jsonPath("$.fieldErrors").isArray());
|
.andExpect(jsonPath("$.fieldErrors").isArray());
|
||||||
|
|
||||||
|
assertThat(jpaRepository.count()).isEqualTo(countBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventExpiryDateInPastReturns400() throws Exception {
|
void createEventExpiryDateInPastReturns400() throws Exception {
|
||||||
|
long countBefore = jpaRepository.count();
|
||||||
|
|
||||||
var request = new CreateEventRequest()
|
var request = new CreateEventRequest()
|
||||||
.title("Past Expiry")
|
.title("Past Expiry")
|
||||||
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
@@ -173,10 +187,14 @@ class EventControllerIntegrationTest {
|
|||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
.andExpect(jsonPath("$.type").value("urn:problem-type:expiry-date-in-past"));
|
.andExpect(jsonPath("$.type").value("urn:problem-type:expiry-date-in-past"));
|
||||||
|
|
||||||
|
assertThat(jpaRepository.count()).isEqualTo(countBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventExpiryDateTodayReturns400() throws Exception {
|
void createEventExpiryDateTodayReturns400() throws Exception {
|
||||||
|
long countBefore = jpaRepository.count();
|
||||||
|
|
||||||
var request = new CreateEventRequest()
|
var request = new CreateEventRequest()
|
||||||
.title("Today Expiry")
|
.title("Today Expiry")
|
||||||
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
@@ -189,6 +207,48 @@ class EventControllerIntegrationTest {
|
|||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
.andExpect(jsonPath("$.type").value("urn:problem-type:expiry-date-in-past"));
|
.andExpect(jsonPath("$.type").value("urn:problem-type:expiry-date-in-past"));
|
||||||
|
|
||||||
|
assertThat(jpaRepository.count()).isEqualTo(countBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createEventExpiryDateBeforeEventDateReturns400() throws Exception {
|
||||||
|
long countBefore = jpaRepository.count();
|
||||||
|
|
||||||
|
var request = new CreateEventRequest()
|
||||||
|
.title("Bad Expiry")
|
||||||
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
|
.timezone("Europe/Berlin")
|
||||||
|
.expiryDate(LocalDate.of(2026, 6, 10));
|
||||||
|
|
||||||
|
mockMvc.perform(post("/api/events")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
|
.andExpect(jsonPath("$.type").value("urn:problem-type:expiry-date-before-event"));
|
||||||
|
|
||||||
|
assertThat(jpaRepository.count()).isEqualTo(countBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createEventExpiryDateSameAsEventDateReturns400() throws Exception {
|
||||||
|
long countBefore = jpaRepository.count();
|
||||||
|
|
||||||
|
var request = new CreateEventRequest()
|
||||||
|
.title("Same Day Expiry")
|
||||||
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
|
.timezone("Europe/Berlin")
|
||||||
|
.expiryDate(LocalDate.of(2026, 6, 15));
|
||||||
|
|
||||||
|
mockMvc.perform(post("/api/events")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
|
.andExpect(jsonPath("$.type").value("urn:problem-type:expiry-date-before-event"));
|
||||||
|
|
||||||
|
assertThat(jpaRepository.count()).isEqualTo(countBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -197,7 +257,7 @@ class EventControllerIntegrationTest {
|
|||||||
.title("")
|
.title("")
|
||||||
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
.timezone("Europe/Berlin")
|
.timezone("Europe/Berlin")
|
||||||
.expiryDate(LocalDate.now().plusDays(30));
|
.expiryDate(LocalDate.of(2026, 6, 16));
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
@@ -208,11 +268,13 @@ class EventControllerIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventWithInvalidTimezoneReturns400() throws Exception {
|
void createEventWithInvalidTimezoneReturns400() throws Exception {
|
||||||
|
long countBefore = jpaRepository.count();
|
||||||
|
|
||||||
var request = new CreateEventRequest()
|
var request = new CreateEventRequest()
|
||||||
.title("Bad TZ")
|
.title("Bad TZ")
|
||||||
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
.timezone("Not/A/Zone")
|
.timezone("Not/A/Zone")
|
||||||
.expiryDate(LocalDate.now().plusDays(30));
|
.expiryDate(LocalDate.of(2026, 6, 16));
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
@@ -220,6 +282,8 @@ class EventControllerIntegrationTest {
|
|||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
.andExpect(jsonPath("$.type").value("urn:problem-type:invalid-timezone"));
|
.andExpect(jsonPath("$.type").value("urn:problem-type:invalid-timezone"));
|
||||||
|
|
||||||
|
assertThat(jpaRepository.count()).isEqualTo(countBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GET /events/{token} tests ---
|
// --- GET /events/{token} tests ---
|
||||||
@@ -307,6 +371,7 @@ class EventControllerIntegrationTest {
|
|||||||
EventJpaEntity event = seedEvent(
|
EventJpaEntity event = seedEvent(
|
||||||
"RSVP Event", null, "Europe/Berlin",
|
"RSVP Event", null, "Europe/Berlin",
|
||||||
null, LocalDate.now().plusDays(30));
|
null, LocalDate.now().plusDays(30));
|
||||||
|
long countBefore = rsvpJpaRepository.count();
|
||||||
|
|
||||||
var request = new CreateRsvpRequest().name("");
|
var request = new CreateRsvpRequest().name("");
|
||||||
|
|
||||||
@@ -315,6 +380,8 @@ class EventControllerIntegrationTest {
|
|||||||
.content(objectMapper.writeValueAsString(request)))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"));
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"));
|
||||||
|
|
||||||
|
assertThat(rsvpJpaRepository.count()).isEqualTo(countBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -339,6 +406,8 @@ class EventControllerIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createRsvpForUnknownEventReturns404() throws Exception {
|
void createRsvpForUnknownEventReturns404() throws Exception {
|
||||||
|
long countBefore = rsvpJpaRepository.count();
|
||||||
|
|
||||||
var request = new CreateRsvpRequest().name("Ghost");
|
var request = new CreateRsvpRequest().name("Ghost");
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events/" + UUID.randomUUID() + "/rsvps")
|
mockMvc.perform(post("/api/events/" + UUID.randomUUID() + "/rsvps")
|
||||||
@@ -347,6 +416,8 @@ class EventControllerIntegrationTest {
|
|||||||
.andExpect(status().isNotFound())
|
.andExpect(status().isNotFound())
|
||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
.andExpect(jsonPath("$.type").value("urn:problem-type:event-not-found"));
|
.andExpect(jsonPath("$.type").value("urn:problem-type:event-not-found"));
|
||||||
|
|
||||||
|
assertThat(rsvpJpaRepository.count()).isEqualTo(countBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -354,6 +425,7 @@ class EventControllerIntegrationTest {
|
|||||||
EventJpaEntity event = seedEvent(
|
EventJpaEntity event = seedEvent(
|
||||||
"Expired Party", null, "Europe/Berlin",
|
"Expired Party", null, "Europe/Berlin",
|
||||||
null, LocalDate.now().minusDays(1));
|
null, LocalDate.now().minusDays(1));
|
||||||
|
long countBefore = rsvpJpaRepository.count();
|
||||||
|
|
||||||
var request = new CreateRsvpRequest().name("Late Guest");
|
var request = new CreateRsvpRequest().name("Late Guest");
|
||||||
|
|
||||||
@@ -363,6 +435,8 @@ class EventControllerIntegrationTest {
|
|||||||
.andExpect(status().isConflict())
|
.andExpect(status().isConflict())
|
||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
.andExpect(jsonPath("$.type").value("urn:problem-type:event-expired"));
|
.andExpect(jsonPath("$.type").value("urn:problem-type:event-expired"));
|
||||||
|
|
||||||
|
assertThat(rsvpJpaRepository.count()).isEqualTo(countBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EventJpaEntity seedEvent(
|
private EventJpaEntity seedEvent(
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import java.time.Instant;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -32,6 +31,7 @@ class EventServiceTest {
|
|||||||
private static final Instant FIXED_INSTANT =
|
private static final Instant FIXED_INSTANT =
|
||||||
LocalDate.of(2026, 3, 5).atStartOfDay(ZONE).toInstant();
|
LocalDate.of(2026, 3, 5).atStartOfDay(ZONE).toInstant();
|
||||||
private static final Clock FIXED_CLOCK = Clock.fixed(FIXED_INSTANT, ZONE);
|
private static final Clock FIXED_CLOCK = Clock.fixed(FIXED_INSTANT, ZONE);
|
||||||
|
private static final LocalDate TODAY = LocalDate.ofInstant(FIXED_INSTANT, ZONE);
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private EventRepository eventRepository;
|
private EventRepository eventRepository;
|
||||||
@@ -51,21 +51,21 @@ class EventServiceTest {
|
|||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
"Birthday Party",
|
"Birthday Party",
|
||||||
"Come celebrate!",
|
"Come celebrate!",
|
||||||
OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)),
|
TODAY.plusDays(90).atStartOfDay(ZONE).toOffsetDateTime(),
|
||||||
ZoneId.of("Europe/Berlin"),
|
ZONE,
|
||||||
"Berlin",
|
"Berlin",
|
||||||
LocalDate.of(2026, 7, 15)
|
TODAY.plusDays(120)
|
||||||
);
|
);
|
||||||
|
|
||||||
Event result = eventService.createEvent(command);
|
Event result = eventService.createEvent(command);
|
||||||
|
|
||||||
assertThat(result.getTitle()).isEqualTo("Birthday Party");
|
assertThat(result.getTitle()).isEqualTo("Birthday Party");
|
||||||
assertThat(result.getDescription()).isEqualTo("Come celebrate!");
|
assertThat(result.getDescription()).isEqualTo("Come celebrate!");
|
||||||
assertThat(result.getTimezone()).isEqualTo(ZoneId.of("Europe/Berlin"));
|
assertThat(result.getTimezone()).isEqualTo(ZONE);
|
||||||
assertThat(result.getLocation()).isEqualTo("Berlin");
|
assertThat(result.getLocation()).isEqualTo("Berlin");
|
||||||
assertThat(result.getEventToken()).isNotNull();
|
assertThat(result.getEventToken()).isNotNull();
|
||||||
assertThat(result.getOrganizerToken()).isNotNull();
|
assertThat(result.getOrganizerToken()).isNotNull();
|
||||||
assertThat(result.getCreatedAt()).isEqualTo(OffsetDateTime.now(FIXED_CLOCK));
|
assertThat(result.getCreatedAt()).isEqualTo(OffsetDateTime.ofInstant(FIXED_INSTANT, ZONE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -75,8 +75,8 @@ class EventServiceTest {
|
|||||||
|
|
||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
"Test", null,
|
"Test", null,
|
||||||
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), ZONE, null,
|
TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(), ZONE, null,
|
||||||
LocalDate.now(FIXED_CLOCK).plusDays(30)
|
TODAY.plusDays(11)
|
||||||
);
|
);
|
||||||
|
|
||||||
eventService.createEvent(command);
|
eventService.createEvent(command);
|
||||||
@@ -90,8 +90,8 @@ class EventServiceTest {
|
|||||||
void expiryDateTodayThrowsException() {
|
void expiryDateTodayThrowsException() {
|
||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
"Test", null,
|
"Test", null,
|
||||||
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), ZONE, null,
|
TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(), ZONE, null,
|
||||||
LocalDate.now(FIXED_CLOCK)
|
TODAY
|
||||||
);
|
);
|
||||||
|
|
||||||
assertThatThrownBy(() -> eventService.createEvent(command))
|
assertThatThrownBy(() -> eventService.createEvent(command))
|
||||||
@@ -102,8 +102,8 @@ class EventServiceTest {
|
|||||||
void expiryDateInPastThrowsException() {
|
void expiryDateInPastThrowsException() {
|
||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
"Test", null,
|
"Test", null,
|
||||||
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), ZONE, null,
|
TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(), ZONE, null,
|
||||||
LocalDate.now(FIXED_CLOCK).minusDays(5)
|
TODAY.minusDays(5)
|
||||||
);
|
);
|
||||||
|
|
||||||
assertThatThrownBy(() -> eventService.createEvent(command))
|
assertThatThrownBy(() -> eventService.createEvent(command))
|
||||||
@@ -117,13 +117,56 @@ class EventServiceTest {
|
|||||||
|
|
||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
"Test", null,
|
"Test", null,
|
||||||
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), ZONE, null,
|
TODAY.plusDays(1).atStartOfDay(ZONE).toOffsetDateTime(), ZONE, null,
|
||||||
LocalDate.now(FIXED_CLOCK).plusDays(1)
|
TODAY.plusDays(2)
|
||||||
);
|
);
|
||||||
|
|
||||||
Event result = eventService.createEvent(command);
|
Event result = eventService.createEvent(command);
|
||||||
|
|
||||||
assertThat(result.getExpiryDate()).isEqualTo(LocalDate.of(2026, 3, 6));
|
assertThat(result.getExpiryDate()).isEqualTo(TODAY.plusDays(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void expiryDateSameAsEventDateThrowsException() {
|
||||||
|
var command = new CreateEventCommand(
|
||||||
|
"Test", null,
|
||||||
|
TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(),
|
||||||
|
ZONE, null,
|
||||||
|
TODAY.plusDays(10)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> eventService.createEvent(command))
|
||||||
|
.isInstanceOf(ExpiryDateBeforeEventException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void expiryDateBeforeEventDateThrowsException() {
|
||||||
|
var command = new CreateEventCommand(
|
||||||
|
"Test", null,
|
||||||
|
TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(),
|
||||||
|
ZONE, null,
|
||||||
|
TODAY.plusDays(5)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> eventService.createEvent(command))
|
||||||
|
.isInstanceOf(ExpiryDateBeforeEventException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void expiryDateDayAfterEventDateSucceeds() {
|
||||||
|
when(eventRepository.save(any(Event.class)))
|
||||||
|
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
|
var command = new CreateEventCommand(
|
||||||
|
"Test", null,
|
||||||
|
TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(),
|
||||||
|
ZONE, null,
|
||||||
|
TODAY.plusDays(11)
|
||||||
|
);
|
||||||
|
|
||||||
|
Event result = eventService.createEvent(command);
|
||||||
|
|
||||||
|
assertThat(result.getExpiryDate()).isEqualTo(TODAY.plusDays(11));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GetEventUseCase tests (T004) ---
|
// --- GetEventUseCase tests (T004) ---
|
||||||
@@ -163,9 +206,9 @@ class EventServiceTest {
|
|||||||
|
|
||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
"Test", null,
|
"Test", null,
|
||||||
OffsetDateTime.now(FIXED_CLOCK).plusDays(1),
|
TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(),
|
||||||
ZoneId.of("America/New_York"), null,
|
ZoneId.of("America/New_York"), null,
|
||||||
LocalDate.now(FIXED_CLOCK).plusDays(30)
|
TODAY.plusDays(11)
|
||||||
);
|
);
|
||||||
|
|
||||||
Event result = eventService.createEvent(command);
|
Event result = eventService.createEvent(command);
|
||||||
|
|||||||
Reference in New Issue
Block a user