Skip to content

Commit

Permalink
#67,75 - Cursor Based Pagination을 도입하고, Sent Vote 스펙을 변경한다. (#81)
Browse files Browse the repository at this point in the history
* refactor: Vote쪽 Sent Votes 로직 수정

* refactor: SentVotes 스펙 변경 완료

* test: 스펙 변경 이후 테스트 수정

* test: CompleteBallotTest 추가

* feat: Notification 목록 조회에 Cursor Based Pagination 적용

* feat: CursorBased Pagination 적용

* test: 테스트 수정

* refactor: 사용하지 않는 메서드 제거

* test: Fixture 추가

* test: CursorBased Repository Test 추가

* refactor: VoteId 추가 및 테스트 보강

* chore: UserSettings CustomUrlFilter에 추가

* refactor: CursorUtils 사용
  • Loading branch information
kpeel5839 authored Aug 8, 2024
1 parent 187ed5b commit dda6919
Show file tree
Hide file tree
Showing 40 changed files with 522 additions and 343 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class CustomUrlFilter(
"/api/v1/users/backgrounds",
"/api/v1/users/characters",
"/api/v1/users/search",
"/api/v1/users/settings",
"/api/v1/messages",
"/api/v1/messages/send",
"/api/v1/messages/status/me",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController


Expand All @@ -17,8 +18,11 @@ class NotificationController(
) {

@GetMapping
fun getNotifications(): ResponseEntity<NotificationResponses> {
val notifications = inquiryNotificationUseCase.getNotifications()
fun getNotifications(
@RequestParam(required = false) cursorId: Long?,
@RequestParam limit: Long
): ResponseEntity<NotificationResponses> {
val notifications = inquiryNotificationUseCase.getNotifications(cursorId, limit)

return ResponseEntity.ok(notifications)
}
Expand Down
1 change: 0 additions & 1 deletion app/src/main/kotlin/com/wespot/report/ReportController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.wespot.report.dto.ReportResponse
import com.wespot.report.port.`in`.SavedReportUseCase
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
Expand Down
25 changes: 10 additions & 15 deletions app/src/main/kotlin/com/wespot/vote/VoteController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.wespot.vote.dto.response.SaveVoteResponse
import com.wespot.vote.dto.response.VoteItems
import com.wespot.vote.dto.response.received.ReceivedVoteResponse
import com.wespot.vote.dto.response.received.ReceivedVotesResponses
import com.wespot.vote.dto.response.sent.SentVoteResponse
import com.wespot.vote.dto.response.sent.SentVotesResponses
import com.wespot.vote.dto.response.top1.VoteResultResponsesOfTop1
import com.wespot.vote.dto.response.top5.VoteResultResponsesOfTop5
Expand Down Expand Up @@ -67,8 +66,11 @@ class VoteController(
}

@GetMapping("/received")
fun getReceivedVotes(): ResponseEntity<ReceivedVotesResponses> {
val responses = receivedVoteUseCase.getReceivedVotes()
fun getReceivedVotes(
@RequestParam(required = false) cursorId: Long?,
@RequestParam limit: Long
): ResponseEntity<ReceivedVotesResponses> {
val responses = receivedVoteUseCase.getReceivedVotes(cursorId, limit)

return ResponseEntity.ok(responses)
}
Expand All @@ -84,20 +86,13 @@ class VoteController(
}

@GetMapping("/sent")
fun getSentVotes(): ResponseEntity<SentVotesResponses> {
val responses = sentVoteUseCase.getSentVotes()
fun getSentVotes(
@RequestParam(required = false) cursorId: Long?,
@RequestParam limit: Long
): ResponseEntity<SentVotesResponses> {
val responses = sentVoteUseCase.getSentVotes(cursorId, limit)

return ResponseEntity.ok(responses)
}

@GetMapping("/sent/options/{optionId}")
fun getSentVote(
@PathVariable optionId: Long,
@RequestParam date: LocalDate
): ResponseEntity<SentVoteResponse> {
val response = sentVoteUseCase.getSentVote(optionId, date)

return ResponseEntity.ok(response)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.wespot.notification.infrastructure.mysql

import com.wespot.notification.NotificationJpaEntity
import com.wespot.notification.NotificationJpaRepository
import com.wespot.notification.NotificationMapper
import com.wespot.notification.NotificationType
import com.wespot.notification.fixtrue.NotificationFixture
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest

@DataJpaTest
class NotificationJpaRepositoryTest @Autowired constructor(
private val notificationJpaRepository: NotificationJpaRepository
) {

@Test
fun `알림 목록 조회에 Cursor Based Pagination을 도입한다`() {
// given
val notification1 = createSavedNotification()
val notification2 = createSavedNotification()
val notification3 = createSavedNotification()
val notification4 = createSavedNotification()
val notification5 = createSavedNotification()
val userId = notification1.userId

// when
val firstSearch =
notificationJpaRepository.findAllByUserIdOrderByBaseEntityCreatedAtDesc(userId, Long.MAX_VALUE, 3)
val secondSearch =
notificationJpaRepository.findAllByUserIdOrderByBaseEntityCreatedAtDesc(userId, firstSearch[2].id, 4)

// then
firstSearch.size shouldBe 3
firstSearch[0] shouldBe notification5
firstSearch[1] shouldBe notification4
firstSearch[2] shouldBe notification3
secondSearch.size shouldBe 2
secondSearch[0] shouldBe notification2
secondSearch[1] shouldBe notification1
}

private fun createSavedNotification(): NotificationJpaEntity {
val notification = NotificationFixture.createWithType(NotificationType.MESSAGE)
val notificationJpaEntity = NotificationMapper.mapToJpaEntity(notification)

return notificationJpaRepository.save(notificationJpaEntity)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,16 @@ class InquiryNotificationServiceTest @Autowired constructor(
)

// when
val responses = inquiryNotificationService.getNotifications().notifications
val responses1 = inquiryNotificationService.getNotifications(null, 1)
val responses2= inquiryNotificationService.getNotifications(responses1.notifications[0].id, 1)

// then
responses.size shouldBe 2
responses[1].id shouldBe notifications[0].id
responses[0].id shouldBe savedNotification.id
responses1.notifications.size shouldBe 1
responses1.notifications[0].id shouldBe savedNotification.id
responses1.hasNext shouldBe true
responses2.notifications.size shouldBe 1
responses2.notifications[0].id shouldBe notifications[0].id
responses2.hasNext shouldBe false
}

@Test
Expand All @@ -72,9 +76,9 @@ class InquiryNotificationServiceTest @Autowired constructor(
)

// when
val responses = inquiryNotificationService.getNotifications().notifications
val responses = inquiryNotificationService.getNotifications(null, 100).notifications
inquiryNotificationService.readNotification(responses[0].id)
val actual = inquiryNotificationService.getNotifications().notifications
val actual = inquiryNotificationService.getNotifications(null, 100).notifications

// then
actual.size shouldBe 2
Expand Down
84 changes: 84 additions & 0 deletions app/src/test/kotlin/com/wespot/vote/domain/CompleteBallotTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.wespot.vote.domain

import com.wespot.user.fixture.UserFixture
import com.wespot.vote.CompleteBallot
import com.wespot.vote.fixture.BallotFixture
import com.wespot.vote.fixture.VoteFixture
import com.wespot.voteoption.fixture.VoteOptionFixture
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.throwable.shouldHaveMessage

class CompleteBallotTest : BehaviorSpec({

given("완전한 투표지를 만들 때") {
val ballot =
BallotFixture.createByVoteAndVoteOptionAndSenderAndReceiver(1, 1, 1, 2)
val validSender = UserFixture.createWithId(1)
val invalidSender = UserFixture.createWithId(2)
val validReceiver = UserFixture.createWithId(2)
val invalidReceiver = UserFixture.createWithId(1)
val validVote = VoteFixture.createWithIdAndVoteNumberAndBallots(1, 0, emptyList())
val invalidVote = VoteFixture.createWithIdAndVoteNumberAndBallots(2, 0, emptyList())
val validVoteOption = VoteOptionFixture.createWithId(1)
val invalidVoteOption = VoteOptionFixture.createWithId(2)
`when`("입력된 값이 투표지와 동일하지 않다면") {
val shouldThrow1 = shouldThrow<IllegalArgumentException> {
CompleteBallot.of(
invalidVote,
validVoteOption,
validSender,
validReceiver,
ballot
)
}
val shouldThrow2 = shouldThrow<IllegalArgumentException> {
CompleteBallot.of(
validVote,
invalidVoteOption,
validSender,
validReceiver,
ballot
)
}
val shouldThrow3 = shouldThrow<IllegalArgumentException> {
CompleteBallot.of(
validVote,
validVoteOption,
invalidSender,
validReceiver,
ballot
)
}
val shouldThrow4 = shouldThrow<IllegalArgumentException> {
CompleteBallot.of(
validVote,
validVoteOption,
validSender,
invalidReceiver,
ballot
)
}
then("예외가 발생한다.") {
shouldThrow1 shouldHaveMessage "입력된 투표가 잘못되었습니다."
shouldThrow2 shouldHaveMessage "입력된 선택지가 잘못되었습니다."
shouldThrow3 shouldHaveMessage "입력된 송신자가 잘못되었습니다."
shouldThrow4 shouldHaveMessage "입력된 수신자가 잘못되었습니다."
}
}
`when`("정상적인 값이 입력된다면") {
val completeBallot = CompleteBallot.of(validVote, validVoteOption, validSender, validReceiver, ballot)
then("정상적으로 생성된다.") {
completeBallot.vote shouldBe validVote
completeBallot.voteOption shouldBe validVoteOption
completeBallot.sender shouldBe validSender
completeBallot.receiver shouldBe validReceiver
completeBallot.createdAt shouldBe ballot.createdAt
completeBallot.updatedAt shouldBe ballot.updatedAt
completeBallot.isReceiverRead shouldBe ballot.isReceiverRead
}
}
}

})
83 changes: 15 additions & 68 deletions app/src/test/kotlin/com/wespot/vote/domain/VoteTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class VoteTest() : BehaviorSpec({
val vote = VoteFixture.createWithVoteNumberAndBallots(0, ballots)
val me = users[0]
val voteUsers = vote.findUsersForVote(users, me)
val userCounts= voteUsers.map { it.id }.toSet()
val userCounts = voteUsers.map { it.id }.toSet()
val doesNotContainsMe = voteUsers.stream().allMatch { it.id != users[0].id }
then("정상적으로 반환한다.") {
voteUsers.size shouldBe 5
Expand Down Expand Up @@ -491,75 +491,22 @@ class VoteTest() : BehaviorSpec({
it.createdAt
)
}
val userReceivedVote = vote.getUserSentVotes(users[0])
val userSentVotes = vote.getUserSentVotes(users[0], users)

then("결과를 정상적으로 반환한다.") {
userReceivedVote.size shouldBe 3
userReceivedVote[voteOptions[0]]!!.size shouldBe 2
userReceivedVote[voteOptions[0]]!!.size shouldBe 2
userReceivedVote[voteOptions[0]]!![0].receiverId shouldBe 2
userReceivedVote[voteOptions[0]]!![0].senderId shouldBe 1
userReceivedVote[voteOptions[0]]!![1].receiverId shouldBe 3
userReceivedVote[voteOptions[0]]!![1].senderId shouldBe 1
userReceivedVote[voteOptions[1]]!!.size shouldBe 1
userReceivedVote[voteOptions[1]]!![0].senderId shouldBe 1
userReceivedVote[voteOptions[1]]!![0].receiverId shouldBe 4
userReceivedVote[voteOptions[2]]!!.size shouldBe 1
userReceivedVote[voteOptions[2]]!![0].senderId shouldBe 1
userReceivedVote[voteOptions[2]]!![0].receiverId shouldBe 5
}
}

`when`("개별 조회하는 경우, 오늘의 질문지가 아니면") {
val users = createUserByCount(5)
val voteOptions = createVoteOptionByCount(10)
val vote = Vote.of(voteIdentifier, voteOptions, null)
ballots.forEach {
vote.addBallot(
it.voteOptionId,
UserFixture.createWithId(it.senderId),
UserFixture.createWithId(it.receiverId),
it.createdAt
)
}

then("예외가 발생한다.") {
val shouldThrow = shouldThrow<IllegalArgumentException> {
vote.getUserSentVote(
voteOptions[5],
users[0]
)
}
shouldThrow shouldHaveMessage "오늘 제공된 질문지만 선택해 투표할 수 있습니다."
}
}
`when`("개별 조회하는 경우") {
val users = createUserByCount(5)
val voteOptions = createVoteOptionByCount(10)
val vote = Vote.of(voteIdentifier, voteOptions, null)
ballots.forEach {
vote.addBallot(
it.voteOptionId,
UserFixture.createWithId(it.senderId),
UserFixture.createWithId(it.receiverId),
it.createdAt
)
}
val userSentVoteByFirstVoteOption = vote.getUserSentVote(voteOptions[0], users[0])
val userSentVoteBySecondVoteOption = vote.getUserSentVote(voteOptions[1], users[0])

then("결과를 정상적으로 반환한다.") {
userSentVoteByFirstVoteOption.size shouldBe 2
userSentVoteByFirstVoteOption[0].voteOptionId shouldBe 1
userSentVoteByFirstVoteOption[0].senderId shouldBe 1
userSentVoteByFirstVoteOption[0].receiverId shouldBe 2
userSentVoteByFirstVoteOption[1].voteOptionId shouldBe 1
userSentVoteByFirstVoteOption[1].senderId shouldBe 1
userSentVoteByFirstVoteOption[1].receiverId shouldBe 3
userSentVoteBySecondVoteOption.size shouldBe 1
userSentVoteBySecondVoteOption[0].voteOptionId shouldBe 2
userSentVoteBySecondVoteOption[0].senderId shouldBe 1
userSentVoteBySecondVoteOption[0].receiverId shouldBe 4
userSentVotes.size shouldBe 4
userSentVotes[0].voteOption shouldBe voteOptions[0]
userSentVotes[0].sender shouldBe users[0]
userSentVotes[0].receiver shouldBe users[1]
userSentVotes[1].voteOption shouldBe voteOptions[0]
userSentVotes[1].sender shouldBe users[0]
userSentVotes[1].receiver shouldBe users[2]
userSentVotes[2].voteOption shouldBe voteOptions[1]
userSentVotes[2].sender shouldBe users[0]
userSentVotes[2].receiver shouldBe users[3]
userSentVotes[3].voteOption shouldBe voteOptions[2]
userSentVotes[3].sender shouldBe users[0]
userSentVotes[3].receiver shouldBe users[4]
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,18 @@ object VoteJpaEntityFixture {
date = LocalDate.now(),
)

fun createWithSchoolIdAndGradeAndClassNumberAndDate(
schoolId: Long,
grade: Int,
classNumber: Int,
date: LocalDate
) = VoteJpaEntity(
id = 0L,
schoolId = schoolId,
grade = grade,
classNumber = classNumber,
voteNumber = 0,
date = date,
)

}
Loading

0 comments on commit dda6919

Please sign in to comment.