Implement cancel-event feature (016) #38
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -86,11 +86,11 @@ public class SpaController {
|
||||
|
||||
private Map<String, String> buildEventMeta(Event event, String baseUrl) {
|
||||
var tags = new LinkedHashMap<String, String>();
|
||||
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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.fete.application.service;
|
||||
package de.fete.application.service.exception;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.fete.application.service;
|
||||
package de.fete.application.service.exception;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.fete.application.service;
|
||||
package de.fete.application.service.exception;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.fete.application.service;
|
||||
package de.fete.application.service.exception;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.fete.application.service;
|
||||
package de.fete.application.service.exception;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.fete.application.service;
|
||||
package de.fete.application.service.exception;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@@ -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 {
|
||||
@@ -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 {
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Application-layer exceptions thrown by service use case implementations.
|
||||
*/
|
||||
package de.fete.application.service.exception;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
) {}
|
||||
|
||||
@@ -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<JavaClass> 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"));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Event> found = eventRepository.findByEventToken(saved.getEventToken());
|
||||
Optional<Event> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Event> 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<Event> 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));
|
||||
|
||||
@@ -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<Event> 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<Event> 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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Rsvp> 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user