feat(channel-donation): 채널 후원 기능 추가
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package kr.co.vividnext.sodalive.explorer.profile.channelDonation
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaExceptionHandler
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.Mockito
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver
|
||||
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
|
||||
class ChannelDonationControllerTest {
|
||||
private lateinit var channelDonationService: ChannelDonationService
|
||||
private lateinit var controller: ChannelDonationController
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
channelDonationService = Mockito.mock(ChannelDonationService::class.java)
|
||||
controller = ChannelDonationController(channelDonationService)
|
||||
|
||||
mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(controller)
|
||||
.setControllerAdvice(SodaExceptionHandler(LangContext(), SodaMessageSource()))
|
||||
.setCustomArgumentResolvers(
|
||||
AuthenticationPrincipalArgumentResolver(),
|
||||
PageableHandlerMethodArgumentResolver()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldReturnErrorResponseWhenRequesterIsAnonymous() {
|
||||
mockMvc.perform(
|
||||
get("/explorer/profile/channel-donation")
|
||||
.param("creatorId", "1")
|
||||
.param("page", "0")
|
||||
.param("size", "5")
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(false))
|
||||
.andExpect(jsonPath("$.message").value("로그인 정보를 확인해주세요."))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldForwardPageableAndMemberToServiceWhenControllerMethodIsCalled() {
|
||||
val member = createMember(id = 7L, role = MemberRole.USER, nickname = "viewer")
|
||||
val item = GetChannelDonationListItem(
|
||||
id = 1001L,
|
||||
memberId = member.id!!,
|
||||
nickname = member.nickname,
|
||||
profileUrl = "https://cdn.test/profile/default-profile.png",
|
||||
can = 3,
|
||||
isSecret = false,
|
||||
message = "3캔을 후원하셨습니다.",
|
||||
createdAt = "2026-02-23T09:30:00"
|
||||
)
|
||||
val response = GetChannelDonationListResponse(totalCount = 1, items = listOf(item))
|
||||
|
||||
Mockito.`when`(
|
||||
channelDonationService.getChannelDonationList(
|
||||
creatorId = 1L,
|
||||
member = member,
|
||||
offset = 10L,
|
||||
limit = 5L
|
||||
)
|
||||
).thenReturn(response)
|
||||
|
||||
val apiResponse = controller.getChannelDonationList(
|
||||
creatorId = 1L,
|
||||
member = member,
|
||||
pageable = PageRequest.of(2, 5)
|
||||
)
|
||||
|
||||
assertEquals(true, apiResponse.success)
|
||||
assertEquals(1, apiResponse.data!!.totalCount)
|
||||
assertEquals(1001L, apiResponse.data!!.items[0].id)
|
||||
|
||||
Mockito.verify(channelDonationService).getChannelDonationList(
|
||||
creatorId = 1L,
|
||||
member = member,
|
||||
offset = 10L,
|
||||
limit = 5L
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMember(id: Long, role: MemberRole, nickname: String): Member {
|
||||
val member = Member(
|
||||
email = "$nickname@test.com",
|
||||
password = "password",
|
||||
nickname = nickname,
|
||||
role = role
|
||||
)
|
||||
member.id = id
|
||||
return member
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package kr.co.vividnext.sodalive.explorer.profile.channelDonation
|
||||
|
||||
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import org.springframework.context.annotation.Import
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.EntityManager
|
||||
|
||||
@DataJpaTest
|
||||
@Import(QueryDslConfig::class)
|
||||
class ChannelDonationMessageRepositoryTest @Autowired constructor(
|
||||
private val channelDonationMessageRepository: ChannelDonationMessageRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val entityManager: EntityManager
|
||||
) {
|
||||
@Test
|
||||
fun shouldFilterByDateAndSortByCreatedAtAndIdDescForViewer() {
|
||||
val creator = saveMember(nickname = "creator", role = MemberRole.CREATOR)
|
||||
val viewer = saveMember(nickname = "viewer", role = MemberRole.USER)
|
||||
val otherUser = saveMember(nickname = "other", role = MemberRole.USER)
|
||||
|
||||
val now = LocalDateTime.now()
|
||||
val tieTime = now.minusDays(2)
|
||||
|
||||
val oldPublic = saveMessage(member = viewer, creator = creator, can = 1, isSecret = false)
|
||||
val publicTieFirst = saveMessage(member = otherUser, creator = creator, can = 2, isSecret = false)
|
||||
val publicTieSecond = saveMessage(member = viewer, creator = creator, can = 3, isSecret = false)
|
||||
val secretMine = saveMessage(member = viewer, creator = creator, can = 4, isSecret = true)
|
||||
val secretOther = saveMessage(member = otherUser, creator = creator, can = 5, isSecret = true)
|
||||
|
||||
updateCreatedAt(oldPublic.id!!, now.minusMonths(2))
|
||||
updateCreatedAt(publicTieFirst.id!!, tieTime)
|
||||
updateCreatedAt(publicTieSecond.id!!, tieTime)
|
||||
updateCreatedAt(secretMine.id!!, now.minusDays(1))
|
||||
updateCreatedAt(secretOther.id!!, now.minusHours(12))
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
|
||||
val list = channelDonationMessageRepository.getChannelDonationMessageList(
|
||||
creatorId = creator.id!!,
|
||||
memberId = viewer.id!!,
|
||||
isCreator = false,
|
||||
offset = 0,
|
||||
limit = 10,
|
||||
startDateTime = now.minusMonths(1)
|
||||
)
|
||||
|
||||
val totalCount = channelDonationMessageRepository.getChannelDonationMessageTotalCount(
|
||||
creatorId = creator.id!!,
|
||||
memberId = viewer.id!!,
|
||||
isCreator = false,
|
||||
startDateTime = now.minusMonths(1)
|
||||
)
|
||||
|
||||
assertEquals(3, list.size)
|
||||
assertEquals(secretMine.id, list[0].id)
|
||||
assertEquals(publicTieSecond.id, list[1].id)
|
||||
assertEquals(publicTieFirst.id, list[2].id)
|
||||
assertEquals(3, totalCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldIncludeAllRecentSecretMessagesForCreator() {
|
||||
val creator = saveMember(nickname = "creator2", role = MemberRole.CREATOR)
|
||||
val viewer = saveMember(nickname = "viewer2", role = MemberRole.USER)
|
||||
val otherUser = saveMember(nickname = "other2", role = MemberRole.USER)
|
||||
|
||||
val now = LocalDateTime.now()
|
||||
val oldPublic = saveMessage(member = viewer, creator = creator, can = 1, isSecret = false)
|
||||
val recentPublic = saveMessage(member = viewer, creator = creator, can = 2, isSecret = false)
|
||||
val recentSecretMine = saveMessage(member = viewer, creator = creator, can = 3, isSecret = true)
|
||||
val recentSecretOther = saveMessage(member = otherUser, creator = creator, can = 4, isSecret = true)
|
||||
|
||||
updateCreatedAt(oldPublic.id!!, now.minusMonths(2))
|
||||
updateCreatedAt(recentPublic.id!!, now.minusDays(3))
|
||||
updateCreatedAt(recentSecretMine.id!!, now.minusDays(2))
|
||||
updateCreatedAt(recentSecretOther.id!!, now.minusDays(1))
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
|
||||
val list = channelDonationMessageRepository.getChannelDonationMessageList(
|
||||
creatorId = creator.id!!,
|
||||
memberId = creator.id!!,
|
||||
isCreator = true,
|
||||
offset = 0,
|
||||
limit = 10,
|
||||
startDateTime = now.minusMonths(1)
|
||||
)
|
||||
|
||||
val totalCount = channelDonationMessageRepository.getChannelDonationMessageTotalCount(
|
||||
creatorId = creator.id!!,
|
||||
memberId = creator.id!!,
|
||||
isCreator = true,
|
||||
startDateTime = now.minusMonths(1)
|
||||
)
|
||||
|
||||
assertEquals(3, list.size)
|
||||
assertEquals(recentSecretOther.id, list[0].id)
|
||||
assertEquals(recentSecretMine.id, list[1].id)
|
||||
assertEquals(recentPublic.id, list[2].id)
|
||||
assertEquals(3, totalCount)
|
||||
}
|
||||
|
||||
private fun saveMember(nickname: String, role: MemberRole): Member {
|
||||
return memberRepository.saveAndFlush(
|
||||
Member(
|
||||
email = "$nickname@test.com",
|
||||
password = "password",
|
||||
nickname = nickname,
|
||||
role = role
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun saveMessage(member: Member, creator: Member, can: Int, isSecret: Boolean): ChannelDonationMessage {
|
||||
val message = ChannelDonationMessage(can = can, isSecret = isSecret)
|
||||
message.member = member
|
||||
message.creator = creator
|
||||
return channelDonationMessageRepository.saveAndFlush(message)
|
||||
}
|
||||
|
||||
private fun updateCreatedAt(id: Long, createdAt: LocalDateTime) {
|
||||
entityManager.createQuery(
|
||||
"update ChannelDonationMessage m set m.createdAt = :createdAt where m.id = :id"
|
||||
)
|
||||
.setParameter("createdAt", createdAt)
|
||||
.setParameter("id", id)
|
||||
.executeUpdate()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package kr.co.vividnext.sodalive.explorer.profile.channelDonation
|
||||
|
||||
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertThrows
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.Mockito
|
||||
import java.time.LocalDateTime
|
||||
|
||||
class ChannelDonationServiceTest {
|
||||
private lateinit var canPaymentService: CanPaymentService
|
||||
private lateinit var memberRepository: MemberRepository
|
||||
private lateinit var channelDonationMessageRepository: ChannelDonationMessageRepository
|
||||
private lateinit var service: ChannelDonationService
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
canPaymentService = Mockito.mock(CanPaymentService::class.java)
|
||||
memberRepository = Mockito.mock(MemberRepository::class.java)
|
||||
channelDonationMessageRepository = Mockito.mock(ChannelDonationMessageRepository::class.java)
|
||||
service = ChannelDonationService(
|
||||
canPaymentService = canPaymentService,
|
||||
memberRepository = memberRepository,
|
||||
channelDonationMessageRepository = channelDonationMessageRepository,
|
||||
messageSource = SodaMessageSource(),
|
||||
langContext = LangContext(),
|
||||
cloudFrontHost = "https://cdn.test"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldThrowWhenDonateCanIsLessThanOne() {
|
||||
val member = createMember(id = 10L, role = MemberRole.USER, nickname = "viewer")
|
||||
val request = PostChannelDonationRequest(
|
||||
creatorId = 1L,
|
||||
can = 0,
|
||||
isSecret = false,
|
||||
message = "",
|
||||
container = "aos"
|
||||
)
|
||||
|
||||
val exception = assertThrows(SodaException::class.java) {
|
||||
service.donate(request, member)
|
||||
}
|
||||
|
||||
assertEquals("content.donation.error.minimum_can", exception.messageKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldPassUserVisibilityFlagToRepositoryWhenRequesterIsNotCreator() {
|
||||
val creator = createMember(id = 1L, role = MemberRole.CREATOR, nickname = "creator")
|
||||
val viewer = createMember(id = 2L, role = MemberRole.USER, nickname = "viewer")
|
||||
val message = ChannelDonationMessage(can = 3, isSecret = true, additionalMessage = "응원합니다")
|
||||
message.id = 1001L
|
||||
message.member = viewer
|
||||
message.creator = creator
|
||||
message.createdAt = LocalDateTime.of(2026, 2, 20, 12, 0, 0)
|
||||
|
||||
Mockito.`when`(memberRepository.findCreatorByIdOrNull(creator.id!!)).thenReturn(creator)
|
||||
Mockito.`when`(
|
||||
channelDonationMessageRepository.getChannelDonationMessageTotalCount(
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(viewer.id!!),
|
||||
Mockito.eq(false),
|
||||
anyLocalDateTime()
|
||||
)
|
||||
).thenReturn(1)
|
||||
Mockito.`when`(
|
||||
channelDonationMessageRepository.getChannelDonationMessageList(
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(viewer.id!!),
|
||||
Mockito.eq(false),
|
||||
Mockito.eq(0L),
|
||||
Mockito.eq(5L),
|
||||
anyLocalDateTime()
|
||||
)
|
||||
).thenReturn(listOf(message))
|
||||
|
||||
val result = service.getChannelDonationList(
|
||||
creatorId = creator.id!!,
|
||||
member = viewer,
|
||||
offset = 0,
|
||||
limit = 5
|
||||
)
|
||||
|
||||
assertEquals(1, result.totalCount)
|
||||
assertEquals(1, result.items.size)
|
||||
assertEquals("3캔을 비밀후원하셨습니다.\n\"응원합니다\"", result.items[0].message)
|
||||
|
||||
Mockito.verify(channelDonationMessageRepository).getChannelDonationMessageTotalCount(
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(viewer.id!!),
|
||||
Mockito.eq(false),
|
||||
anyLocalDateTime()
|
||||
)
|
||||
Mockito.verify(channelDonationMessageRepository).getChannelDonationMessageList(
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(viewer.id!!),
|
||||
Mockito.eq(false),
|
||||
Mockito.eq(0L),
|
||||
Mockito.eq(5L),
|
||||
anyLocalDateTime()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldPassCreatorVisibilityFlagToRepositoryWhenRequesterIsCreatorSelf() {
|
||||
val creator = createMember(id = 1L, role = MemberRole.CREATOR, nickname = "creator")
|
||||
|
||||
Mockito.`when`(memberRepository.findCreatorByIdOrNull(creator.id!!)).thenReturn(creator)
|
||||
Mockito.`when`(
|
||||
channelDonationMessageRepository.getChannelDonationMessageTotalCount(
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(true),
|
||||
anyLocalDateTime()
|
||||
)
|
||||
).thenReturn(0)
|
||||
Mockito.`when`(
|
||||
channelDonationMessageRepository.getChannelDonationMessageList(
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(true),
|
||||
Mockito.eq(0L),
|
||||
Mockito.eq(5L),
|
||||
anyLocalDateTime()
|
||||
)
|
||||
).thenReturn(emptyList())
|
||||
|
||||
service.getChannelDonationList(
|
||||
creatorId = creator.id!!,
|
||||
member = creator,
|
||||
offset = 0,
|
||||
limit = 5
|
||||
)
|
||||
|
||||
Mockito.verify(channelDonationMessageRepository).getChannelDonationMessageTotalCount(
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(true),
|
||||
anyLocalDateTime()
|
||||
)
|
||||
Mockito.verify(channelDonationMessageRepository).getChannelDonationMessageList(
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(creator.id!!),
|
||||
Mockito.eq(true),
|
||||
Mockito.eq(0L),
|
||||
Mockito.eq(5L),
|
||||
anyLocalDateTime()
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMember(id: Long, role: MemberRole, nickname: String): Member {
|
||||
val member = Member(
|
||||
email = "$nickname@test.com",
|
||||
password = "password",
|
||||
nickname = nickname,
|
||||
role = role
|
||||
)
|
||||
member.id = id
|
||||
return member
|
||||
}
|
||||
|
||||
private fun anyLocalDateTime(): LocalDateTime {
|
||||
Mockito.any(LocalDateTime::class.java)
|
||||
return LocalDateTime.MIN
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user