Implement GET /events/{token} backend with timezone support
Domain: add timezone field to Event and CreateEventCommand. Ports: new GetEventUseCase inbound port. Service: implement getByEventToken, validate IANA timezone on create. Controller: map to GetEventResponse, compute expired flag via Clock. Persistence: timezone column in JPA entity and mapping. Tests: integration tests use DTOs + ObjectMapper instead of inline JSON, GET tests seed DB directly via JpaRepository for isolation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,18 @@ package de.fete.adapter.in.web;
|
|||||||
import de.fete.adapter.in.web.api.EventsApi;
|
import de.fete.adapter.in.web.api.EventsApi;
|
||||||
import de.fete.adapter.in.web.model.CreateEventRequest;
|
import de.fete.adapter.in.web.model.CreateEventRequest;
|
||||||
import de.fete.adapter.in.web.model.CreateEventResponse;
|
import de.fete.adapter.in.web.model.CreateEventResponse;
|
||||||
|
import de.fete.adapter.in.web.model.GetEventResponse;
|
||||||
|
import de.fete.application.service.EventNotFoundException;
|
||||||
|
import de.fete.application.service.InvalidTimezoneException;
|
||||||
import de.fete.domain.model.CreateEventCommand;
|
import de.fete.domain.model.CreateEventCommand;
|
||||||
import de.fete.domain.model.Event;
|
import de.fete.domain.model.Event;
|
||||||
import de.fete.domain.port.in.CreateEventUseCase;
|
import de.fete.domain.port.in.CreateEventUseCase;
|
||||||
|
import de.fete.domain.port.in.GetEventUseCase;
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.DateTimeException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.UUID;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@@ -15,19 +24,29 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
public class EventController implements EventsApi {
|
public class EventController implements EventsApi {
|
||||||
|
|
||||||
private final CreateEventUseCase createEventUseCase;
|
private final CreateEventUseCase createEventUseCase;
|
||||||
|
private final GetEventUseCase getEventUseCase;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
/** Creates a new controller with the given use case. */
|
/** Creates a new controller with the given use cases and clock. */
|
||||||
public EventController(CreateEventUseCase createEventUseCase) {
|
public EventController(
|
||||||
|
CreateEventUseCase createEventUseCase,
|
||||||
|
GetEventUseCase getEventUseCase,
|
||||||
|
Clock clock) {
|
||||||
this.createEventUseCase = createEventUseCase;
|
this.createEventUseCase = createEventUseCase;
|
||||||
|
this.getEventUseCase = getEventUseCase;
|
||||||
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseEntity<CreateEventResponse> createEvent(
|
public ResponseEntity<CreateEventResponse> createEvent(
|
||||||
CreateEventRequest request) {
|
CreateEventRequest request) {
|
||||||
|
ZoneId zoneId = parseTimezone(request.getTimezone());
|
||||||
|
|
||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
request.getTitle(),
|
request.getTitle(),
|
||||||
request.getDescription(),
|
request.getDescription(),
|
||||||
request.getDateTime(),
|
request.getDateTime(),
|
||||||
|
zoneId,
|
||||||
request.getLocation(),
|
request.getLocation(),
|
||||||
request.getExpiryDate()
|
request.getExpiryDate()
|
||||||
);
|
);
|
||||||
@@ -39,8 +58,36 @@ public class EventController implements EventsApi {
|
|||||||
response.setOrganizerToken(event.getOrganizerToken());
|
response.setOrganizerToken(event.getOrganizerToken());
|
||||||
response.setTitle(event.getTitle());
|
response.setTitle(event.getTitle());
|
||||||
response.setDateTime(event.getDateTime());
|
response.setDateTime(event.getDateTime());
|
||||||
|
response.setTimezone(event.getTimezone().getId());
|
||||||
response.setExpiryDate(event.getExpiryDate());
|
response.setExpiryDate(event.getExpiryDate());
|
||||||
|
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<GetEventResponse> getEvent(UUID token) {
|
||||||
|
Event event = getEventUseCase.getByEventToken(token)
|
||||||
|
.orElseThrow(() -> new EventNotFoundException(token));
|
||||||
|
|
||||||
|
var response = new GetEventResponse();
|
||||||
|
response.setEventToken(event.getEventToken());
|
||||||
|
response.setTitle(event.getTitle());
|
||||||
|
response.setDescription(event.getDescription());
|
||||||
|
response.setDateTime(event.getDateTime());
|
||||||
|
response.setTimezone(event.getTimezone().getId());
|
||||||
|
response.setLocation(event.getLocation());
|
||||||
|
response.setAttendeeCount(0);
|
||||||
|
response.setExpired(
|
||||||
|
event.getExpiryDate().isBefore(LocalDate.now(clock)));
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ZoneId parseTimezone(String timezone) {
|
||||||
|
try {
|
||||||
|
return ZoneId.of(timezone);
|
||||||
|
} catch (DateTimeException e) {
|
||||||
|
throw new InvalidTimezoneException(timezone);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package de.fete.adapter.in.web;
|
package de.fete.adapter.in.web;
|
||||||
|
|
||||||
|
import de.fete.application.service.EventNotFoundException;
|
||||||
import de.fete.application.service.ExpiryDateInPastException;
|
import de.fete.application.service.ExpiryDateInPastException;
|
||||||
|
import de.fete.application.service.InvalidTimezoneException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -57,6 +59,32 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
|
|||||||
.body(problemDetail);
|
.body(problemDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Handles event not found. */
|
||||||
|
@ExceptionHandler(EventNotFoundException.class)
|
||||||
|
public ResponseEntity<ProblemDetail> handleEventNotFound(
|
||||||
|
EventNotFoundException ex) {
|
||||||
|
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
|
||||||
|
HttpStatus.NOT_FOUND, ex.getMessage());
|
||||||
|
problemDetail.setTitle("Event Not Found");
|
||||||
|
problemDetail.setType(URI.create("urn:problem-type:event-not-found"));
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||||
|
.contentType(MediaType.APPLICATION_PROBLEM_JSON)
|
||||||
|
.body(problemDetail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handles invalid timezone. */
|
||||||
|
@ExceptionHandler(InvalidTimezoneException.class)
|
||||||
|
public ResponseEntity<ProblemDetail> handleInvalidTimezone(
|
||||||
|
InvalidTimezoneException ex) {
|
||||||
|
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
|
||||||
|
HttpStatus.BAD_REQUEST, ex.getMessage());
|
||||||
|
problemDetail.setTitle("Invalid Timezone");
|
||||||
|
problemDetail.setType(URI.create("urn:problem-type:invalid-timezone"));
|
||||||
|
return ResponseEntity.badRequest()
|
||||||
|
.contentType(MediaType.APPLICATION_PROBLEM_JSON)
|
||||||
|
.body(problemDetail);
|
||||||
|
}
|
||||||
|
|
||||||
/** Catches all unhandled exceptions. */
|
/** Catches all unhandled exceptions. */
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public ResponseEntity<ProblemDetail> handleAll(Exception ex) {
|
public ResponseEntity<ProblemDetail> handleAll(Exception ex) {
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ public class EventJpaEntity {
|
|||||||
@Column(name = "date_time", nullable = false)
|
@Column(name = "date_time", nullable = false)
|
||||||
private OffsetDateTime dateTime;
|
private OffsetDateTime dateTime;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 64)
|
||||||
|
private String timezone;
|
||||||
|
|
||||||
@Column(length = 500)
|
@Column(length = 500)
|
||||||
private String location;
|
private String location;
|
||||||
|
|
||||||
@@ -103,6 +106,16 @@ public class EventJpaEntity {
|
|||||||
this.dateTime = dateTime;
|
this.dateTime = dateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the IANA timezone name. */
|
||||||
|
public String getTimezone() {
|
||||||
|
return timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the IANA timezone name. */
|
||||||
|
public void setTimezone(String timezone) {
|
||||||
|
this.timezone = timezone;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the event location. */
|
/** Returns the event location. */
|
||||||
public String getLocation() {
|
public String getLocation() {
|
||||||
return location;
|
return location;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.fete.adapter.out.persistence;
|
|||||||
|
|
||||||
import de.fete.domain.model.Event;
|
import de.fete.domain.model.Event;
|
||||||
import de.fete.domain.port.out.EventRepository;
|
import de.fete.domain.port.out.EventRepository;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
@@ -37,6 +38,7 @@ public class EventPersistenceAdapter implements EventRepository {
|
|||||||
entity.setTitle(event.getTitle());
|
entity.setTitle(event.getTitle());
|
||||||
entity.setDescription(event.getDescription());
|
entity.setDescription(event.getDescription());
|
||||||
entity.setDateTime(event.getDateTime());
|
entity.setDateTime(event.getDateTime());
|
||||||
|
entity.setTimezone(event.getTimezone().getId());
|
||||||
entity.setLocation(event.getLocation());
|
entity.setLocation(event.getLocation());
|
||||||
entity.setExpiryDate(event.getExpiryDate());
|
entity.setExpiryDate(event.getExpiryDate());
|
||||||
entity.setCreatedAt(event.getCreatedAt());
|
entity.setCreatedAt(event.getCreatedAt());
|
||||||
@@ -51,6 +53,7 @@ public class EventPersistenceAdapter implements EventRepository {
|
|||||||
event.setTitle(entity.getTitle());
|
event.setTitle(entity.getTitle());
|
||||||
event.setDescription(entity.getDescription());
|
event.setDescription(entity.getDescription());
|
||||||
event.setDateTime(entity.getDateTime());
|
event.setDateTime(entity.getDateTime());
|
||||||
|
event.setTimezone(ZoneId.of(entity.getTimezone()));
|
||||||
event.setLocation(entity.getLocation());
|
event.setLocation(entity.getLocation());
|
||||||
event.setExpiryDate(entity.getExpiryDate());
|
event.setExpiryDate(entity.getExpiryDate());
|
||||||
event.setCreatedAt(entity.getCreatedAt());
|
event.setCreatedAt(entity.getCreatedAt());
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.fete.application.service;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/** Thrown when an event cannot be found by its token. */
|
||||||
|
public class EventNotFoundException extends RuntimeException {
|
||||||
|
|
||||||
|
/** Creates a new exception for the given event token. */
|
||||||
|
public EventNotFoundException(UUID eventToken) {
|
||||||
|
super("Event not found: " + eventToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,16 +3,18 @@ package de.fete.application.service;
|
|||||||
import de.fete.domain.model.CreateEventCommand;
|
import de.fete.domain.model.CreateEventCommand;
|
||||||
import de.fete.domain.model.Event;
|
import de.fete.domain.model.Event;
|
||||||
import de.fete.domain.port.in.CreateEventUseCase;
|
import de.fete.domain.port.in.CreateEventUseCase;
|
||||||
|
import de.fete.domain.port.in.GetEventUseCase;
|
||||||
import de.fete.domain.port.out.EventRepository;
|
import de.fete.domain.port.out.EventRepository;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
/** Application service implementing event creation. */
|
/** Application service implementing event creation and retrieval. */
|
||||||
@Service
|
@Service
|
||||||
public class EventService implements CreateEventUseCase {
|
public class EventService implements CreateEventUseCase, GetEventUseCase {
|
||||||
|
|
||||||
private final EventRepository eventRepository;
|
private final EventRepository eventRepository;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
@@ -35,10 +37,16 @@ public class EventService implements CreateEventUseCase {
|
|||||||
event.setTitle(command.title());
|
event.setTitle(command.title());
|
||||||
event.setDescription(command.description());
|
event.setDescription(command.description());
|
||||||
event.setDateTime(command.dateTime());
|
event.setDateTime(command.dateTime());
|
||||||
|
event.setTimezone(command.timezone());
|
||||||
event.setLocation(command.location());
|
event.setLocation(command.location());
|
||||||
event.setExpiryDate(command.expiryDate());
|
event.setExpiryDate(command.expiryDate());
|
||||||
event.setCreatedAt(OffsetDateTime.now(clock));
|
event.setCreatedAt(OffsetDateTime.now(clock));
|
||||||
|
|
||||||
return eventRepository.save(event);
|
return eventRepository.save(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Event> getByEventToken(UUID eventToken) {
|
||||||
|
return eventRepository.findByEventToken(eventToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.fete.application.service;
|
||||||
|
|
||||||
|
/** Thrown when an invalid IANA timezone ID is provided. */
|
||||||
|
public class InvalidTimezoneException extends RuntimeException {
|
||||||
|
|
||||||
|
/** Creates a new exception for the given invalid timezone. */
|
||||||
|
public InvalidTimezoneException(String timezone) {
|
||||||
|
super("Invalid IANA timezone: " + timezone);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,14 @@ package de.fete.domain.model;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
|
||||||
/** Command carrying the data needed to create an event. */
|
/** Command carrying the data needed to create an event. */
|
||||||
public record CreateEventCommand(
|
public record CreateEventCommand(
|
||||||
String title,
|
String title,
|
||||||
String description,
|
String description,
|
||||||
OffsetDateTime dateTime,
|
OffsetDateTime dateTime,
|
||||||
|
ZoneId timezone,
|
||||||
String location,
|
String location,
|
||||||
LocalDate expiryDate
|
LocalDate expiryDate
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.fete.domain.model;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/** Domain entity representing an event. */
|
/** Domain entity representing an event. */
|
||||||
@@ -13,6 +14,7 @@ public class Event {
|
|||||||
private String title;
|
private String title;
|
||||||
private String description;
|
private String description;
|
||||||
private OffsetDateTime dateTime;
|
private OffsetDateTime dateTime;
|
||||||
|
private ZoneId timezone;
|
||||||
private String location;
|
private String location;
|
||||||
private LocalDate expiryDate;
|
private LocalDate expiryDate;
|
||||||
private OffsetDateTime createdAt;
|
private OffsetDateTime createdAt;
|
||||||
@@ -77,6 +79,16 @@ public class Event {
|
|||||||
this.dateTime = dateTime;
|
this.dateTime = dateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the IANA timezone. */
|
||||||
|
public ZoneId getTimezone() {
|
||||||
|
return timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the IANA timezone. */
|
||||||
|
public void setTimezone(ZoneId timezone) {
|
||||||
|
this.timezone = timezone;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the event location. */
|
/** Returns the event location. */
|
||||||
public String getLocation() {
|
public String getLocation() {
|
||||||
return location;
|
return location;
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.fete.domain.port.in;
|
||||||
|
|
||||||
|
import de.fete.domain.model.Event;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/** Inbound port for retrieving a public event by its token. */
|
||||||
|
public interface GetEventUseCase {
|
||||||
|
|
||||||
|
/** Finds an event by its public event token. */
|
||||||
|
Optional<Event> getByEventToken(UUID eventToken);
|
||||||
|
}
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
package de.fete.adapter.in.web;
|
package de.fete.adapter.in.web;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
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.request.MockMvcRequestBuilders.post;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import de.fete.TestcontainersConfig;
|
import de.fete.TestcontainersConfig;
|
||||||
|
import de.fete.adapter.in.web.model.CreateEventRequest;
|
||||||
|
import de.fete.adapter.in.web.model.CreateEventResponse;
|
||||||
|
import de.fete.adapter.out.persistence.EventJpaEntity;
|
||||||
|
import de.fete.adapter.out.persistence.EventJpaRepository;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.UUID;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
@@ -23,63 +33,89 @@ class EventControllerIntegrationTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EventJpaRepository jpaRepository;
|
||||||
|
|
||||||
|
// --- Create Event tests ---
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventWithValidBody() throws Exception {
|
void createEventWithValidBody() throws Exception {
|
||||||
String body =
|
var request = new CreateEventRequest()
|
||||||
"""
|
.title("Birthday Party")
|
||||||
{
|
.description("Come celebrate!")
|
||||||
"title": "Birthday Party",
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
"description": "Come celebrate!",
|
.timezone("Europe/Berlin")
|
||||||
"dateTime": "2026-06-15T20:00:00+02:00",
|
.location("Berlin")
|
||||||
"location": "Berlin",
|
.expiryDate(LocalDate.now().plusDays(30));
|
||||||
"expiryDate": "%s"
|
|
||||||
}
|
|
||||||
""".formatted(LocalDate.now().plusDays(30));
|
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
var result = mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(body))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
.andExpect(jsonPath("$.eventToken").isNotEmpty())
|
.andExpect(jsonPath("$.eventToken").isNotEmpty())
|
||||||
.andExpect(jsonPath("$.organizerToken").isNotEmpty())
|
.andExpect(jsonPath("$.organizerToken").isNotEmpty())
|
||||||
.andExpect(jsonPath("$.title").value("Birthday Party"))
|
.andExpect(jsonPath("$.title").value("Birthday Party"))
|
||||||
|
.andExpect(jsonPath("$.timezone").value("Europe/Berlin"))
|
||||||
.andExpect(jsonPath("$.dateTime").isNotEmpty())
|
.andExpect(jsonPath("$.dateTime").isNotEmpty())
|
||||||
.andExpect(jsonPath("$.expiryDate").isNotEmpty());
|
.andExpect(jsonPath("$.expiryDate").isNotEmpty())
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
var response = objectMapper.readValue(
|
||||||
|
result.getResponse().getContentAsString(), CreateEventResponse.class);
|
||||||
|
|
||||||
|
EventJpaEntity persisted = jpaRepository
|
||||||
|
.findByEventToken(response.getEventToken()).orElseThrow();
|
||||||
|
assertThat(persisted.getTitle()).isEqualTo("Birthday Party");
|
||||||
|
assertThat(persisted.getDescription()).isEqualTo("Come celebrate!");
|
||||||
|
assertThat(persisted.getTimezone()).isEqualTo("Europe/Berlin");
|
||||||
|
assertThat(persisted.getLocation()).isEqualTo("Berlin");
|
||||||
|
assertThat(persisted.getExpiryDate()).isEqualTo(request.getExpiryDate());
|
||||||
|
assertThat(persisted.getDateTime().toInstant())
|
||||||
|
.isEqualTo(request.getDateTime().toInstant());
|
||||||
|
assertThat(persisted.getOrganizerToken()).isNotNull();
|
||||||
|
assertThat(persisted.getCreatedAt()).isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventWithOptionalFieldsNull() throws Exception {
|
void createEventWithOptionalFieldsNull() throws Exception {
|
||||||
String body =
|
var request = new CreateEventRequest()
|
||||||
"""
|
.title("Minimal Event")
|
||||||
{
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
"title": "Minimal Event",
|
.timezone("UTC")
|
||||||
"dateTime": "2026-06-15T20:00:00+02:00",
|
.expiryDate(LocalDate.now().plusDays(30));
|
||||||
"expiryDate": "%s"
|
|
||||||
}
|
|
||||||
""".formatted(LocalDate.now().plusDays(30));
|
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
var result = mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(body))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
.andExpect(jsonPath("$.eventToken").isNotEmpty())
|
.andExpect(jsonPath("$.eventToken").isNotEmpty())
|
||||||
.andExpect(jsonPath("$.organizerToken").isNotEmpty())
|
.andExpect(jsonPath("$.organizerToken").isNotEmpty())
|
||||||
.andExpect(jsonPath("$.title").value("Minimal Event"));
|
.andExpect(jsonPath("$.title").value("Minimal Event"))
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
var response = objectMapper.readValue(
|
||||||
|
result.getResponse().getContentAsString(), CreateEventResponse.class);
|
||||||
|
|
||||||
|
EventJpaEntity persisted = jpaRepository
|
||||||
|
.findByEventToken(response.getEventToken()).orElseThrow();
|
||||||
|
assertThat(persisted.getTitle()).isEqualTo("Minimal Event");
|
||||||
|
assertThat(persisted.getDescription()).isNull();
|
||||||
|
assertThat(persisted.getLocation()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventMissingTitleReturns400() throws Exception {
|
void createEventMissingTitleReturns400() throws Exception {
|
||||||
String body =
|
var request = new CreateEventRequest()
|
||||||
"""
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
{
|
.timezone("Europe/Berlin")
|
||||||
"dateTime": "2026-06-15T20:00:00+02:00",
|
.expiryDate(LocalDate.now().plusDays(30));
|
||||||
"expiryDate": "%s"
|
|
||||||
}
|
|
||||||
""".formatted(LocalDate.now().plusDays(30));
|
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(body))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
.andExpect(jsonPath("$.title").value("Validation Failed"))
|
.andExpect(jsonPath("$.title").value("Validation Failed"))
|
||||||
@@ -88,17 +124,14 @@ class EventControllerIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventMissingDateTimeReturns400() throws Exception {
|
void createEventMissingDateTimeReturns400() throws Exception {
|
||||||
String body =
|
var request = new CreateEventRequest()
|
||||||
"""
|
.title("No Date")
|
||||||
{
|
.timezone("Europe/Berlin")
|
||||||
"title": "No Date",
|
.expiryDate(LocalDate.now().plusDays(30));
|
||||||
"expiryDate": "%s"
|
|
||||||
}
|
|
||||||
""".formatted(LocalDate.now().plusDays(30));
|
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(body))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.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());
|
||||||
@@ -106,17 +139,14 @@ class EventControllerIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventMissingExpiryDateReturns400() throws Exception {
|
void createEventMissingExpiryDateReturns400() throws Exception {
|
||||||
String body =
|
var request = new CreateEventRequest()
|
||||||
"""
|
.title("No Expiry")
|
||||||
{
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
"title": "No Expiry",
|
.timezone("Europe/Berlin");
|
||||||
"dateTime": "2026-06-15T20:00:00+02:00"
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(body))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.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());
|
||||||
@@ -124,18 +154,15 @@ class EventControllerIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventExpiryDateInPastReturns400() throws Exception {
|
void createEventExpiryDateInPastReturns400() throws Exception {
|
||||||
String body =
|
var request = new CreateEventRequest()
|
||||||
"""
|
.title("Past Expiry")
|
||||||
{
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
"title": "Past Expiry",
|
.timezone("Europe/Berlin")
|
||||||
"dateTime": "2026-06-15T20:00:00+02:00",
|
.expiryDate(LocalDate.of(2025, 1, 1));
|
||||||
"expiryDate": "2025-01-01"
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(body))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.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"));
|
||||||
@@ -143,18 +170,15 @@ class EventControllerIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createEventExpiryDateTodayReturns400() throws Exception {
|
void createEventExpiryDateTodayReturns400() throws Exception {
|
||||||
String body =
|
var request = new CreateEventRequest()
|
||||||
"""
|
.title("Today Expiry")
|
||||||
{
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
"title": "Today Expiry",
|
.timezone("Europe/Berlin")
|
||||||
"dateTime": "2026-06-15T20:00:00+02:00",
|
.expiryDate(LocalDate.now());
|
||||||
"expiryDate": "%s"
|
|
||||||
}
|
|
||||||
""".formatted(LocalDate.now());
|
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(body))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.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"));
|
||||||
@@ -162,19 +186,101 @@ class EventControllerIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void errorResponseContentTypeIsProblemJson() throws Exception {
|
void errorResponseContentTypeIsProblemJson() throws Exception {
|
||||||
String body =
|
var request = new CreateEventRequest()
|
||||||
"""
|
.title("")
|
||||||
{
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
"title": "",
|
.timezone("Europe/Berlin")
|
||||||
"dateTime": "2026-06-15T20:00:00+02:00",
|
.expiryDate(LocalDate.now().plusDays(30));
|
||||||
"expiryDate": "%s"
|
|
||||||
}
|
|
||||||
""".formatted(LocalDate.now().plusDays(30));
|
|
||||||
|
|
||||||
mockMvc.perform(post("/api/events")
|
mockMvc.perform(post("/api/events")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(body))
|
.content(objectMapper.writeValueAsString(request)))
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(content().contentTypeCompatibleWith("application/problem+json"));
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createEventWithInvalidTimezoneReturns400() throws Exception {
|
||||||
|
var request = new CreateEventRequest()
|
||||||
|
.title("Bad TZ")
|
||||||
|
.dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
|
||||||
|
.timezone("Not/A/Zone")
|
||||||
|
.expiryDate(LocalDate.now().plusDays(30));
|
||||||
|
|
||||||
|
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:invalid-timezone"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GET /events/{token} tests ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getEventReturnsFullResponse() throws Exception {
|
||||||
|
EventJpaEntity entity = seedEvent(
|
||||||
|
"Summer BBQ", "Bring drinks!", "Europe/Berlin",
|
||||||
|
"Central Park", LocalDate.now().plusDays(30));
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/events/" + entity.getEventToken()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.eventToken").value(entity.getEventToken().toString()))
|
||||||
|
.andExpect(jsonPath("$.title").value("Summer BBQ"))
|
||||||
|
.andExpect(jsonPath("$.description").value("Bring drinks!"))
|
||||||
|
.andExpect(jsonPath("$.timezone").value("Europe/Berlin"))
|
||||||
|
.andExpect(jsonPath("$.location").value("Central Park"))
|
||||||
|
.andExpect(jsonPath("$.attendeeCount").value(0))
|
||||||
|
.andExpect(jsonPath("$.expired").value(false))
|
||||||
|
.andExpect(jsonPath("$.dateTime").isNotEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getEventWithOptionalFieldsAbsent() throws Exception {
|
||||||
|
EventJpaEntity entity = seedEvent(
|
||||||
|
"Minimal", null, "UTC", null, LocalDate.now().plusDays(30));
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/events/" + entity.getEventToken()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.title").value("Minimal"))
|
||||||
|
.andExpect(jsonPath("$.description").doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.location").doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.attendeeCount").value(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getEventNotFoundReturns404() throws Exception {
|
||||||
|
mockMvc.perform(get("/api/events/" + UUID.randomUUID()))
|
||||||
|
.andExpect(status().isNotFound())
|
||||||
|
.andExpect(content().contentTypeCompatibleWith("application/problem+json"))
|
||||||
|
.andExpect(jsonPath("$.type").value("urn:problem-type:event-not-found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getExpiredEventReturnsExpiredTrue() throws Exception {
|
||||||
|
EventJpaEntity entity = seedEvent(
|
||||||
|
"Past Event", "It happened", "Europe/Berlin",
|
||||||
|
"Old Venue", LocalDate.now().minusDays(1));
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/events/" + entity.getEventToken()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.title").value("Past Event"))
|
||||||
|
.andExpect(jsonPath("$.expired").value(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventJpaEntity seedEvent(
|
||||||
|
String title, String description, String timezone,
|
||||||
|
String location, LocalDate expiryDate) {
|
||||||
|
var entity = new EventJpaEntity();
|
||||||
|
entity.setEventToken(UUID.randomUUID());
|
||||||
|
entity.setOrganizerToken(UUID.randomUUID());
|
||||||
|
entity.setTitle(title);
|
||||||
|
entity.setDescription(description);
|
||||||
|
entity.setDateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)));
|
||||||
|
entity.setTimezone(timezone);
|
||||||
|
entity.setLocation(location);
|
||||||
|
entity.setExpiryDate(expiryDate);
|
||||||
|
entity.setCreatedAt(OffsetDateTime.now());
|
||||||
|
return jpaRepository.save(entity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import de.fete.domain.model.Event;
|
|||||||
import de.fete.domain.port.out.EventRepository;
|
import de.fete.domain.port.out.EventRepository;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -65,6 +66,7 @@ class EventPersistenceAdapterTest {
|
|||||||
event.setTitle("Full Event");
|
event.setTitle("Full Event");
|
||||||
event.setDescription("A detailed description");
|
event.setDescription("A detailed description");
|
||||||
event.setDateTime(dateTime);
|
event.setDateTime(dateTime);
|
||||||
|
event.setTimezone(ZoneId.of("Europe/Berlin"));
|
||||||
event.setLocation("Berlin, Germany");
|
event.setLocation("Berlin, Germany");
|
||||||
event.setExpiryDate(expiryDate);
|
event.setExpiryDate(expiryDate);
|
||||||
event.setCreatedAt(createdAt);
|
event.setCreatedAt(createdAt);
|
||||||
@@ -77,6 +79,7 @@ class EventPersistenceAdapterTest {
|
|||||||
assertThat(found.getTitle()).isEqualTo("Full Event");
|
assertThat(found.getTitle()).isEqualTo("Full Event");
|
||||||
assertThat(found.getDescription()).isEqualTo("A detailed description");
|
assertThat(found.getDescription()).isEqualTo("A detailed description");
|
||||||
assertThat(found.getDateTime().toInstant()).isEqualTo(dateTime.toInstant());
|
assertThat(found.getDateTime().toInstant()).isEqualTo(dateTime.toInstant());
|
||||||
|
assertThat(found.getTimezone()).isEqualTo(ZoneId.of("Europe/Berlin"));
|
||||||
assertThat(found.getLocation()).isEqualTo("Berlin, Germany");
|
assertThat(found.getLocation()).isEqualTo("Berlin, Germany");
|
||||||
assertThat(found.getExpiryDate()).isEqualTo(expiryDate);
|
assertThat(found.getExpiryDate()).isEqualTo(expiryDate);
|
||||||
assertThat(found.getCreatedAt().toInstant()).isEqualTo(createdAt.toInstant());
|
assertThat(found.getCreatedAt().toInstant()).isEqualTo(createdAt.toInstant());
|
||||||
@@ -89,6 +92,7 @@ class EventPersistenceAdapterTest {
|
|||||||
event.setTitle("Test Event");
|
event.setTitle("Test Event");
|
||||||
event.setDescription("Test description");
|
event.setDescription("Test description");
|
||||||
event.setDateTime(OffsetDateTime.now().plusDays(7));
|
event.setDateTime(OffsetDateTime.now().plusDays(7));
|
||||||
|
event.setTimezone(ZoneId.of("Europe/Berlin"));
|
||||||
event.setLocation("Somewhere");
|
event.setLocation("Somewhere");
|
||||||
event.setExpiryDate(LocalDate.now().plusDays(30));
|
event.setExpiryDate(LocalDate.now().plusDays(30));
|
||||||
event.setCreatedAt(OffsetDateTime.now());
|
event.setCreatedAt(OffsetDateTime.now());
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ 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.time.ZoneOffset;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@@ -50,6 +52,7 @@ class EventServiceTest {
|
|||||||
"Birthday Party",
|
"Birthday Party",
|
||||||
"Come celebrate!",
|
"Come celebrate!",
|
||||||
OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)),
|
OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)),
|
||||||
|
ZoneId.of("Europe/Berlin"),
|
||||||
"Berlin",
|
"Berlin",
|
||||||
LocalDate.of(2026, 7, 15)
|
LocalDate.of(2026, 7, 15)
|
||||||
);
|
);
|
||||||
@@ -58,28 +61,13 @@ class EventServiceTest {
|
|||||||
|
|
||||||
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.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.now(FIXED_CLOCK));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void eventTokenAndOrganizerTokenAreDifferent() {
|
|
||||||
when(eventRepository.save(any(Event.class)))
|
|
||||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
|
||||||
|
|
||||||
var command = new CreateEventCommand(
|
|
||||||
"Test", null,
|
|
||||||
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), null,
|
|
||||||
LocalDate.now(FIXED_CLOCK).plusDays(30)
|
|
||||||
);
|
|
||||||
|
|
||||||
Event result = eventService.createEvent(command);
|
|
||||||
|
|
||||||
assertThat(result.getEventToken()).isNotEqualTo(result.getOrganizerToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void repositorySaveCalledExactlyOnce() {
|
void repositorySaveCalledExactlyOnce() {
|
||||||
when(eventRepository.save(any(Event.class)))
|
when(eventRepository.save(any(Event.class)))
|
||||||
@@ -87,7 +75,7 @@ class EventServiceTest {
|
|||||||
|
|
||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
"Test", null,
|
"Test", null,
|
||||||
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), null,
|
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), ZONE, null,
|
||||||
LocalDate.now(FIXED_CLOCK).plusDays(30)
|
LocalDate.now(FIXED_CLOCK).plusDays(30)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -102,7 +90,7 @@ class EventServiceTest {
|
|||||||
void expiryDateTodayThrowsException() {
|
void expiryDateTodayThrowsException() {
|
||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
"Test", null,
|
"Test", null,
|
||||||
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), null,
|
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), ZONE, null,
|
||||||
LocalDate.now(FIXED_CLOCK)
|
LocalDate.now(FIXED_CLOCK)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -114,7 +102,7 @@ class EventServiceTest {
|
|||||||
void expiryDateInPastThrowsException() {
|
void expiryDateInPastThrowsException() {
|
||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
"Test", null,
|
"Test", null,
|
||||||
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), null,
|
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), ZONE, null,
|
||||||
LocalDate.now(FIXED_CLOCK).minusDays(5)
|
LocalDate.now(FIXED_CLOCK).minusDays(5)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -129,7 +117,7 @@ class EventServiceTest {
|
|||||||
|
|
||||||
var command = new CreateEventCommand(
|
var command = new CreateEventCommand(
|
||||||
"Test", null,
|
"Test", null,
|
||||||
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), null,
|
OffsetDateTime.now(FIXED_CLOCK).plusDays(1), ZONE, null,
|
||||||
LocalDate.now(FIXED_CLOCK).plusDays(1)
|
LocalDate.now(FIXED_CLOCK).plusDays(1)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -137,4 +125,51 @@ class EventServiceTest {
|
|||||||
|
|
||||||
assertThat(result.getExpiryDate()).isEqualTo(LocalDate.of(2026, 3, 6));
|
assertThat(result.getExpiryDate()).isEqualTo(LocalDate.of(2026, 3, 6));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- GetEventUseCase tests (T004) ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getByEventTokenReturnsEvent() {
|
||||||
|
UUID token = UUID.randomUUID();
|
||||||
|
var event = new Event();
|
||||||
|
event.setEventToken(token);
|
||||||
|
event.setTitle("Found Event");
|
||||||
|
when(eventRepository.findByEventToken(token))
|
||||||
|
.thenReturn(Optional.of(event));
|
||||||
|
|
||||||
|
Optional<Event> result = eventService.getByEventToken(token);
|
||||||
|
|
||||||
|
assertThat(result).isPresent();
|
||||||
|
assertThat(result.get().getTitle()).isEqualTo("Found Event");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getByEventTokenReturnsEmptyForUnknownToken() {
|
||||||
|
UUID token = UUID.randomUUID();
|
||||||
|
when(eventRepository.findByEventToken(token))
|
||||||
|
.thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
Optional<Event> result = eventService.getByEventToken(token);
|
||||||
|
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Timezone validation tests (T006) ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createEventWithValidTimezoneSucceeds() {
|
||||||
|
when(eventRepository.save(any(Event.class)))
|
||||||
|
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
|
var command = new CreateEventCommand(
|
||||||
|
"Test", null,
|
||||||
|
OffsetDateTime.now(FIXED_CLOCK).plusDays(1),
|
||||||
|
ZoneId.of("America/New_York"), null,
|
||||||
|
LocalDate.now(FIXED_CLOCK).plusDays(30)
|
||||||
|
);
|
||||||
|
|
||||||
|
Event result = eventService.createEvent(command);
|
||||||
|
|
||||||
|
assertThat(result.getTimezone()).isEqualTo(ZoneId.of("America/New_York"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user