diff --git a/backend/src/main/java/de/fete/adapter/in/web/EventController.java b/backend/src/main/java/de/fete/adapter/in/web/EventController.java index 8a94dcc..521d4fd 100644 --- a/backend/src/main/java/de/fete/adapter/in/web/EventController.java +++ b/backend/src/main/java/de/fete/adapter/in/web/EventController.java @@ -9,8 +9,8 @@ import de.fete.adapter.in.web.model.CreateRsvpResponse; import de.fete.adapter.in.web.model.GetAttendeesResponse; import de.fete.adapter.in.web.model.GetEventResponse; import de.fete.adapter.in.web.model.PatchEventRequest; -import de.fete.application.service.EventNotFoundException; -import de.fete.application.service.InvalidTimezoneException; +import de.fete.application.service.exception.EventNotFoundException; +import de.fete.application.service.exception.InvalidTimezoneException; import de.fete.domain.model.CreateEventCommand; import de.fete.domain.model.Event; import de.fete.domain.model.EventToken; @@ -78,11 +78,11 @@ public class EventController implements EventsApi { Event event = createEventUseCase.createEvent(command); var response = new CreateEventResponse(); - response.setEventToken(event.getEventToken().value()); - response.setOrganizerToken(event.getOrganizerToken().value()); - response.setTitle(event.getTitle()); - response.setDateTime(event.getDateTime()); - response.setTimezone(event.getTimezone().getId()); + response.setEventToken(event.eventToken().value()); + response.setOrganizerToken(event.organizerToken().value()); + response.setTitle(event.title()); + response.setDateTime(event.dateTime()); + response.setTimezone(event.timezone().getId()); return ResponseEntity.status(HttpStatus.CREATED).body(response); } @@ -94,16 +94,16 @@ public class EventController implements EventsApi { .orElseThrow(() -> new EventNotFoundException(eventToken)); var response = new GetEventResponse(); - response.setEventToken(event.getEventToken().value()); - response.setTitle(event.getTitle()); - response.setDescription(event.getDescription()); - response.setDateTime(event.getDateTime()); - response.setTimezone(event.getTimezone().getId()); - response.setLocation(event.getLocation()); + response.setEventToken(event.eventToken().value()); + response.setTitle(event.title()); + response.setDescription(event.description()); + response.setDateTime(event.dateTime()); + response.setTimezone(event.timezone().getId()); + response.setLocation(event.location()); response.setAttendeeCount( (int) countAttendeesByEventUseCase.countByEvent(evtToken)); - response.setCancelled(event.isCancelled()); - response.setCancellationReason(event.getCancellationReason()); + response.setCancelled(event.cancelled()); + response.setCancellationReason(event.cancellationReason()); return ResponseEntity.ok(response); } @@ -145,8 +145,8 @@ public class EventController implements EventsApi { Rsvp rsvp = createRsvpUseCase.createRsvp(evtToken, createRsvpRequest.getName()); var response = new CreateRsvpResponse(); - response.setRsvpToken(rsvp.getRsvpToken().value()); - response.setName(rsvp.getName()); + response.setRsvpToken(rsvp.rsvpToken().value()); + response.setName(rsvp.name()); return ResponseEntity.status(HttpStatus.CREATED).body(response); } diff --git a/backend/src/main/java/de/fete/adapter/in/web/GlobalExceptionHandler.java b/backend/src/main/java/de/fete/adapter/in/web/GlobalExceptionHandler.java index bd16dcd..008fafd 100644 --- a/backend/src/main/java/de/fete/adapter/in/web/GlobalExceptionHandler.java +++ b/backend/src/main/java/de/fete/adapter/in/web/GlobalExceptionHandler.java @@ -1,13 +1,13 @@ package de.fete.adapter.in.web; -import de.fete.application.service.EventAlreadyCancelledException; -import de.fete.application.service.EventCancelledException; -import de.fete.application.service.EventExpiredException; -import de.fete.application.service.EventNotFoundException; -import de.fete.application.service.ExpiryDateBeforeEventException; -import de.fete.application.service.ExpiryDateInPastException; -import de.fete.application.service.InvalidOrganizerTokenException; -import de.fete.application.service.InvalidTimezoneException; +import de.fete.application.service.exception.EventAlreadyCancelledException; +import de.fete.application.service.exception.EventCancelledException; +import de.fete.application.service.exception.EventExpiredException; +import de.fete.application.service.exception.EventNotFoundException; +import de.fete.application.service.exception.ExpiryDateBeforeEventException; +import de.fete.application.service.exception.ExpiryDateInPastException; +import de.fete.application.service.exception.InvalidOrganizerTokenException; +import de.fete.application.service.exception.InvalidTimezoneException; import java.net.URI; import java.util.List; import java.util.Map; diff --git a/backend/src/main/java/de/fete/adapter/in/web/SpaController.java b/backend/src/main/java/de/fete/adapter/in/web/SpaController.java index e57e7f2..fdc53aa 100644 --- a/backend/src/main/java/de/fete/adapter/in/web/SpaController.java +++ b/backend/src/main/java/de/fete/adapter/in/web/SpaController.java @@ -86,11 +86,11 @@ public class SpaController { private Map buildEventMeta(Event event, String baseUrl) { var tags = new LinkedHashMap(); - String title = truncateTitle(event.getTitle()); + String title = truncateTitle(event.title()); String description = formatDescription(event); tags.put("og:title", title); tags.put("og:description", description); - tags.put("og:url", baseUrl + "/events/" + event.getEventToken().value()); + tags.put("og:url", baseUrl + "/events/" + event.eventToken().value()); tags.put("og:type", "website"); tags.put("og:site_name", GENERIC_TITLE); tags.put("og:image", baseUrl + "/og-image.png"); @@ -138,16 +138,16 @@ public class SpaController { } private String formatDescription(Event event) { - ZonedDateTime zoned = event.getDateTime().atZoneSameInstant(event.getTimezone()); + ZonedDateTime zoned = event.dateTime().atZoneSameInstant(event.timezone()); var sb = new StringBuilder(); sb.append("๐Ÿ“… ").append(zoned.format(DATE_FORMAT)); - if (event.getLocation() != null && !event.getLocation().isBlank()) { - sb.append(" ยท ๐Ÿ“ ").append(event.getLocation()); + if (event.location() != null && !event.location().isBlank()) { + sb.append(" ยท ๐Ÿ“ ").append(event.location()); } - if (event.getDescription() != null && !event.getDescription().isBlank()) { - sb.append(" โ€” ").append(event.getDescription()); + if (event.description() != null && !event.description().isBlank()) { + sb.append(" โ€” ").append(event.description()); } String result = sb.toString(); diff --git a/backend/src/main/java/de/fete/adapter/out/persistence/EventPersistenceAdapter.java b/backend/src/main/java/de/fete/adapter/out/persistence/EventPersistenceAdapter.java index 358f10f..02918e3 100644 --- a/backend/src/main/java/de/fete/adapter/out/persistence/EventPersistenceAdapter.java +++ b/backend/src/main/java/de/fete/adapter/out/persistence/EventPersistenceAdapter.java @@ -38,35 +38,34 @@ public class EventPersistenceAdapter implements EventRepository { private EventJpaEntity toEntity(Event event) { var entity = new EventJpaEntity(); - entity.setId(event.getId()); - entity.setEventToken(event.getEventToken().value()); - entity.setOrganizerToken(event.getOrganizerToken().value()); - entity.setTitle(event.getTitle()); - entity.setDescription(event.getDescription()); - entity.setDateTime(event.getDateTime()); - entity.setTimezone(event.getTimezone().getId()); - entity.setLocation(event.getLocation()); - entity.setExpiryDate(event.getExpiryDate()); - entity.setCreatedAt(event.getCreatedAt()); - entity.setCancelled(event.isCancelled()); - entity.setCancellationReason(event.getCancellationReason()); + entity.setId(event.id()); + entity.setEventToken(event.eventToken().value()); + entity.setOrganizerToken(event.organizerToken().value()); + entity.setTitle(event.title()); + entity.setDescription(event.description()); + entity.setDateTime(event.dateTime()); + entity.setTimezone(event.timezone().getId()); + entity.setLocation(event.location()); + entity.setExpiryDate(event.expiryDate()); + entity.setCreatedAt(event.createdAt()); + entity.setCancelled(event.cancelled()); + entity.setCancellationReason(event.cancellationReason()); return entity; } private Event toDomain(EventJpaEntity entity) { - var event = new Event(); - event.setId(entity.getId()); - event.setEventToken(new EventToken(entity.getEventToken())); - event.setOrganizerToken(new OrganizerToken(entity.getOrganizerToken())); - event.setTitle(entity.getTitle()); - event.setDescription(entity.getDescription()); - event.setDateTime(entity.getDateTime()); - event.setTimezone(ZoneId.of(entity.getTimezone())); - event.setLocation(entity.getLocation()); - event.setExpiryDate(entity.getExpiryDate()); - event.setCreatedAt(entity.getCreatedAt()); - event.setCancelled(entity.isCancelled()); - event.setCancellationReason(entity.getCancellationReason()); - return event; + return new Event( + entity.getId(), + new EventToken(entity.getEventToken()), + new OrganizerToken(entity.getOrganizerToken()), + entity.getTitle(), + entity.getDescription(), + entity.getDateTime(), + ZoneId.of(entity.getTimezone()), + entity.getLocation(), + entity.getExpiryDate(), + entity.getCreatedAt(), + entity.isCancelled(), + entity.getCancellationReason()); } } diff --git a/backend/src/main/java/de/fete/adapter/out/persistence/RsvpPersistenceAdapter.java b/backend/src/main/java/de/fete/adapter/out/persistence/RsvpPersistenceAdapter.java index 6cc3c9a..ec274f8 100644 --- a/backend/src/main/java/de/fete/adapter/out/persistence/RsvpPersistenceAdapter.java +++ b/backend/src/main/java/de/fete/adapter/out/persistence/RsvpPersistenceAdapter.java @@ -43,19 +43,18 @@ public class RsvpPersistenceAdapter implements RsvpRepository { private RsvpJpaEntity toEntity(Rsvp rsvp) { var entity = new RsvpJpaEntity(); - entity.setId(rsvp.getId()); - entity.setRsvpToken(rsvp.getRsvpToken().value()); - entity.setEventId(rsvp.getEventId()); - entity.setName(rsvp.getName()); + entity.setId(rsvp.id()); + entity.setRsvpToken(rsvp.rsvpToken().value()); + entity.setEventId(rsvp.eventId()); + entity.setName(rsvp.name()); return entity; } private Rsvp toDomain(RsvpJpaEntity entity) { - var rsvp = new Rsvp(); - rsvp.setId(entity.getId()); - rsvp.setRsvpToken(new RsvpToken(entity.getRsvpToken())); - rsvp.setEventId(entity.getEventId()); - rsvp.setName(entity.getName()); - return rsvp; + return new Rsvp( + entity.getId(), + new RsvpToken(entity.getRsvpToken()), + entity.getEventId(), + entity.getName()); } } diff --git a/backend/src/main/java/de/fete/application/service/EventService.java b/backend/src/main/java/de/fete/application/service/EventService.java index f8fbcec..8eae4dd 100644 --- a/backend/src/main/java/de/fete/application/service/EventService.java +++ b/backend/src/main/java/de/fete/application/service/EventService.java @@ -1,5 +1,8 @@ package de.fete.application.service; +import de.fete.application.service.exception.EventAlreadyCancelledException; +import de.fete.application.service.exception.EventNotFoundException; +import de.fete.application.service.exception.InvalidOrganizerTokenException; import de.fete.domain.model.CreateEventCommand; import de.fete.domain.model.Event; import de.fete.domain.model.EventToken; @@ -34,16 +37,19 @@ public class EventService implements CreateEventUseCase, GetEventUseCase, Update public Event createEvent(CreateEventCommand command) { LocalDate expiryDate = command.dateTime().toLocalDate().plusDays(EXPIRY_DAYS_AFTER_EVENT); - var event = new Event(); - event.setEventToken(EventToken.generate()); - event.setOrganizerToken(OrganizerToken.generate()); - event.setTitle(command.title()); - event.setDescription(command.description()); - event.setDateTime(command.dateTime()); - event.setTimezone(command.timezone()); - event.setLocation(command.location()); - event.setExpiryDate(expiryDate); - event.setCreatedAt(OffsetDateTime.now(clock)); + var event = new Event( + null, + EventToken.generate(), + OrganizerToken.generate(), + command.title(), + command.description(), + command.dateTime(), + command.timezone(), + command.location(), + expiryDate, + OffsetDateTime.now(clock), + false, + null); return eventRepository.save(event); } @@ -65,16 +71,14 @@ public class EventService implements CreateEventUseCase, GetEventUseCase, Update Event event = eventRepository.findByEventToken(eventToken) .orElseThrow(() -> new EventNotFoundException(eventToken.value())); - if (!event.getOrganizerToken().equals(organizerToken)) { + if (!event.organizerToken().equals(organizerToken)) { throw new InvalidOrganizerTokenException(); } - if (event.isCancelled()) { + if (event.cancelled()) { throw new EventAlreadyCancelledException(eventToken.value()); } - event.setCancelled(true); - event.setCancellationReason(reason); - eventRepository.save(event); + eventRepository.save(event.withCancellation(true, reason)); } } diff --git a/backend/src/main/java/de/fete/application/service/RsvpService.java b/backend/src/main/java/de/fete/application/service/RsvpService.java index 4e9e189..d649851 100644 --- a/backend/src/main/java/de/fete/application/service/RsvpService.java +++ b/backend/src/main/java/de/fete/application/service/RsvpService.java @@ -1,5 +1,9 @@ package de.fete.application.service; +import de.fete.application.service.exception.EventCancelledException; +import de.fete.application.service.exception.EventExpiredException; +import de.fete.application.service.exception.EventNotFoundException; +import de.fete.application.service.exception.InvalidOrganizerTokenException; import de.fete.domain.model.Event; import de.fete.domain.model.EventToken; import de.fete.domain.model.OrganizerToken; @@ -42,18 +46,15 @@ public class RsvpService Event event = eventRepository.findByEventToken(eventToken) .orElseThrow(() -> new EventNotFoundException(eventToken.value())); - if (event.isCancelled()) { + if (event.cancelled()) { throw new EventCancelledException(eventToken.value()); } - if (!event.getExpiryDate().isAfter(LocalDate.now(clock))) { + if (!event.expiryDate().isAfter(LocalDate.now(clock))) { throw new EventExpiredException(eventToken.value()); } - var rsvp = new Rsvp(); - rsvp.setRsvpToken(RsvpToken.generate()); - rsvp.setEventId(event.getId()); - rsvp.setName(name.strip()); + var rsvp = new Rsvp(null, RsvpToken.generate(), event.id(), name.strip()); return rsvpRepository.save(rsvp); } @@ -63,14 +64,14 @@ public class RsvpService public void cancelRsvp(EventToken eventToken, RsvpToken rsvpToken) { eventRepository.findByEventToken(eventToken) .ifPresent(event -> - rsvpRepository.deleteByEventIdAndRsvpToken(event.getId(), rsvpToken)); + rsvpRepository.deleteByEventIdAndRsvpToken(event.id(), rsvpToken)); } @Override public long countByEvent(EventToken eventToken) { Event event = eventRepository.findByEventToken(eventToken) .orElseThrow(() -> new EventNotFoundException(eventToken.value())); - return rsvpRepository.countByEventId(event.getId()); + return rsvpRepository.countByEventId(event.id()); } @Override @@ -78,12 +79,12 @@ public class RsvpService Event event = eventRepository.findByEventToken(eventToken) .orElseThrow(() -> new EventNotFoundException(eventToken.value())); - if (!event.getOrganizerToken().equals(organizerToken)) { + if (!event.organizerToken().equals(organizerToken)) { throw new InvalidOrganizerTokenException(); } - return rsvpRepository.findByEventId(event.getId()).stream() - .map(Rsvp::getName) + return rsvpRepository.findByEventId(event.id()).stream() + .map(Rsvp::name) .toList(); } } diff --git a/backend/src/main/java/de/fete/application/service/EventAlreadyCancelledException.java b/backend/src/main/java/de/fete/application/service/exception/EventAlreadyCancelledException.java similarity index 88% rename from backend/src/main/java/de/fete/application/service/EventAlreadyCancelledException.java rename to backend/src/main/java/de/fete/application/service/exception/EventAlreadyCancelledException.java index 72f1ec4..71d9395 100644 --- a/backend/src/main/java/de/fete/application/service/EventAlreadyCancelledException.java +++ b/backend/src/main/java/de/fete/application/service/exception/EventAlreadyCancelledException.java @@ -1,4 +1,4 @@ -package de.fete.application.service; +package de.fete.application.service.exception; import java.util.UUID; diff --git a/backend/src/main/java/de/fete/application/service/EventCancelledException.java b/backend/src/main/java/de/fete/application/service/exception/EventCancelledException.java similarity index 87% rename from backend/src/main/java/de/fete/application/service/EventCancelledException.java rename to backend/src/main/java/de/fete/application/service/exception/EventCancelledException.java index 14dbe68..0946d0a 100644 --- a/backend/src/main/java/de/fete/application/service/EventCancelledException.java +++ b/backend/src/main/java/de/fete/application/service/exception/EventCancelledException.java @@ -1,4 +1,4 @@ -package de.fete.application.service; +package de.fete.application.service.exception; import java.util.UUID; diff --git a/backend/src/main/java/de/fete/application/service/EventExpiredException.java b/backend/src/main/java/de/fete/application/service/exception/EventExpiredException.java similarity index 86% rename from backend/src/main/java/de/fete/application/service/EventExpiredException.java rename to backend/src/main/java/de/fete/application/service/exception/EventExpiredException.java index 374830d..5c3ef6b 100644 --- a/backend/src/main/java/de/fete/application/service/EventExpiredException.java +++ b/backend/src/main/java/de/fete/application/service/exception/EventExpiredException.java @@ -1,4 +1,4 @@ -package de.fete.application.service; +package de.fete.application.service.exception; import java.util.UUID; diff --git a/backend/src/main/java/de/fete/application/service/EventNotFoundException.java b/backend/src/main/java/de/fete/application/service/exception/EventNotFoundException.java similarity index 86% rename from backend/src/main/java/de/fete/application/service/EventNotFoundException.java rename to backend/src/main/java/de/fete/application/service/exception/EventNotFoundException.java index 6e3025f..b9b895b 100644 --- a/backend/src/main/java/de/fete/application/service/EventNotFoundException.java +++ b/backend/src/main/java/de/fete/application/service/exception/EventNotFoundException.java @@ -1,4 +1,4 @@ -package de.fete.application.service; +package de.fete.application.service.exception; import java.util.UUID; diff --git a/backend/src/main/java/de/fete/application/service/ExpiryDateBeforeEventException.java b/backend/src/main/java/de/fete/application/service/exception/ExpiryDateBeforeEventException.java similarity index 90% rename from backend/src/main/java/de/fete/application/service/ExpiryDateBeforeEventException.java rename to backend/src/main/java/de/fete/application/service/exception/ExpiryDateBeforeEventException.java index ccccef3..b4aba0a 100644 --- a/backend/src/main/java/de/fete/application/service/ExpiryDateBeforeEventException.java +++ b/backend/src/main/java/de/fete/application/service/exception/ExpiryDateBeforeEventException.java @@ -1,4 +1,4 @@ -package de.fete.application.service; +package de.fete.application.service.exception; import java.time.LocalDate; import java.time.OffsetDateTime; diff --git a/backend/src/main/java/de/fete/application/service/ExpiryDateInPastException.java b/backend/src/main/java/de/fete/application/service/exception/ExpiryDateInPastException.java similarity index 91% rename from backend/src/main/java/de/fete/application/service/ExpiryDateInPastException.java rename to backend/src/main/java/de/fete/application/service/exception/ExpiryDateInPastException.java index 77808d4..ba254ae 100644 --- a/backend/src/main/java/de/fete/application/service/ExpiryDateInPastException.java +++ b/backend/src/main/java/de/fete/application/service/exception/ExpiryDateInPastException.java @@ -1,4 +1,4 @@ -package de.fete.application.service; +package de.fete.application.service.exception; import java.time.LocalDate; diff --git a/backend/src/main/java/de/fete/application/service/InvalidOrganizerTokenException.java b/backend/src/main/java/de/fete/application/service/exception/InvalidOrganizerTokenException.java similarity index 85% rename from backend/src/main/java/de/fete/application/service/InvalidOrganizerTokenException.java rename to backend/src/main/java/de/fete/application/service/exception/InvalidOrganizerTokenException.java index 0576a81..b25d68f 100644 --- a/backend/src/main/java/de/fete/application/service/InvalidOrganizerTokenException.java +++ b/backend/src/main/java/de/fete/application/service/exception/InvalidOrganizerTokenException.java @@ -1,4 +1,4 @@ -package de.fete.application.service; +package de.fete.application.service.exception; /** Thrown when an invalid organizer token is provided. */ public class InvalidOrganizerTokenException extends RuntimeException { diff --git a/backend/src/main/java/de/fete/application/service/InvalidTimezoneException.java b/backend/src/main/java/de/fete/application/service/exception/InvalidTimezoneException.java similarity index 86% rename from backend/src/main/java/de/fete/application/service/InvalidTimezoneException.java rename to backend/src/main/java/de/fete/application/service/exception/InvalidTimezoneException.java index 4269804..67cdfc7 100644 --- a/backend/src/main/java/de/fete/application/service/InvalidTimezoneException.java +++ b/backend/src/main/java/de/fete/application/service/exception/InvalidTimezoneException.java @@ -1,4 +1,4 @@ -package de.fete.application.service; +package de.fete.application.service.exception; /** Thrown when an invalid IANA timezone ID is provided. */ public class InvalidTimezoneException extends RuntimeException { diff --git a/backend/src/main/java/de/fete/application/service/exception/package-info.java b/backend/src/main/java/de/fete/application/service/exception/package-info.java new file mode 100644 index 0000000..6585c76 --- /dev/null +++ b/backend/src/main/java/de/fete/application/service/exception/package-info.java @@ -0,0 +1,4 @@ +/** + * Application-layer exceptions thrown by service use case implementations. + */ +package de.fete.application.service.exception; diff --git a/backend/src/main/java/de/fete/domain/model/Event.java b/backend/src/main/java/de/fete/domain/model/Event.java index 3e84a92..8c9a539 100644 --- a/backend/src/main/java/de/fete/domain/model/Event.java +++ b/backend/src/main/java/de/fete/domain/model/Event.java @@ -5,138 +5,26 @@ import java.time.OffsetDateTime; import java.time.ZoneId; /** Domain entity representing an event. */ -public class Event { +public record Event( + Long id, + EventToken eventToken, + OrganizerToken organizerToken, + String title, + String description, + OffsetDateTime dateTime, + ZoneId timezone, + String location, + LocalDate expiryDate, + OffsetDateTime createdAt, + boolean cancelled, + String cancellationReason +) { - private Long id; - private EventToken eventToken; - private OrganizerToken organizerToken; - private String title; - private String description; - private OffsetDateTime dateTime; - private ZoneId timezone; - private String location; - private LocalDate expiryDate; - private OffsetDateTime createdAt; - private boolean cancelled; - private String cancellationReason; - - /** Returns the internal database ID. */ - public Long getId() { - return id; - } - - /** Sets the internal database ID. */ - public void setId(Long id) { - this.id = id; - } - - /** Returns the public event token. */ - public EventToken getEventToken() { - return eventToken; - } - - /** Sets the public event token. */ - public void setEventToken(EventToken eventToken) { - this.eventToken = eventToken; - } - - /** Returns the secret organizer token. */ - public OrganizerToken getOrganizerToken() { - return organizerToken; - } - - /** Sets the secret organizer token. */ - public void setOrganizerToken(OrganizerToken organizerToken) { - this.organizerToken = organizerToken; - } - - /** Returns the event title. */ - public String getTitle() { - return title; - } - - /** Sets the event title. */ - public void setTitle(String title) { - this.title = title; - } - - /** Returns the event description. */ - public String getDescription() { - return description; - } - - /** Sets the event description. */ - public void setDescription(String description) { - this.description = description; - } - - /** Returns the event date and time with UTC offset. */ - public OffsetDateTime getDateTime() { - return dateTime; - } - - /** Sets the event date and time. */ - public void setDateTime(OffsetDateTime 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. */ - public String getLocation() { - return location; - } - - /** Sets the event location. */ - public void setLocation(String location) { - this.location = location; - } - - /** Returns the expiry date after which event data is deleted. */ - public LocalDate getExpiryDate() { - return expiryDate; - } - - /** Sets the expiry date. */ - public void setExpiryDate(LocalDate expiryDate) { - this.expiryDate = expiryDate; - } - - /** Returns the creation timestamp. */ - public OffsetDateTime getCreatedAt() { - return createdAt; - } - - /** Sets the creation timestamp. */ - public void setCreatedAt(OffsetDateTime createdAt) { - this.createdAt = createdAt; - } - - /** Returns whether the event has been cancelled. */ - public boolean isCancelled() { - return cancelled; - } - - /** Sets the cancelled flag. */ - public void setCancelled(boolean cancelled) { - this.cancelled = cancelled; - } - - /** Returns the cancellation reason, if any. */ - public String getCancellationReason() { - return cancellationReason; - } - - /** Sets the cancellation reason. */ - public void setCancellationReason(String cancellationReason) { - this.cancellationReason = cancellationReason; + /** Returns a copy of this event with cancellation applied. */ + public Event withCancellation(boolean cancelled, String cancellationReason) { + return new Event( + id, eventToken, organizerToken, title, description, + dateTime, timezone, location, expiryDate, createdAt, + cancelled, cancellationReason); } } diff --git a/backend/src/main/java/de/fete/domain/model/Rsvp.java b/backend/src/main/java/de/fete/domain/model/Rsvp.java index 53285db..535af47 100644 --- a/backend/src/main/java/de/fete/domain/model/Rsvp.java +++ b/backend/src/main/java/de/fete/domain/model/Rsvp.java @@ -1,50 +1,9 @@ package de.fete.domain.model; /** Domain entity representing an RSVP. */ -public class Rsvp { - - private Long id; - private RsvpToken rsvpToken; - private Long eventId; - private String name; - - /** Returns the internal database ID. */ - public Long getId() { - return id; - } - - /** Sets the internal database ID. */ - public void setId(Long id) { - this.id = id; - } - - /** Returns the RSVP token. */ - public RsvpToken getRsvpToken() { - return rsvpToken; - } - - /** Sets the RSVP token. */ - public void setRsvpToken(RsvpToken rsvpToken) { - this.rsvpToken = rsvpToken; - } - - /** Returns the event ID this RSVP belongs to. */ - public Long getEventId() { - return eventId; - } - - /** Sets the event ID. */ - public void setEventId(Long eventId) { - this.eventId = eventId; - } - - /** Returns the guest's display name. */ - public String getName() { - return name; - } - - /** Sets the guest's display name. */ - public void setName(String name) { - this.name = name; - } -} +public record Rsvp( + Long id, + RsvpToken rsvpToken, + Long eventId, + String name +) {} diff --git a/backend/src/test/java/de/fete/HexagonalArchitectureTest.java b/backend/src/test/java/de/fete/HexagonalArchitectureTest.java index 90e2124..8620fe4 100644 --- a/backend/src/test/java/de/fete/HexagonalArchitectureTest.java +++ b/backend/src/test/java/de/fete/HexagonalArchitectureTest.java @@ -4,10 +4,14 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import static com.tngtech.archunit.library.Architectures.onionArchitecture; +import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; @AnalyzeClasses(packages = "de.fete", importOptions = ImportOption.DoNotIncludeTests.class) class HexagonalArchitectureTest { @@ -65,4 +69,24 @@ class HexagonalArchitectureTest { static final ArchRule webAdapterMustNotDependOnOutboundPorts = noClasses() .that().resideInAPackage("de.fete.adapter.in.web..") .should().dependOnClassesThat().resideInAPackage("de.fete.domain.port.out.."); + + @ArchTest + static final ArchRule domainModelsMustBeRecords = classes() + .that().resideInAPackage("de.fete.domain.model..") + .and().doNotHaveSimpleName("package-info") + .should(beRecords()); + + private static ArchCondition beRecords() { + return new ArchCondition<>("be records") { + @Override + public void check(JavaClass javaClass, + ConditionEvents events) { + boolean isRecord = javaClass.reflect().isRecord(); + if (!isRecord) { + events.add(SimpleConditionEvent.violated(javaClass, + javaClass.getFullName() + " is not a record")); + } + } + }; + } } diff --git a/backend/src/test/java/de/fete/adapter/out/persistence/EventPersistenceAdapterIntegrationTest.java b/backend/src/test/java/de/fete/adapter/out/persistence/EventPersistenceAdapterIntegrationTest.java index f926949..2a0b3b6 100644 --- a/backend/src/test/java/de/fete/adapter/out/persistence/EventPersistenceAdapterIntegrationTest.java +++ b/backend/src/test/java/de/fete/adapter/out/persistence/EventPersistenceAdapterIntegrationTest.java @@ -42,7 +42,7 @@ class EventPersistenceAdapterIntegrationTest { eventRepository.deleteExpired(); - assertThat(eventRepository.findByEventToken(saved.getEventToken())).isPresent(); + assertThat(eventRepository.findByEventToken(saved.eventToken())).isPresent(); } @Test @@ -52,7 +52,7 @@ class EventPersistenceAdapterIntegrationTest { eventRepository.deleteExpired(); - assertThat(eventRepository.findByEventToken(saved.getEventToken())).isPresent(); + assertThat(eventRepository.findByEventToken(saved.eventToken())).isPresent(); } @Test @@ -66,16 +66,18 @@ class EventPersistenceAdapterIntegrationTest { } private Event buildEvent(String title, LocalDate expiryDate) { - var event = new Event(); - event.setEventToken(EventToken.generate()); - event.setOrganizerToken(OrganizerToken.generate()); - event.setTitle(title); - event.setDescription("Test description"); - event.setDateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2))); - event.setTimezone(ZoneId.of("Europe/Berlin")); - event.setLocation("Test Location"); - event.setExpiryDate(expiryDate); - event.setCreatedAt(OffsetDateTime.now()); - return event; + return new Event( + null, + EventToken.generate(), + OrganizerToken.generate(), + title, + "Test description", + OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)), + ZoneId.of("Europe/Berlin"), + "Test Location", + expiryDate, + OffsetDateTime.now(), + false, + null); } } diff --git a/backend/src/test/java/de/fete/adapter/out/persistence/EventPersistenceAdapterTest.java b/backend/src/test/java/de/fete/adapter/out/persistence/EventPersistenceAdapterTest.java index d12c789..455d276 100644 --- a/backend/src/test/java/de/fete/adapter/out/persistence/EventPersistenceAdapterTest.java +++ b/backend/src/test/java/de/fete/adapter/out/persistence/EventPersistenceAdapterTest.java @@ -30,8 +30,8 @@ class EventPersistenceAdapterTest { Event saved = eventRepository.save(event); - assertThat(saved.getId()).isNotNull(); - assertThat(saved.getTitle()).isEqualTo("Test Event"); + assertThat(saved.id()).isNotNull(); + assertThat(saved.title()).isEqualTo("Test Event"); } @Test @@ -39,11 +39,11 @@ class EventPersistenceAdapterTest { Event event = buildEvent(); Event saved = eventRepository.save(event); - Optional found = eventRepository.findByEventToken(saved.getEventToken()); + Optional found = eventRepository.findByEventToken(saved.eventToken()); assertThat(found).isPresent(); - assertThat(found.get().getTitle()).isEqualTo("Test Event"); - assertThat(found.get().getId()).isEqualTo(saved.getId()); + assertThat(found.get().title()).isEqualTo("Test Event"); + assertThat(found.get().id()).isEqualTo(saved.id()); } @Test @@ -61,42 +61,47 @@ class EventPersistenceAdapterTest { OffsetDateTime createdAt = OffsetDateTime.of(2026, 3, 4, 12, 0, 0, 0, ZoneOffset.UTC); - var event = new Event(); - event.setEventToken(EventToken.generate()); - event.setOrganizerToken(OrganizerToken.generate()); - event.setTitle("Full Event"); - event.setDescription("A detailed description"); - event.setDateTime(dateTime); - event.setTimezone(ZoneId.of("Europe/Berlin")); - event.setLocation("Berlin, Germany"); - event.setExpiryDate(expiryDate); - event.setCreatedAt(createdAt); + var event = new Event( + null, + EventToken.generate(), + OrganizerToken.generate(), + "Full Event", + "A detailed description", + dateTime, + ZoneId.of("Europe/Berlin"), + "Berlin, Germany", + expiryDate, + createdAt, + false, + null); Event saved = eventRepository.save(event); - Event found = eventRepository.findByEventToken(saved.getEventToken()).orElseThrow(); + Event found = eventRepository.findByEventToken(saved.eventToken()).orElseThrow(); - assertThat(found.getEventToken()).isEqualTo(event.getEventToken()); - assertThat(found.getOrganizerToken()).isEqualTo(event.getOrganizerToken()); - assertThat(found.getTitle()).isEqualTo("Full Event"); - assertThat(found.getDescription()).isEqualTo("A detailed description"); - assertThat(found.getDateTime().toInstant()).isEqualTo(dateTime.toInstant()); - assertThat(found.getTimezone()).isEqualTo(ZoneId.of("Europe/Berlin")); - assertThat(found.getLocation()).isEqualTo("Berlin, Germany"); - assertThat(found.getExpiryDate()).isEqualTo(expiryDate); - assertThat(found.getCreatedAt().toInstant()).isEqualTo(createdAt.toInstant()); + assertThat(found.eventToken()).isEqualTo(event.eventToken()); + assertThat(found.organizerToken()).isEqualTo(event.organizerToken()); + assertThat(found.title()).isEqualTo("Full Event"); + assertThat(found.description()).isEqualTo("A detailed description"); + assertThat(found.dateTime().toInstant()).isEqualTo(dateTime.toInstant()); + assertThat(found.timezone()).isEqualTo(ZoneId.of("Europe/Berlin")); + assertThat(found.location()).isEqualTo("Berlin, Germany"); + assertThat(found.expiryDate()).isEqualTo(expiryDate); + assertThat(found.createdAt().toInstant()).isEqualTo(createdAt.toInstant()); } private Event buildEvent() { - var event = new Event(); - event.setEventToken(EventToken.generate()); - event.setOrganizerToken(OrganizerToken.generate()); - event.setTitle("Test Event"); - event.setDescription("Test description"); - event.setDateTime(OffsetDateTime.now().plusDays(7)); - event.setTimezone(ZoneId.of("Europe/Berlin")); - event.setLocation("Somewhere"); - event.setExpiryDate(LocalDate.now().plusDays(30)); - event.setCreatedAt(OffsetDateTime.now()); - return event; + return new Event( + null, + EventToken.generate(), + OrganizerToken.generate(), + "Test Event", + "Test description", + OffsetDateTime.now().plusDays(7), + ZoneId.of("Europe/Berlin"), + "Somewhere", + LocalDate.now().plusDays(30), + OffsetDateTime.now(), + false, + null); } } diff --git a/backend/src/test/java/de/fete/application/service/EventServiceCancelTest.java b/backend/src/test/java/de/fete/application/service/EventServiceCancelTest.java index 6fa6c43..e76000d 100644 --- a/backend/src/test/java/de/fete/application/service/EventServiceCancelTest.java +++ b/backend/src/test/java/de/fete/application/service/EventServiceCancelTest.java @@ -7,7 +7,9 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import de.fete.application.service.EventAlreadyCancelledException; +import de.fete.application.service.exception.EventAlreadyCancelledException; +import de.fete.application.service.exception.EventNotFoundException; +import de.fete.application.service.exception.InvalidOrganizerTokenException; import de.fete.domain.model.Event; import de.fete.domain.model.EventToken; import de.fete.domain.model.OrganizerToken; @@ -46,10 +48,8 @@ class EventServiceCancelTest { void cancelEventDelegatesToDomainAndSaves() { EventToken eventToken = EventToken.generate(); OrganizerToken organizerToken = OrganizerToken.generate(); - var event = new Event(); - event.setEventToken(eventToken); - event.setOrganizerToken(organizerToken); - event.setCancelled(false); + var event = new Event(null, eventToken, organizerToken, null, null, null, null, null, null, + null, false, null); when(eventRepository.findByEventToken(eventToken)) .thenReturn(Optional.of(event)); @@ -60,18 +60,16 @@ class EventServiceCancelTest { ArgumentCaptor captor = ArgumentCaptor.forClass(Event.class); verify(eventRepository).save(captor.capture()); - assertThat(captor.getValue().isCancelled()).isTrue(); - assertThat(captor.getValue().getCancellationReason()).isEqualTo("Venue unavailable"); + assertThat(captor.getValue().cancelled()).isTrue(); + assertThat(captor.getValue().cancellationReason()).isEqualTo("Venue unavailable"); } @Test void cancelEventWithNullReason() { EventToken eventToken = EventToken.generate(); OrganizerToken organizerToken = OrganizerToken.generate(); - var event = new Event(); - event.setEventToken(eventToken); - event.setOrganizerToken(organizerToken); - event.setCancelled(false); + var event = new Event(null, eventToken, organizerToken, null, null, null, null, null, null, + null, false, null); when(eventRepository.findByEventToken(eventToken)) .thenReturn(Optional.of(event)); @@ -82,8 +80,8 @@ class EventServiceCancelTest { ArgumentCaptor captor = ArgumentCaptor.forClass(Event.class); verify(eventRepository).save(captor.capture()); - assertThat(captor.getValue().isCancelled()).isTrue(); - assertThat(captor.getValue().getCancellationReason()).isNull(); + assertThat(captor.getValue().cancelled()).isTrue(); + assertThat(captor.getValue().cancellationReason()).isNull(); } @Test @@ -104,9 +102,8 @@ class EventServiceCancelTest { void cancelEventThrows403WhenWrongOrganizerToken() { EventToken eventToken = EventToken.generate(); OrganizerToken correctToken = OrganizerToken.generate(); - var event = new Event(); - event.setEventToken(eventToken); - event.setOrganizerToken(correctToken); + var event = new Event(null, eventToken, correctToken, null, null, null, null, null, null, + null, false, null); when(eventRepository.findByEventToken(eventToken)) .thenReturn(Optional.of(event)); @@ -122,10 +119,8 @@ class EventServiceCancelTest { void cancelEventThrows409WhenAlreadyCancelled() { EventToken eventToken = EventToken.generate(); OrganizerToken organizerToken = OrganizerToken.generate(); - var event = new Event(); - event.setEventToken(eventToken); - event.setOrganizerToken(organizerToken); - event.setCancelled(true); + var event = new Event(null, eventToken, organizerToken, null, null, null, null, null, null, + null, true, null); when(eventRepository.findByEventToken(eventToken)) .thenReturn(Optional.of(event)); diff --git a/backend/src/test/java/de/fete/application/service/EventServiceTest.java b/backend/src/test/java/de/fete/application/service/EventServiceTest.java index 0225866..48d924c 100644 --- a/backend/src/test/java/de/fete/application/service/EventServiceTest.java +++ b/backend/src/test/java/de/fete/application/service/EventServiceTest.java @@ -57,13 +57,13 @@ class EventServiceTest { 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)); + assertThat(result.title()).isEqualTo("Birthday Party"); + assertThat(result.description()).isEqualTo("Come celebrate!"); + assertThat(result.timezone()).isEqualTo(ZONE); + assertThat(result.location()).isEqualTo("Berlin"); + assertThat(result.eventToken()).isNotNull(); + assertThat(result.organizerToken()).isNotNull(); + assertThat(result.createdAt()).isEqualTo(OffsetDateTime.ofInstant(FIXED_INSTANT, ZONE)); } @Test @@ -80,7 +80,7 @@ class EventServiceTest { ArgumentCaptor captor = ArgumentCaptor.forClass(Event.class); verify(eventRepository, times(1)).save(captor.capture()); - assertThat(captor.getValue().getTitle()).isEqualTo("Test"); + assertThat(captor.getValue().title()).isEqualTo("Test"); } @Test @@ -96,7 +96,7 @@ class EventServiceTest { Event result = eventService.createEvent(command); - assertThat(result.getExpiryDate()).isEqualTo(eventDate.plusDays(7)); + assertThat(result.expiryDate()).isEqualTo(eventDate.plusDays(7)); } // --- GetEventUseCase tests (T004) --- @@ -104,16 +104,15 @@ class EventServiceTest { @Test void getByEventTokenReturnsEvent() { EventToken token = EventToken.generate(); - var event = new Event(); - event.setEventToken(token); - event.setTitle("Found Event"); + var event = new Event(null, token, null, "Found Event", null, null, null, null, null, null, + false, null); when(eventRepository.findByEventToken(token)) .thenReturn(Optional.of(event)); Optional result = eventService.getByEventToken(token); assertThat(result).isPresent(); - assertThat(result.get().getTitle()).isEqualTo("Found Event"); + assertThat(result.get().title()).isEqualTo("Found Event"); } @Test @@ -142,6 +141,6 @@ class EventServiceTest { Event result = eventService.createEvent(command); - assertThat(result.getTimezone()).isEqualTo(ZoneId.of("America/New_York")); + assertThat(result.timezone()).isEqualTo(ZoneId.of("America/New_York")); } } diff --git a/backend/src/test/java/de/fete/application/service/RsvpServiceTest.java b/backend/src/test/java/de/fete/application/service/RsvpServiceTest.java index ee60a0c..4d21a40 100644 --- a/backend/src/test/java/de/fete/application/service/RsvpServiceTest.java +++ b/backend/src/test/java/de/fete/application/service/RsvpServiceTest.java @@ -6,6 +6,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import de.fete.application.service.exception.EventCancelledException; +import de.fete.application.service.exception.EventExpiredException; +import de.fete.application.service.exception.EventNotFoundException; +import de.fete.application.service.exception.InvalidOrganizerTokenException; import de.fete.domain.model.Event; import de.fete.domain.model.EventToken; import de.fete.domain.model.OrganizerToken; @@ -51,23 +55,23 @@ class RsvpServiceTest { @Test void createRsvpSucceedsForActiveEvent() { - Event event = buildActiveEvent(); - EventToken token = event.getEventToken(); + Event event = buildActiveEvent(TODAY.plusDays(30)); + EventToken token = event.eventToken(); when(eventRepository.findByEventToken(token)).thenReturn(Optional.of(event)); when(rsvpRepository.save(any(Rsvp.class))) .thenAnswer(invocation -> invocation.getArgument(0)); Rsvp result = rsvpService.createRsvp(token, "Max Mustermann"); - assertThat(result.getName()).isEqualTo("Max Mustermann"); - assertThat(result.getRsvpToken()).isNotNull(); - assertThat(result.getEventId()).isEqualTo(event.getId()); + assertThat(result.name()).isEqualTo("Max Mustermann"); + assertThat(result.rsvpToken()).isNotNull(); + assertThat(result.eventId()).isEqualTo(event.id()); } @Test void createRsvpPersistsViaRepository() { - Event event = buildActiveEvent(); - EventToken token = event.getEventToken(); + Event event = buildActiveEvent(TODAY.plusDays(30)); + EventToken token = event.eventToken(); when(eventRepository.findByEventToken(token)).thenReturn(Optional.of(event)); when(rsvpRepository.save(any(Rsvp.class))) .thenAnswer(invocation -> invocation.getArgument(0)); @@ -76,8 +80,8 @@ class RsvpServiceTest { ArgumentCaptor captor = ArgumentCaptor.forClass(Rsvp.class); verify(rsvpRepository).save(captor.capture()); - assertThat(captor.getValue().getName()).isEqualTo("Test Guest"); - assertThat(captor.getValue().getEventId()).isEqualTo(event.getId()); + assertThat(captor.getValue().name()).isEqualTo("Test Guest"); + assertThat(captor.getValue().eventId()).isEqualTo(event.id()); } @Test @@ -91,22 +95,21 @@ class RsvpServiceTest { @Test void createRsvpTrimsName() { - Event event = buildActiveEvent(); - EventToken token = event.getEventToken(); + Event event = buildActiveEvent(TODAY.plusDays(30)); + EventToken token = event.eventToken(); when(eventRepository.findByEventToken(token)).thenReturn(Optional.of(event)); when(rsvpRepository.save(any(Rsvp.class))) .thenAnswer(invocation -> invocation.getArgument(0)); Rsvp result = rsvpService.createRsvp(token, " Max "); - assertThat(result.getName()).isEqualTo("Max"); + assertThat(result.name()).isEqualTo("Max"); } @Test void createRsvpThrowsWhenEventExpired() { - var event = buildActiveEvent(); - event.setExpiryDate(TODAY.minusDays(1)); - EventToken token = event.getEventToken(); + Event event = buildActiveEvent(TODAY.minusDays(1)); + EventToken token = event.eventToken(); when(eventRepository.findByEventToken(token)).thenReturn(Optional.of(event)); assertThatThrownBy(() -> rsvpService.createRsvp(token, "Late Guest")) @@ -115,9 +118,8 @@ class RsvpServiceTest { @Test void createRsvpThrowsWhenEventExpiresToday() { - var event = buildActiveEvent(); - event.setExpiryDate(TODAY); - EventToken token = event.getEventToken(); + Event event = buildActiveEvent(TODAY); + EventToken token = event.eventToken(); when(eventRepository.findByEventToken(token)).thenReturn(Optional.of(event)); assertThatThrownBy(() -> rsvpService.createRsvp(token, "Late Guest")) @@ -126,12 +128,12 @@ class RsvpServiceTest { @Test void getAttendeeNamesReturnsNamesInOrder() { - Event event = buildActiveEvent(); - EventToken token = event.getEventToken(); - OrganizerToken orgToken = event.getOrganizerToken(); + Event event = buildActiveEvent(TODAY.plusDays(30)); + EventToken token = event.eventToken(); + OrganizerToken orgToken = event.organizerToken(); when(eventRepository.findByEventToken(token)) .thenReturn(Optional.of(event)); - when(rsvpRepository.findByEventId(event.getId())) + when(rsvpRepository.findByEventId(event.id())) .thenReturn(List.of( buildRsvp(1L, "Alice"), buildRsvp(2L, "Bob"), @@ -144,12 +146,12 @@ class RsvpServiceTest { @Test void getAttendeeNamesReturnsEmptyListWhenNoRsvps() { - Event event = buildActiveEvent(); - EventToken token = event.getEventToken(); - OrganizerToken orgToken = event.getOrganizerToken(); + Event event = buildActiveEvent(TODAY.plusDays(30)); + EventToken token = event.eventToken(); + OrganizerToken orgToken = event.organizerToken(); when(eventRepository.findByEventToken(token)) .thenReturn(Optional.of(event)); - when(rsvpRepository.findByEventId(event.getId())) + when(rsvpRepository.findByEventId(event.id())) .thenReturn(List.of()); List names = rsvpService.getAttendeeNames(token, orgToken); @@ -171,8 +173,8 @@ class RsvpServiceTest { @Test void getAttendeeNamesThrowsWhenOrganizerTokenInvalid() { - Event event = buildActiveEvent(); - EventToken token = event.getEventToken(); + Event event = buildActiveEvent(TODAY.plusDays(30)); + EventToken token = event.eventToken(); OrganizerToken wrongToken = OrganizerToken.generate(); when(eventRepository.findByEventToken(token)) .thenReturn(Optional.of(event)); @@ -183,38 +185,33 @@ class RsvpServiceTest { } private Rsvp buildRsvp(Long id, String name) { - var rsvp = new Rsvp(); - rsvp.setId(id); - rsvp.setRsvpToken(RsvpToken.generate()); - rsvp.setEventId(1L); - rsvp.setName(name); - return rsvp; + return new Rsvp(id, RsvpToken.generate(), 1L, name); } @Test void cancelRsvpDeletesWhenEventAndRsvpExist() { - Event event = buildActiveEvent(); - EventToken token = event.getEventToken(); + Event event = buildActiveEvent(TODAY.plusDays(30)); + EventToken token = event.eventToken(); RsvpToken rsvpToken = RsvpToken.generate(); when(eventRepository.findByEventToken(token)).thenReturn(Optional.of(event)); - when(rsvpRepository.deleteByEventIdAndRsvpToken(event.getId(), rsvpToken)).thenReturn(true); + when(rsvpRepository.deleteByEventIdAndRsvpToken(event.id(), rsvpToken)).thenReturn(true); rsvpService.cancelRsvp(token, rsvpToken); - verify(rsvpRepository).deleteByEventIdAndRsvpToken(event.getId(), rsvpToken); + verify(rsvpRepository).deleteByEventIdAndRsvpToken(event.id(), rsvpToken); } @Test void cancelRsvpSucceedsWhenRsvpNotFound() { - Event event = buildActiveEvent(); - EventToken token = event.getEventToken(); + Event event = buildActiveEvent(TODAY.plusDays(30)); + EventToken token = event.eventToken(); RsvpToken rsvpToken = RsvpToken.generate(); when(eventRepository.findByEventToken(token)).thenReturn(Optional.of(event)); - when(rsvpRepository.deleteByEventIdAndRsvpToken(event.getId(), rsvpToken)).thenReturn(false); + when(rsvpRepository.deleteByEventIdAndRsvpToken(event.id(), rsvpToken)).thenReturn(false); rsvpService.cancelRsvp(token, rsvpToken); - verify(rsvpRepository).deleteByEventIdAndRsvpToken(event.getId(), rsvpToken); + verify(rsvpRepository).deleteByEventIdAndRsvpToken(event.id(), rsvpToken); } @Test @@ -226,16 +223,19 @@ class RsvpServiceTest { rsvpService.cancelRsvp(token, rsvpToken); } - private Event buildActiveEvent() { - var event = new Event(); - event.setId(1L); - event.setEventToken(EventToken.generate()); - event.setOrganizerToken(OrganizerToken.generate()); - event.setTitle("Test Event"); - event.setDateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2))); - event.setTimezone(ZONE); - event.setExpiryDate(TODAY.plusDays(30)); - event.setCreatedAt(OffsetDateTime.now()); - return event; + private Event buildActiveEvent(LocalDate expiryDate) { + return new Event( + 1L, + EventToken.generate(), + OrganizerToken.generate(), + "Test Event", + null, + OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)), + ZONE, + null, + expiryDate, + OffsetDateTime.now(), + false, + null); } }