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>
219 lines
6.6 KiB
Java
219 lines
6.6 KiB
Java
package de.fete.application.service;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
import static org.mockito.Mockito.times;
|
|
import static org.mockito.Mockito.verify;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
import de.fete.domain.model.CreateEventCommand;
|
|
import de.fete.domain.model.Event;
|
|
import de.fete.domain.model.EventToken;
|
|
import de.fete.domain.port.out.EventRepository;
|
|
import java.time.Clock;
|
|
import java.time.Instant;
|
|
import java.time.LocalDate;
|
|
import java.time.OffsetDateTime;
|
|
import java.time.ZoneId;
|
|
import java.util.Optional;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
import org.mockito.ArgumentCaptor;
|
|
import org.mockito.Mock;
|
|
import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
|
@ExtendWith(MockitoExtension.class)
|
|
class EventServiceTest {
|
|
|
|
private static final ZoneId ZONE = ZoneId.of("Europe/Berlin");
|
|
private static final Instant FIXED_INSTANT =
|
|
LocalDate.of(2026, 3, 5).atStartOfDay(ZONE).toInstant();
|
|
private static final Clock FIXED_CLOCK = Clock.fixed(FIXED_INSTANT, ZONE);
|
|
private static final LocalDate TODAY = LocalDate.ofInstant(FIXED_INSTANT, ZONE);
|
|
|
|
@Mock
|
|
private EventRepository eventRepository;
|
|
|
|
private EventService eventService;
|
|
|
|
@BeforeEach
|
|
void setUp() {
|
|
eventService = new EventService(eventRepository, FIXED_CLOCK);
|
|
}
|
|
|
|
@Test
|
|
void createEventWithValidCommand() {
|
|
when(eventRepository.save(any(Event.class)))
|
|
.thenAnswer(invocation -> invocation.getArgument(0));
|
|
|
|
var command = new CreateEventCommand(
|
|
"Birthday Party",
|
|
"Come celebrate!",
|
|
TODAY.plusDays(90).atStartOfDay(ZONE).toOffsetDateTime(),
|
|
ZONE,
|
|
"Berlin",
|
|
TODAY.plusDays(120)
|
|
);
|
|
|
|
Event result = eventService.createEvent(command);
|
|
|
|
assertThat(result.getTitle()).isEqualTo("Birthday Party");
|
|
assertThat(result.getDescription()).isEqualTo("Come celebrate!");
|
|
assertThat(result.getTimezone()).isEqualTo(ZONE);
|
|
assertThat(result.getLocation()).isEqualTo("Berlin");
|
|
assertThat(result.getEventToken()).isNotNull();
|
|
assertThat(result.getOrganizerToken()).isNotNull();
|
|
assertThat(result.getCreatedAt()).isEqualTo(OffsetDateTime.ofInstant(FIXED_INSTANT, ZONE));
|
|
}
|
|
|
|
@Test
|
|
void repositorySaveCalledExactlyOnce() {
|
|
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)
|
|
);
|
|
|
|
eventService.createEvent(command);
|
|
|
|
ArgumentCaptor<Event> captor = ArgumentCaptor.forClass(Event.class);
|
|
verify(eventRepository, times(1)).save(captor.capture());
|
|
assertThat(captor.getValue().getTitle()).isEqualTo("Test");
|
|
}
|
|
|
|
@Test
|
|
void expiryDateTodayThrowsException() {
|
|
var command = new CreateEventCommand(
|
|
"Test", null,
|
|
TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(), ZONE, null,
|
|
TODAY
|
|
);
|
|
|
|
assertThatThrownBy(() -> eventService.createEvent(command))
|
|
.isInstanceOf(ExpiryDateInPastException.class);
|
|
}
|
|
|
|
@Test
|
|
void expiryDateInPastThrowsException() {
|
|
var command = new CreateEventCommand(
|
|
"Test", null,
|
|
TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(), ZONE, null,
|
|
TODAY.minusDays(5)
|
|
);
|
|
|
|
assertThatThrownBy(() -> eventService.createEvent(command))
|
|
.isInstanceOf(ExpiryDateInPastException.class);
|
|
}
|
|
|
|
@Test
|
|
void expiryDateTomorrowSucceeds() {
|
|
when(eventRepository.save(any(Event.class)))
|
|
.thenAnswer(invocation -> invocation.getArgument(0));
|
|
|
|
var command = new CreateEventCommand(
|
|
"Test", null,
|
|
TODAY.plusDays(1).atStartOfDay(ZONE).toOffsetDateTime(), ZONE, null,
|
|
TODAY.plusDays(2)
|
|
);
|
|
|
|
Event result = eventService.createEvent(command);
|
|
|
|
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) ---
|
|
|
|
@Test
|
|
void getByEventTokenReturnsEvent() {
|
|
EventToken token = EventToken.generate();
|
|
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() {
|
|
EventToken token = EventToken.generate();
|
|
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,
|
|
TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(),
|
|
ZoneId.of("America/New_York"), null,
|
|
TODAY.plusDays(11)
|
|
);
|
|
|
|
Event result = eventService.createEvent(command);
|
|
|
|
assertThat(result.getTimezone()).isEqualTo(ZoneId.of("America/New_York"));
|
|
}
|
|
}
|