diff --git a/src/main/kotlin/io/github/tyostokarry/eventshuffle/controller/EventController.kt b/src/main/kotlin/io/github/tyostokarry/eventshuffle/controller/EventController.kt index 76b3dea..6bfa8ff 100644 --- a/src/main/kotlin/io/github/tyostokarry/eventshuffle/controller/EventController.kt +++ b/src/main/kotlin/io/github/tyostokarry/eventshuffle/controller/EventController.kt @@ -5,6 +5,7 @@ import io.github.tyostokarry.eventshuffle.dto.EventCreateResponse import io.github.tyostokarry.eventshuffle.dto.EventDetailsResponse import io.github.tyostokarry.eventshuffle.dto.EventListItemDto import io.github.tyostokarry.eventshuffle.dto.EventListResponse +import io.github.tyostokarry.eventshuffle.dto.EventResultResponse import io.github.tyostokarry.eventshuffle.dto.VoteCreateRequest import io.github.tyostokarry.eventshuffle.entity.Event import io.github.tyostokarry.eventshuffle.service.EventService @@ -57,4 +58,13 @@ class EventController( val response = EventDetailsResponse.fromEvent(event) return ResponseEntity.ok(response) } + + @GetMapping("/{id}/results") + fun getEventResults( + @PathVariable id: Long, + ): ResponseEntity { + val event = eventService.getEventById(id) + val response = EventResultResponse.fromEvent(event) + return ResponseEntity.ok(response) + } } diff --git a/src/main/kotlin/io/github/tyostokarry/eventshuffle/dto/EventResultResponse.kt b/src/main/kotlin/io/github/tyostokarry/eventshuffle/dto/EventResultResponse.kt new file mode 100644 index 0000000..b90835a --- /dev/null +++ b/src/main/kotlin/io/github/tyostokarry/eventshuffle/dto/EventResultResponse.kt @@ -0,0 +1,42 @@ +package io.github.tyostokarry.eventshuffle.dto + +import io.github.tyostokarry.eventshuffle.entity.Event + +data class EventResultResponse( + val id: Long, + val name: String, + val suitableDates: List, +) { + companion object { + fun fromEvent(event: Event): EventResultResponse { + if (event.votes.isEmpty()) { + return EventResultResponse( + id = event.id, + name = event.name, + suitableDates = emptyList(), + ) + } + + val allVoters = event.votes.map { it.voterName }.toSet() + + val suitableDates = + event.dates + .filter { date -> + event.votes.all { + date in it.votedDates + } + }.map { date -> + VotesListItemDto( + date = date, + people = allVoters.toList(), + ) + } + + return EventResultResponse( + id = event.id, + name = event.name, + suitableDates = suitableDates, + ) + } + } +} diff --git a/src/main/kotlin/io/github/tyostokarry/eventshuffle/service/EventService.kt b/src/main/kotlin/io/github/tyostokarry/eventshuffle/service/EventService.kt index 1d7326f..94d3a7d 100644 --- a/src/main/kotlin/io/github/tyostokarry/eventshuffle/service/EventService.kt +++ b/src/main/kotlin/io/github/tyostokarry/eventshuffle/service/EventService.kt @@ -40,13 +40,15 @@ class EventService( // If participant has voted previously, remove old vote event.votes.removeIf { it.voterName.equals(request.name, ignoreCase = true) } - val newVote = - EventVote( - voterName = request.name, - votedDates = request.votes, - event = event, - ) - event.votes.add(newVote) + if (request.votes.isNotEmpty()) { + val newVote = + EventVote( + voterName = request.name, + votedDates = request.votes, + event = event, + ) + event.votes.add(newVote) + } val saved = eventRepository.save(event) return saved diff --git a/src/test/kotlin/io/github/tyostokarry/eventshuffle/controller/EventControllerTest.kt b/src/test/kotlin/io/github/tyostokarry/eventshuffle/controller/EventControllerTest.kt index 5ddc077..08708d3 100644 --- a/src/test/kotlin/io/github/tyostokarry/eventshuffle/controller/EventControllerTest.kt +++ b/src/test/kotlin/io/github/tyostokarry/eventshuffle/controller/EventControllerTest.kt @@ -5,6 +5,7 @@ import io.github.tyostokarry.eventshuffle.dto.EventCreateRequest import io.github.tyostokarry.eventshuffle.dto.EventCreateResponse import io.github.tyostokarry.eventshuffle.dto.EventDetailsResponse import io.github.tyostokarry.eventshuffle.dto.EventListResponse +import io.github.tyostokarry.eventshuffle.dto.EventResultResponse import io.github.tyostokarry.eventshuffle.dto.VoteCreateRequest import io.github.tyostokarry.eventshuffle.entity.Event import io.github.tyostokarry.eventshuffle.entity.EventVote @@ -389,4 +390,164 @@ class EventControllerTest( ) } } + + @Nested + inner class GetResult { + @Test + fun `get event results returns aggregated vote summary`() { + val event = + Event( + id = 1L, + name = "Test Event", + dates = + listOf( + LocalDate.of(2025, 10, 5), + LocalDate.of(2025, 10, 6), + ), + ) + + event.votes.addAll( + listOf( + EventVote( + id = 1L, + voterName = "Test Name 1", + votedDates = listOf(LocalDate.of(2025, 10, 5)), + event = event, + ), + EventVote( + id = 2L, + voterName = "Test Name 2", + votedDates = + listOf( + LocalDate.of(2025, 10, 5), + LocalDate.of(2025, 10, 6), + ), + event = event, + ), + ), + ) + + given(eventService.getEventById(1L)).willReturn(event) + + val responseBody = + mockMvc + .get("/api/v1/event/1/results") + .andExpect { status { isOk() } } + .andReturn() + .response + .contentAsString + + val response = objectMapper.readValue(responseBody, EventResultResponse::class.java) + + assertEquals(1, response.id, "Response event id should be 1") + assertEquals("Test Event", response.name, "Response event name should be 'Test Event'") + assertEquals(1, response.suitableDates.size, "Response event should have exactly 1 suitable date") + assertEquals( + LocalDate.of(2025, 10, 5), + response.suitableDates.first().date, + "Response events suitable date should be '2025-10-05'", + ) + assertEquals( + listOf("Test Name 1", "Test Name 2"), + response.suitableDates.first().people, + "Response events suitable date voters should be 'Test Name 1', 'Test Name 2'", + ) + } + + @Test + fun `get event results returns empty suitable dates list when no votes are recorded`() { + val event = + Event( + id = 1L, + name = "Test Event", + dates = + listOf( + LocalDate.of(2025, 10, 5), + LocalDate.of(2025, 10, 6), + ), + ) + + given(eventService.getEventById(1L)).willReturn(event) + + val responseBody = + mockMvc + .get("/api/v1/event/1/results") + .andExpect { status { isOk() } } + .andReturn() + .response + .contentAsString + + val response = objectMapper.readValue(responseBody, EventResultResponse::class.java) + + assertEquals(1, response.id, "Response event id should be 1") + assertEquals("Test Event", response.name, "Response event name should be 'Test Event'") + assertTrue(response.suitableDates.isEmpty(), "Response event should have no suitable dates") + } + + @Test + fun `get event results returns empty suitable dates list when no date has everyone's vote`() { + val event = + Event( + id = 1L, + name = "Test Event", + dates = + listOf( + LocalDate.of(2025, 10, 5), + LocalDate.of(2025, 10, 6), + ), + ) + + event.votes.addAll( + listOf( + EventVote( + id = 1L, + voterName = "Test Name 1", + votedDates = listOf(LocalDate.of(2025, 10, 5)), + event = event, + ), + EventVote( + id = 2L, + voterName = "Test Name 2", + votedDates = listOf(LocalDate.of(2025, 10, 6)), + event = event, + ), + ), + ) + + given(eventService.getEventById(1L)).willReturn(event) + + val responseBody = + mockMvc + .get("/api/v1/event/1/results") + .andExpect { status { isOk() } } + .andReturn() + .response + .contentAsString + + val response = objectMapper.readValue(responseBody, EventResultResponse::class.java) + + assertEquals(1, response.id, "Response event id should be 1") + assertEquals("Test Event", response.name, "Response event name should be 'Test Event'") + assertTrue(response.suitableDates.isEmpty(), "Response event should have no suitable dates") + } + + @Test + fun `get event results returns 404 when event not found`() { + given(eventService.getEventById(999L)).willThrow(EventNotFoundException(999L)) + + val responseBody = + mockMvc + .get("/api/v1/event/999/results") + .andExpect { status { isNotFound() } } + .andReturn() + .response + .contentAsString + + assertEquals( + "Event with ID 999 not found", + responseBody, + "Error body should contain an indication of invalid event id.", + ) + } + } }