test #426

Merged
klaus merged 415 commits from test into main 2026-06-27 00:35:30 +00:00
3 changed files with 220 additions and 0 deletions
Showing only changes of commit 14f648cd10 - Show all commits

View File

@@ -0,0 +1,32 @@
package kr.co.vividnext.sodalive.v2.api.creator.channel.donation.application
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.v2.api.creator.channel.donation.dto.CreatorChannelDonationTabResponse
import kr.co.vividnext.sodalive.v2.creator.channel.donation.application.CreatorChannelDonationQueryService
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@Service
@Transactional(readOnly = true)
class CreatorChannelDonationFacade(
private val creatorChannelDonationQueryService: CreatorChannelDonationQueryService
) {
fun getDonationTab(
creatorId: Long,
viewer: Member,
page: Int?,
size: Int?,
now: LocalDateTime = LocalDateTime.now()
): CreatorChannelDonationTabResponse {
return CreatorChannelDonationTabResponse.from(
creatorChannelDonationQueryService.getDonationTab(
creatorId = creatorId,
viewer = viewer,
page = page,
size = size,
now = now
)
)
}
}

View File

@@ -0,0 +1,68 @@
package kr.co.vividnext.sodalive.v2.api.creator.channel.donation.dto
import com.fasterxml.jackson.annotation.JsonProperty
import kr.co.vividnext.sodalive.extensions.toUtcIso
import kr.co.vividnext.sodalive.v2.creator.channel.donation.domain.CreatorChannelDonation
import kr.co.vividnext.sodalive.v2.creator.channel.donation.domain.CreatorChannelDonationRanking
import kr.co.vividnext.sodalive.v2.creator.channel.donation.domain.CreatorChannelDonationTab
data class CreatorChannelDonationTabResponse(
val donationCount: Int,
val rankings: List<MemberDonationRankingResponse>,
val donations: List<CreatorChannelDonationResponse>,
val page: Int,
val size: Int,
@JsonProperty("hasNext")
val hasNext: Boolean
) {
companion object {
fun from(tab: CreatorChannelDonationTab): CreatorChannelDonationTabResponse {
return CreatorChannelDonationTabResponse(
donationCount = tab.donationCount,
rankings = tab.rankings.map(MemberDonationRankingResponse::from),
donations = tab.donations.map(CreatorChannelDonationResponse::from),
page = tab.page.page,
size = tab.page.size,
hasNext = tab.hasNext
)
}
}
}
data class MemberDonationRankingResponse(
@JsonProperty("userId") val userId: Long,
@JsonProperty("nickname") val nickname: String,
@JsonProperty("profileImage") val profileImage: String,
@JsonProperty("donationCan") val donationCan: Int
) {
companion object {
fun from(ranking: CreatorChannelDonationRanking): MemberDonationRankingResponse {
return MemberDonationRankingResponse(
userId = ranking.userId,
nickname = ranking.nickname,
profileImage = ranking.profileImage,
donationCan = ranking.donationCan
)
}
}
}
data class CreatorChannelDonationResponse(
val nickname: String,
val profileImageUrl: String,
val can: Int,
val message: String,
val createdAtUtc: String
) {
companion object {
fun from(donation: CreatorChannelDonation): CreatorChannelDonationResponse {
return CreatorChannelDonationResponse(
nickname = donation.nickname,
profileImageUrl = donation.profileImageUrl,
can = donation.can,
message = donation.message,
createdAtUtc = donation.createdAt.toUtcIso()
)
}
}
}

View File

@@ -0,0 +1,120 @@
package kr.co.vividnext.sodalive.v2.api.creator.channel.donation.application
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.v2.api.creator.channel.donation.dto.CreatorChannelDonationTabResponse
import kr.co.vividnext.sodalive.v2.creator.channel.donation.application.CreatorChannelDonationQueryService
import kr.co.vividnext.sodalive.v2.creator.channel.donation.domain.CreatorChannelDonation
import kr.co.vividnext.sodalive.v2.creator.channel.donation.domain.CreatorChannelDonationRanking
import kr.co.vividnext.sodalive.v2.creator.channel.donation.domain.CreatorChannelDonationTab
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelPage
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import java.time.LocalDateTime
class CreatorChannelDonationFacadeTest {
@Test
@DisplayName("후원 탭 응답 DTO는 domain tab 값을 공개 응답 필드와 UTC 문자열로 매핑한다")
fun shouldMapDonationTabDomainToPublicResponse() {
val response = CreatorChannelDonationTabResponse.from(createTab())
assertEquals(3, response.donationCount)
assertEquals(10L, response.rankings.first().userId)
assertEquals("fan", response.rankings.first().nickname)
assertEquals("https://cdn.test/fan.png", response.rankings.first().profileImage)
assertEquals(100, response.rankings.first().donationCan)
assertEquals("donor", response.donations.first().nickname)
assertEquals("https://cdn.test/donor.png", response.donations.first().profileImageUrl)
assertEquals(50, response.donations.first().can)
assertEquals("thanks", response.donations.first().message)
assertEquals("2026-06-21T03:30:00Z", response.donations.first().createdAtUtc)
assertEquals(1, response.page)
assertEquals(20, response.size)
assertTrue(response.hasNext)
val mapper = ObjectMapper().registerModule(KotlinModule.Builder().build())
val json = mapper.readTree(mapper.writeValueAsString(response))
assertEquals(10L, json["rankings"][0]["userId"].asLong())
assertEquals("fan", json["rankings"][0]["nickname"].asText())
assertEquals("https://cdn.test/fan.png", json["rankings"][0]["profileImage"].asText())
assertEquals(100, json["rankings"][0]["donationCan"].asInt())
assertEquals("donor", json["donations"][0]["nickname"].asText())
assertEquals("https://cdn.test/donor.png", json["donations"][0]["profileImageUrl"].asText())
assertEquals(50, json["donations"][0]["can"].asInt())
assertEquals("thanks", json["donations"][0]["message"].asText())
assertEquals("2026-06-21T03:30:00Z", json["donations"][0]["createdAtUtc"].asText())
assertTrue(json["hasNext"].asBoolean())
assertFalse(json.has("languageCode"))
}
@Test
@DisplayName("후원 탭 facade는 query service 결과를 공개 응답 DTO로 변환한다")
fun shouldMapDonationTabQueryResultToPublicResponse() {
val service = Mockito.mock(CreatorChannelDonationQueryService::class.java)
val facade = CreatorChannelDonationFacade(service)
val viewer = createMember(id = 10L)
val now = LocalDateTime.of(2026, 6, 21, 12, 0)
Mockito.doReturn(createTab()).`when`(service).getDonationTab(
creatorId = 1L,
viewer = viewer,
page = -1,
size = 100,
now = now
)
val response = facade.getDonationTab(
creatorId = 1L,
viewer = viewer,
page = -1,
size = 100,
now = now
)
assertEquals(3, response.donationCount)
assertEquals(10L, response.rankings.first().userId)
assertEquals("https://cdn.test/donor.png", response.donations.first().profileImageUrl)
assertEquals(1, response.page)
assertEquals(20, response.size)
assertTrue(response.hasNext)
}
private fun createMember(id: Long): Member {
return Member(
email = "viewer$id@test.com",
password = "password",
nickname = "viewer$id",
role = MemberRole.USER
).apply { this.id = id }
}
private fun createTab(): CreatorChannelDonationTab {
return CreatorChannelDonationTab(
donationCount = 3,
rankings = listOf(
CreatorChannelDonationRanking(
userId = 10L,
nickname = "fan",
profileImage = "https://cdn.test/fan.png",
donationCan = 100
)
),
donations = listOf(
CreatorChannelDonation(
nickname = "donor",
profileImageUrl = "https://cdn.test/donor.png",
can = 50,
message = "thanks",
createdAt = LocalDateTime.of(2026, 6, 21, 3, 30)
)
),
page = CreatorChannelPage(page = 1, size = 20),
hasNext = true
)
}
}