From 286b206cbcd93d0df00bb70ef2696f537cac1027 Mon Sep 17 00:00:00 2001 From: YoungJun Park Date: Fri, 4 Oct 2024 01:16:12 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Add:=20SnowMaker=20API=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weski/snow_maker/SnowMakerController.kt | 20 +++++++++ .../nexters/weski/snow_maker/SnowMakerDto.kt | 8 ++++ .../weski/snow_maker/SnowMakerService.kt | 42 +++++++++++++++++++ .../nexters/weski/snow_maker/SnowMakerVote.kt | 22 ++++++++++ .../snow_maker/SnowMakerVoteRepository.kt | 8 ++++ 5 files changed, 100 insertions(+) create mode 100644 src/main/kotlin/nexters/weski/snow_maker/SnowMakerController.kt create mode 100644 src/main/kotlin/nexters/weski/snow_maker/SnowMakerDto.kt create mode 100644 src/main/kotlin/nexters/weski/snow_maker/SnowMakerService.kt create mode 100644 src/main/kotlin/nexters/weski/snow_maker/SnowMakerVote.kt create mode 100644 src/main/kotlin/nexters/weski/snow_maker/SnowMakerVoteRepository.kt diff --git a/src/main/kotlin/nexters/weski/snow_maker/SnowMakerController.kt b/src/main/kotlin/nexters/weski/snow_maker/SnowMakerController.kt new file mode 100644 index 0000000..d9b62ea --- /dev/null +++ b/src/main/kotlin/nexters/weski/snow_maker/SnowMakerController.kt @@ -0,0 +1,20 @@ +package nexters.weski.snow_maker + +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/api/snow-maker") + +class SnowMakerController( + private val snowMakerService: SnowMakerService +) { + @GetMapping("/{resortId}") + fun getSnowMaker(@PathVariable resortId: Long): SnowMakerDto { + return snowMakerService.getSnowMaker(resortId) + } + + @PostMapping("/{resortId}/vote") + fun voteSnowMaker(@PathVariable resortId: Long, @RequestParam isPositive: Boolean) { + snowMakerService.voteSnowMaker(resortId, isPositive) + } +} diff --git a/src/main/kotlin/nexters/weski/snow_maker/SnowMakerDto.kt b/src/main/kotlin/nexters/weski/snow_maker/SnowMakerDto.kt new file mode 100644 index 0000000..3924588 --- /dev/null +++ b/src/main/kotlin/nexters/weski/snow_maker/SnowMakerDto.kt @@ -0,0 +1,8 @@ +package nexters.weski.snow_maker + +data class SnowMakerDto( + val resortId: Long, + val totalVotes: Long, + val positiveVotes: Long, + val status: String, +) diff --git a/src/main/kotlin/nexters/weski/snow_maker/SnowMakerService.kt b/src/main/kotlin/nexters/weski/snow_maker/SnowMakerService.kt new file mode 100644 index 0000000..4f5ea16 --- /dev/null +++ b/src/main/kotlin/nexters/weski/snow_maker/SnowMakerService.kt @@ -0,0 +1,42 @@ +package nexters.weski.snow_maker + +import nexters.weski.ski_resort.SkiResortRepository +import org.springframework.stereotype.Service + +@Service +class SnowMakerService( + private val snowMakerVoteRepository: SnowMakerVoteRepository, + private val skiResortRepository: SkiResortRepository +) { + fun getSnowMaker(resortId: Long): SnowMakerDto { + val totalVotes = snowMakerVoteRepository.countBySkiResortResortId(resortId) + val positiveVotes = snowMakerVoteRepository.countBySkiResortResortIdAndIsPositive(resortId, true) + val status = calculateStatus(totalVotes, positiveVotes) + + return SnowMakerDto( + resortId = resortId, + totalVotes = totalVotes, + positiveVotes = positiveVotes, + status = status + ) + } + + fun voteSnowMaker(resortId: Long, isPositive: Boolean) { + val skiResort = skiResortRepository.findById(resortId).orElseThrow { Exception("Resort not found") } + val vote = SnowMakerVote( + isPositive = isPositive, + skiResort = skiResort + ) + snowMakerVoteRepository.save(vote) + } + + private fun calculateStatus(totalVotes: Long, positiveVotes: Long): String { + if (totalVotes == 0L) return "정보 없음" + val positiveRate = (positiveVotes.toDouble() / totalVotes.toDouble()) * 100 + return when { + positiveRate >= 80 -> "좋음" + positiveRate >= 50 -> "나쁘지 않음" + else -> "좋지 않음" + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/nexters/weski/snow_maker/SnowMakerVote.kt b/src/main/kotlin/nexters/weski/snow_maker/SnowMakerVote.kt new file mode 100644 index 0000000..a347881 --- /dev/null +++ b/src/main/kotlin/nexters/weski/snow_maker/SnowMakerVote.kt @@ -0,0 +1,22 @@ +package nexters.weski.snow_maker + + +import jakarta.persistence.* +import nexters.weski.common.BaseEntity +import nexters.weski.ski_resort.SkiResort +import java.time.LocalDateTime + +@Entity +@Table(name = "snow_quality_votes") +data class SnowMakerVote( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + val isPositive: Boolean, + val votedAt: LocalDateTime = LocalDateTime.now(), + + @ManyToOne + @JoinColumn(name = "resort_id") + val skiResort: SkiResort +) : BaseEntity() diff --git a/src/main/kotlin/nexters/weski/snow_maker/SnowMakerVoteRepository.kt b/src/main/kotlin/nexters/weski/snow_maker/SnowMakerVoteRepository.kt new file mode 100644 index 0000000..f4d7b9f --- /dev/null +++ b/src/main/kotlin/nexters/weski/snow_maker/SnowMakerVoteRepository.kt @@ -0,0 +1,8 @@ +package nexters.weski.snow_maker + +import org.springframework.data.jpa.repository.JpaRepository + +interface SnowMakerVoteRepository : JpaRepository { + fun countBySkiResortResortId(resortId: Long): Long + fun countBySkiResortResortIdAndIsPositive(resortId: Long, isPositive: Boolean): Long +} \ No newline at end of file From 589af146c81a6aec66aab1062c6fe076dbb62621 Mon Sep 17 00:00:00 2001 From: YoungJun Park Date: Fri, 4 Oct 2024 01:16:33 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=A7=AA=20SnowMaker=20TestCode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../snow_maker/SnowMakerControllerTest.kt | 57 +++++++++++++++++++ .../weski/snow_maker/SnowMakerServiceTest.kt | 53 +++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 src/test/kotlin/nexters/weski/snow_maker/SnowMakerControllerTest.kt create mode 100644 src/test/kotlin/nexters/weski/snow_maker/SnowMakerServiceTest.kt diff --git a/src/test/kotlin/nexters/weski/snow_maker/SnowMakerControllerTest.kt b/src/test/kotlin/nexters/weski/snow_maker/SnowMakerControllerTest.kt new file mode 100644 index 0000000..69d5dbb --- /dev/null +++ b/src/test/kotlin/nexters/weski/snow_maker/SnowMakerControllerTest.kt @@ -0,0 +1,57 @@ +package nexters.weski.snow_maker + + +import com.ninjasquad.springmockk.MockkBean +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import nexters.weski.common.config.JpaConfig +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.FilterType +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.* + +@WebMvcTest(SnowMakerController::class) +@ComponentScan( + excludeFilters = [ComponentScan.Filter( + type = FilterType.ASSIGNABLE_TYPE, + classes = [JpaConfig::class] + )] +) +class SnowMakerControllerTest @Autowired constructor( + private val mockMvc: MockMvc +) { + + @MockkBean + lateinit var snowMakerService: SnowMakerService + + @Test + fun `GET api_snow-maker_resortId should return snow Maker data`() { + // Given + val resortId = 1L + val snowMakerDto = SnowMakerDto(resortId, 100, 80, "좋음") + every { snowMakerService.getSnowMaker(resortId) } returns snowMakerDto + + // When & Then + mockMvc.perform(get("/api/snow-maker/$resortId")) + .andExpect(status().isOk) + .andExpect(jsonPath("$.totalVotes").value(100)) + .andExpect(jsonPath("$.status").value("좋음")) + } + + @Test + fun `POST api_snow-maker_resortId_vote should vote successfully`() { + // Given + val resortId = 1L + every { snowMakerService.voteSnowMaker(any(), any()) } just Runs + + // When & Then + mockMvc.perform(post("/api/snow-maker/$resortId/vote") + .param("isPositive", "true")) + .andExpect(status().isOk) + } +} diff --git a/src/test/kotlin/nexters/weski/snow_maker/SnowMakerServiceTest.kt b/src/test/kotlin/nexters/weski/snow_maker/SnowMakerServiceTest.kt new file mode 100644 index 0000000..a036e57 --- /dev/null +++ b/src/test/kotlin/nexters/weski/snow_maker/SnowMakerServiceTest.kt @@ -0,0 +1,53 @@ +package nexters.weski.snow_maker + + +import io.mockk.* +import nexters.weski.ski_resort.ResortStatus +import nexters.weski.ski_resort.SkiResort +import nexters.weski.ski_resort.SkiResortRepository +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.Optional + +class SnowMakerServiceTest { + + private val snowMakerVoteRepository: SnowMakerVoteRepository = mockk(relaxed = true) + private val skiResortRepository: SkiResortRepository = mockk() + private val snowMakerService = SnowMakerService(snowMakerVoteRepository, skiResortRepository) + + @Test + fun `getSnowMaker should return SnowMakerDto`() { + // Given + val resortId = 1L + every { snowMakerVoteRepository.countBySkiResortResortId(resortId) } returns 100 + every { snowMakerVoteRepository.countBySkiResortResortIdAndIsPositive(resortId, true) } returns 80 + + // When + val result = snowMakerService.getSnowMaker(resortId) + + // Then + assertEquals(100, result.totalVotes) + assertEquals(80, result.positiveVotes) + assertEquals("좋음", result.status) + } + + @Test + fun `voteSnowMaker should save vote`() { + // Given + val resortId = 1L + val isPositive = true + val skiResort = SkiResort(resortId, "스키장 A", ResortStatus.운영중, null, null, 5, 10) + val snowMakerVote = SnowMakerVote( + isPositive = isPositive, + skiResort = skiResort + ) + every { skiResortRepository.findById(resortId) } returns Optional.of(skiResort) + every { snowMakerVoteRepository.save(any()) } returns snowMakerVote + + // When + snowMakerService.voteSnowMaker(resortId, isPositive) + + // Then + verify { snowMakerVoteRepository.save(any()) } + } +} \ No newline at end of file