test #426

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

View File

@@ -4,8 +4,6 @@ import com.querydsl.core.types.Projections
import com.querydsl.core.types.dsl.BooleanExpression import com.querydsl.core.types.dsl.BooleanExpression
import com.querydsl.core.types.dsl.Expressions import com.querydsl.core.types.dsl.Expressions
import com.querydsl.jpa.impl.JPAQueryFactory import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.can.use.CanUsage
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
import kr.co.vividnext.sodalive.chat.character.QChatCharacter.chatCharacter import kr.co.vividnext.sodalive.chat.character.QChatCharacter.chatCharacter
import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
@@ -15,9 +13,6 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.QChannelDonationMessage.channelDonationMessage import kr.co.vividnext.sodalive.explorer.profile.channelDonation.QChannelDonationMessage.channelDonationMessage
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
import kr.co.vividnext.sodalive.live.room.GenderRestriction import kr.co.vividnext.sodalive.live.room.GenderRestriction
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
@@ -30,7 +25,6 @@ import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollow
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelActivityRecord import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelActivityRecord
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelAudioContentRecord import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelAudioContentRecord
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelCommunityPostRecord
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelCreatorRecord import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelCreatorRecord
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelDonationRecord import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelDonationRecord
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelFanTalkRecord import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelFanTalkRecord
@@ -207,90 +201,6 @@ class DefaultCreatorChannelHomeQueryRepository(
.fetch() .fetch()
} }
override fun findCommunityPosts(
creatorId: Long,
viewerId: Long?,
isFixed: Boolean,
canViewAdultContent: Boolean,
limit: Int
): List<CreatorChannelCommunityPostRecord> {
val posts = queryFactory
.select(
creatorCommunity.id,
creatorCommunity.member.id,
creatorCommunity.member.nickname,
creatorCommunity.member.profileImage,
creatorCommunity.imagePath,
creatorCommunity.audioPath,
creatorCommunity.content,
creatorCommunity.price,
creatorCommunity.createdAt,
creatorCommunity.fixedAt,
creatorCommunity.isFixed,
creatorCommunity.isCommentAvailable
)
.from(creatorCommunity)
.where(
creatorCommunity.member.id.eq(creatorId),
creatorCommunity.member.isActive.isTrue,
visibleCommunityPostCondition(viewerId),
creatorCommunity.isFixed.eq(isFixed),
fixedNoticeCondition(isFixed),
adultCommunityCondition(canViewAdultContent)
)
.orderBy(
if (isFixed) creatorCommunity.fixedAt.desc() else creatorCommunity.createdAt.desc(),
creatorCommunity.id.desc()
)
.limit(limit.toLong())
.fetch()
val postIds = posts.map { it.get(creatorCommunity.id)!! }
val orderedPostIds = orderedCommunityPostIds(creatorId, viewerId, postIds)
val likeCounts = communityLikeCounts(postIds)
val commentCounts = communityCommentCounts(
postIds = posts.filter { it.get(creatorCommunity.isCommentAvailable)!! }.map { it.get(creatorCommunity.id)!! },
viewerId = viewerId,
isContentCreator = viewerId == creatorId
)
return posts
.map {
val postId = it.get(creatorCommunity.id)!!
val postCreatorId = it.get(creatorCommunity.member.id)!!
val isFixedPost = it.get(creatorCommunity.isFixed)!!
val price = it.get(creatorCommunity.price)!!
val existOrdered = postId in orderedPostIds
val canAccessPaidContent = canAccessPaidCommunityContent(
price = price,
viewerId = viewerId,
creatorId = postCreatorId,
existOrdered = existOrdered
)
CreatorChannelCommunityPostRecord(
postId = postId,
creatorId = postCreatorId,
creatorNickname = it.get(creatorCommunity.member.nickname)!!,
creatorProfilePath = it.get(creatorCommunity.member.profileImage),
imagePath = it.get(creatorCommunity.imagePath),
audioPath = if (canAccessPaidContent) it.get(creatorCommunity.audioPath) else null,
content = maskPaidCommunityContent(
content = it.get(creatorCommunity.content)!!,
canAccessPaidContent = canAccessPaidContent
),
price = price,
date = if (isFixedPost) {
it.get(creatorCommunity.fixedAt) ?: it.get(creatorCommunity.createdAt)!!
} else {
it.get(creatorCommunity.createdAt)!!
},
existOrdered = existOrdered,
likeCount = likeCounts[postId] ?: 0,
commentCount = commentCounts[postId] ?: 0
)
}
}
override fun findSchedules( override fun findSchedules(
creatorId: Long, creatorId: Long,
now: LocalDateTime, now: LocalDateTime,
@@ -639,103 +549,6 @@ class DefaultCreatorChannelHomeQueryRepository(
.fetchFirst() .fetchFirst()
} }
private fun orderedCommunityPostIds(creatorId: Long, viewerId: Long?, postIds: List<Long>): Set<Long> {
if (viewerId == null || postIds.isEmpty()) return emptySet()
if (viewerId == creatorId) return postIds.toSet()
return queryFactory
.select(useCan.communityPost.id)
.from(useCan)
.where(
useCan.member.id.eq(viewerId),
useCan.communityPost.id.`in`(postIds),
useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST),
useCan.isRefund.isFalse
)
.fetch()
.toSet()
}
private fun communityLikeCounts(postIds: List<Long>): Map<Long, Int> {
if (postIds.isEmpty()) return emptyMap()
return queryFactory
.select(creatorCommunityLike.creatorCommunity.id, creatorCommunityLike.id.count())
.from(creatorCommunityLike)
.where(creatorCommunityLike.creatorCommunity.id.`in`(postIds), creatorCommunityLike.isActive.isTrue)
.groupBy(creatorCommunityLike.creatorCommunity.id)
.fetch()
.associate {
it.get(creatorCommunityLike.creatorCommunity.id)!! to
(it.get(creatorCommunityLike.id.count())?.toInt() ?: 0)
}
}
private fun communityCommentCounts(postIds: List<Long>, viewerId: Long?, isContentCreator: Boolean): Map<Long, Int> {
if (postIds.isEmpty()) return emptyMap()
var where = creatorCommunityComment.creatorCommunity.id.`in`(postIds)
.and(creatorCommunityComment.isActive.isTrue)
.and(creatorCommunityComment.parent.isNull)
if (viewerId != null) {
where = where
.and(creatorCommunityComment.member.id.notIn(blockedMemberIdSubQuery(viewerId)))
.and(creatorCommunityComment.member.id.notIn(blockingMemberIdSubQuery(viewerId)))
}
if (!isContentCreator) {
where = where.and(
creatorCommunityComment.isSecret.isFalse.or(
viewerId?.let { creatorCommunityComment.member.id.eq(it) }
?: creatorCommunityComment.isSecret.isFalse
)
)
}
return queryFactory
.select(creatorCommunityComment.creatorCommunity.id, creatorCommunityComment.id.count())
.from(creatorCommunityComment)
.where(where)
.groupBy(creatorCommunityComment.creatorCommunity.id)
.fetch()
.associate {
it.get(creatorCommunityComment.creatorCommunity.id)!! to
(it.get(creatorCommunityComment.id.count())?.toInt() ?: 0)
}
}
private fun blockedMemberIdSubQuery(viewerId: Long) = QBlockMember("communityCommentViewerBlock").let { viewerBlock ->
queryFactory
.select(viewerBlock.blockedMember.id)
.from(viewerBlock)
.where(viewerBlock.member.id.eq(viewerId), viewerBlock.isActive.isTrue)
}
private fun blockingMemberIdSubQuery(viewerId: Long) = QBlockMember("communityCommentWriterBlock").let { writerBlock ->
queryFactory
.select(writerBlock.member.id)
.from(writerBlock)
.where(writerBlock.blockedMember.id.eq(viewerId), writerBlock.isActive.isTrue)
}
private fun canAccessPaidCommunityContent(
price: Int,
viewerId: Long?,
creatorId: Long,
existOrdered: Boolean
): Boolean {
return price <= 0 || viewerId == creatorId || existOrdered
}
private fun maskPaidCommunityContent(content: String, canAccessPaidContent: Boolean): String {
if (canAccessPaidContent) return content
val length = content.codePointCount(0, content.length)
val endIndex = if (length > 15) {
content.offsetByCodePoints(0, 15)
} else {
content.offsetByCodePoints(0, length / 2)
}
return content.substring(0, endIndex).plus("...")
}
private fun firstAudioDebutAt(creatorId: Long, now: LocalDateTime): LocalDateTime? { private fun firstAudioDebutAt(creatorId: Long, now: LocalDateTime): LocalDateTime? {
val firstThreeUploads = queryFactory val firstThreeUploads = queryFactory
.select(audioContent.releaseDate, audioContent.createdAt) .select(audioContent.releaseDate, audioContent.createdAt)
@@ -793,31 +606,6 @@ class DefaultCreatorChannelHomeQueryRepository(
return liveRoom.isAvailableJoinCreator.isTrue.or(liveRoom.member.id.eq(viewerId)) return liveRoom.isAvailableJoinCreator.isTrue.or(liveRoom.member.id.eq(viewerId))
} }
private fun adultCommunityCondition(canViewAdultContent: Boolean): BooleanExpression? {
return if (canViewAdultContent) null else creatorCommunity.isAdult.isFalse
}
private fun fixedNoticeCondition(isFixed: Boolean): BooleanExpression? {
return if (isFixed) creatorCommunity.fixedAt.isNotNull else null
}
private fun visibleCommunityPostCondition(viewerId: Long?): BooleanExpression {
val activePost = creatorCommunity.isActive.isTrue
if (viewerId == null) return activePost
return activePost.or(
queryFactory
.select(useCan.id)
.from(useCan)
.where(
useCan.member.id.eq(viewerId),
useCan.communityPost.id.eq(creatorCommunity.id),
useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST),
useCan.isRefund.isFalse
)
.exists()
)
}
private fun adultSeriesCondition(canViewAdultContent: Boolean): BooleanExpression? { private fun adultSeriesCondition(canViewAdultContent: Boolean): BooleanExpression? {
return if (canViewAdultContent) null else series.isAdult.isFalse return if (canViewAdultContent) null else series.isAdult.isFalse
} }

View File

@@ -34,14 +34,6 @@ interface CreatorChannelHomeQueryPort {
limit: Int = 8 limit: Int = 8
): List<CreatorChannelDonationRecord> ): List<CreatorChannelDonationRecord>
fun findCommunityPosts(
creatorId: Long,
viewerId: Long?,
isFixed: Boolean,
canViewAdultContent: Boolean,
limit: Int = 3
): List<CreatorChannelCommunityPostRecord>
fun findSchedules( fun findSchedules(
creatorId: Long, creatorId: Long,
now: LocalDateTime, now: LocalDateTime,
@@ -140,21 +132,6 @@ data class CreatorChannelSeriesRecord(
val isOriginal: Boolean val isOriginal: Boolean
) )
data class CreatorChannelCommunityPostRecord(
val postId: Long,
val creatorId: Long,
val creatorNickname: String,
val creatorProfilePath: String?,
val imagePath: String?,
val audioPath: String?,
val content: String,
val price: Int,
val date: LocalDateTime,
val existOrdered: Boolean,
val likeCount: Int,
val commentCount: Int
)
data class CreatorChannelFanTalkSummaryRecord( data class CreatorChannelFanTalkSummaryRecord(
val totalCount: Int, val totalCount: Int,
val latestFanTalk: CreatorChannelFanTalkRecord? val latestFanTalk: CreatorChannelFanTalkRecord?

View File

@@ -2,8 +2,6 @@ package kr.co.vividnext.sodalive.v2.creator.channel.home.adapter.out.persistence
import com.querydsl.jpa.impl.JPAQueryFactory import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre
import kr.co.vividnext.sodalive.can.use.CanUsage
import kr.co.vividnext.sodalive.can.use.UseCan
import kr.co.vividnext.sodalive.chat.character.ChatCharacter import kr.co.vividnext.sodalive.chat.character.ChatCharacter
import kr.co.vividnext.sodalive.configs.QueryDslConfig import kr.co.vividnext.sodalive.configs.QueryDslConfig
import kr.co.vividnext.sodalive.content.AudioContent import kr.co.vividnext.sodalive.content.AudioContent
@@ -15,9 +13,6 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.Series
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.ChannelDonationMessage import kr.co.vividnext.sodalive.explorer.profile.channelDonation.ChannelDonationMessage
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.CreatorCommunityComment
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.CreatorCommunityLike
import kr.co.vividnext.sodalive.live.room.GenderRestriction import kr.co.vividnext.sodalive.live.room.GenderRestriction
import kr.co.vividnext.sodalive.live.room.LiveRoom import kr.co.vividnext.sodalive.live.room.LiveRoom
import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisit import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisit
@@ -143,14 +138,10 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
"audio queries in this repository must project required columns" "audio queries in this repository must project required columns"
) )
assertFalse(source.contains(".selectFrom(channelDonationMessage)"), "donation query must project required columns") assertFalse(source.contains(".selectFrom(channelDonationMessage)"), "donation query must project required columns")
assertFalse(source.contains(".selectFrom(creatorCommunity)"), "community query must project required columns")
assertFalse(source.contains(".selectFrom(series)"), "series query must project required columns") assertFalse(source.contains(".selectFrom(series)"), "series query must project required columns")
assertFalse(source.contains(".select(series)"), "series query must not fetch full Series entity") assertFalse(source.contains(".select(series)"), "series query must not fetch full Series entity")
assertFalse(source.contains(".selectFrom(creatorCheers)"), "fan talk latest query must project required columns") assertFalse(source.contains(".selectFrom(creatorCheers)"), "fan talk latest query must project required columns")
assertFalse(source.contains(".fetch()\n .size"), "counts must use DB count instead of fetching ids") assertFalse(source.contains(".fetch()\n .size"), "counts must use DB count instead of fetching ids")
assertFalse(source.contains("existsCommunityOrder("), "community orders must be bulk calculated")
assertFalse(source.contains("countCommunityLikes("), "community likes must be bulk calculated")
assertFalse(source.contains("countCommunityComments("), "community comments must be bulk calculated")
assertFalse(source.contains("publishedSeriesContents("), "series contents must be bulk calculated") assertFalse(source.contains("publishedSeriesContents("), "series contents must be bulk calculated")
assertFalse(source.contains("hasNewSeriesContent("), "series new flags must be bulk calculated") assertFalse(source.contains("hasNewSeriesContent("), "series new flags must be bulk calculated")
assertTrue( assertTrue(
@@ -214,17 +205,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
saveOrder(viewer, creator, latestAudio, OrderType.KEEP) saveOrder(viewer, creator, latestAudio, OrderType.KEEP)
saveOrder(viewer, creator, listAudio, OrderType.RENTAL, endDate = now.plusDays(1)) saveOrder(viewer, creator, listAudio, OrderType.RENTAL, endDate = now.plusDays(1))
val donation = saveDonation(creator, donor, 500, now.minusHours(3), additionalMessage = "integrated thanks") val donation = saveDonation(creator, donor, 500, now.minusHours(3), additionalMessage = "integrated thanks")
val notice = saveCommunity(creator, isFixed = true, fixedAt = now.minusHours(4), price = 0)
val community = saveCommunity(
creator,
isFixed = false,
price = 100,
imagePath = "community.png",
audioPath = "community.mp3"
)
saveCommunityOrder(viewer, community, isRefund = false)
saveCommunityLike(viewer, community, isActive = true)
saveCommunityComment(viewer, community, isActive = true)
val fanTalk = saveCheers(fan, creator, "integrated fan talk", isActive = true, now.minusMinutes(30)) val fanTalk = saveCheers(fan, creator, "integrated fan talk", isActive = true, now.minusMinutes(30))
saveVisit(currentLive, viewer) saveVisit(currentLive, viewer)
flushAndClear() flushAndClear()
@@ -247,7 +227,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
viewerId = viewer.id!! viewerId = viewer.id!!
) )
val donations = repository.findChannelDonations(creator.id!!, viewer.id!!, now, limit = 8) val donations = repository.findChannelDonations(creator.id!!, viewer.id!!, now, limit = 8)
val notices = repository.findCommunityPosts(creator.id!!, viewer.id!!, isFixed = true, false, limit = 3)
val schedules = repository.findSchedules( val schedules = repository.findSchedules(
creator.id!!, creator.id!!,
now, now,
@@ -266,7 +245,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
limit = 9 limit = 9
) )
val seriesRecords = repository.findSeries(creator.id!!, viewer.id!!, now, false, ContentType.ALL, limit = 8) val seriesRecords = repository.findSeries(creator.id!!, viewer.id!!, now, false, ContentType.ALL, limit = 8)
val communities = repository.findCommunityPosts(creator.id!!, viewer.id!!, isFixed = false, false, limit = 3)
val fanTalkSummary = repository.findFanTalkSummary(creator.id!!, viewer.id!!) val fanTalkSummary = repository.findFanTalkSummary(creator.id!!, viewer.id!!)
val activity = repository.findActivity(creator.id!!, now) val activity = repository.findActivity(creator.id!!, now)
val sns = repository.findSns(creator.id!!) val sns = repository.findSns(creator.id!!)
@@ -279,7 +257,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
assertFalse(latestAudioRecord.isRented) assertFalse(latestAudioRecord.isRented)
assertEquals(listOf(donation.can), donations.map { it.can }) assertEquals(listOf(donation.can), donations.map { it.can })
assertEquals("integrated thanks", donations.single().message) assertEquals("integrated thanks", donations.single().message)
assertEquals(listOf(notice.id), notices.map { it.postId })
assertEquals(listOf(liveSchedule.id, audioSchedule.id), schedules.map { it.targetId }) assertEquals(listOf(liveSchedule.id, audioSchedule.id), schedules.map { it.targetId })
assertEquals(listOf(CreatorActivityType.LIVE, CreatorActivityType.AUDIO), schedules.map { it.type }) assertEquals(listOf(CreatorActivityType.LIVE, CreatorActivityType.AUDIO), schedules.map { it.type })
assertEquals(listOf(listAudio.id, firstAudio.id), audioContents.map { it.audioContentId }) assertEquals(listOf(listAudio.id, firstAudio.id), audioContents.map { it.audioContentId })
@@ -287,10 +264,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
assertEquals(listOf(true, false), audioContents.map { it.isRented }) assertEquals(listOf(true, false), audioContents.map { it.isRented })
assertEquals(listOf(series.id), seriesRecords.map { it.seriesId }) assertEquals(listOf(series.id), seriesRecords.map { it.seriesId })
assertEquals(true, seriesRecords.single().isOriginal) assertEquals(true, seriesRecords.single().isOriginal)
assertEquals(listOf(community.id), communities.map { it.postId })
assertEquals(1, communities.single().likeCount)
assertEquals(1, communities.single().commentCount)
assertTrue(communities.single().existOrdered)
assertEquals(1, fanTalkSummary.totalCount) assertEquals(1, fanTalkSummary.totalCount)
assertEquals(fanTalk.id, fanTalkSummary.latestFanTalk!!.fanTalkId) assertEquals(fanTalk.id, fanTalkSummary.latestFanTalk!!.fanTalkId)
assertEquals(now.minusDays(3), activity.debutDate) assertEquals(now.minusDays(3), activity.debutDate)
@@ -642,54 +615,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
assertTrue(records.single().isFirstContent) assertTrue(records.single().isFirstContent)
} }
@Test
@DisplayName("채널 후원, 공지, 커뮤니티, 팬 Talk는 기존 전체보기 의미에 맞는 요약을 조회한다")
fun shouldFindDonationsCommunitiesAndFanTalkSummary() {
val now = LocalDateTime.of(2026, 6, 12, 12, 0)
val creator = saveMember("community-creator", MemberRole.CREATOR)
val viewer = saveMember("community-viewer", MemberRole.USER)
val donor = saveMember("community-donor", MemberRole.USER)
val blockedWriter = saveMember("blocked-talk-writer", MemberRole.USER)
val donation = saveDonation(creator, donor, 300, now.minusDays(1))
saveDonation(creator, donor, 100, now.minusMonths(1))
val notice = saveCommunity(creator, isFixed = true, fixedAt = now.minusHours(1), price = 0)
saveCommunity(creator, isFixed = true, fixedAt = null, price = 0)
val post = saveCommunity(creator, isFixed = false, price = 100, imagePath = "community.png", audioPath = "community.mp3")
saveCommunityLike(viewer, post, isActive = true)
saveCommunityComment(viewer, post, isActive = true)
saveCommunityOrder(viewer, post, isRefund = false)
val latestTalk = saveCheers(viewer, creator, "latest", isActive = true, now.minusMinutes(1))
saveCheers(blockedWriter, creator, "blocked", isActive = true, now)
saveBlock(viewer, blockedWriter)
flushAndClear()
val donations = repository.findChannelDonations(creator.id!!, viewer.id!!, now, limit = 8)
val notices = repository.findCommunityPosts(
creator.id!!,
viewer.id!!,
isFixed = true,
canViewAdultContent = false,
limit = 3
)
val posts = repository.findCommunityPosts(
creator.id!!,
viewer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
val fanTalk = repository.findFanTalkSummary(creator.id!!, viewer.id!!)
assertEquals(listOf(donation.can), donations.map { it.can })
assertEquals(listOf(notice.id), notices.map { it.postId })
assertEquals(listOf(post.id), posts.map { it.postId })
assertEquals(1, posts.single().likeCount)
assertEquals(1, posts.single().commentCount)
assertTrue(posts.single().existOrdered)
assertEquals(1, fanTalk.totalCount)
assertEquals(latestTalk.id, fanTalk.latestFanTalk!!.fanTalkId)
}
@Test @Test
@DisplayName("팬 Talk 요약은 활성 최상위 글 전체 개수와 최신 1개만 조회한다") @DisplayName("팬 Talk 요약은 활성 최상위 글 전체 개수와 최신 1개만 조회한다")
fun shouldSummarizeFanTalkWithTotalCountAndLatestOnly() { fun shouldSummarizeFanTalkWithTotalCountAndLatestOnly() {
@@ -709,181 +634,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
assertEquals("latest", summary.latestFanTalk!!.content) assertEquals("latest", summary.latestFanTalk!!.content)
} }
@Test
@DisplayName("커뮤니티는 성인 정책과 작성자 본인의 구매 여부 의미를 반영한다")
fun shouldFilterAdultCommunityAndTreatCreatorAsOrdered() {
val creator = saveMember("adult-community-creator", MemberRole.CREATOR)
val visiblePost = saveCommunity(creator, isFixed = false, price = 100)
saveCommunity(creator, isFixed = false, price = 100, isAdult = true)
flushAndClear()
val viewerPosts = repository.findCommunityPosts(
creator.id!!,
viewerId = creator.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals(listOf(visiblePost.id), viewerPosts.map { it.postId })
assertTrue(viewerPosts.single().existOrdered)
}
@Test
@DisplayName("유료 커뮤니티는 비구매자에게 본문을 축약하고 오디오를 숨긴다")
fun shouldMaskPaidCommunityContentAndAudioForNonBuyer() {
val creator = saveMember("paid-community-creator", MemberRole.CREATOR)
val viewer = saveMember("paid-community-viewer", MemberRole.USER)
val content = "12345678901234567890"
saveCommunity(
creator,
isFixed = false,
price = 100,
audioPath = "paid-audio.mp3",
content = content
)
flushAndClear()
val posts = repository.findCommunityPosts(
creator.id!!,
viewerId = viewer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals("123456789012345...", posts.single().content)
assertEquals(null, posts.single().audioPath)
assertFalse(posts.single().existOrdered)
}
@Test
@DisplayName("유료 커뮤니티는 구매자와 작성자에게 본문과 오디오를 노출한다")
fun shouldExposePaidCommunityContentAndAudioForBuyerAndCreator() {
val creator = saveMember("paid-community-owner", MemberRole.CREATOR)
val buyer = saveMember("paid-community-buyer", MemberRole.USER)
val post = saveCommunity(
creator,
isFixed = false,
price = 100,
audioPath = "paid-visible.mp3",
content = "paid full content"
)
saveCommunityOrder(buyer, post, isRefund = false)
flushAndClear()
val buyerPosts = repository.findCommunityPosts(
creator.id!!,
viewerId = buyer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
val creatorPosts = repository.findCommunityPosts(
creator.id!!,
viewerId = creator.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals("paid full content", buyerPosts.single().content)
assertEquals("paid-visible.mp3", buyerPosts.single().audioPath)
assertTrue(buyerPosts.single().existOrdered)
assertEquals("paid full content", creatorPosts.single().content)
assertEquals("paid-visible.mp3", creatorPosts.single().audioPath)
assertTrue(creatorPosts.single().existOrdered)
}
@Test
@DisplayName("구매한 유료 커뮤니티는 크리에이터가 삭제해도 구매자에게 조회된다")
fun shouldExposeDeletedPaidCommunityContentToBuyer() {
val creator = saveMember("deleted-paid-community-creator", MemberRole.CREATOR)
val buyer = saveMember("deleted-paid-community-buyer", MemberRole.USER)
val nonBuyer = saveMember("deleted-paid-community-non-buyer", MemberRole.USER)
val post = saveCommunity(
creator,
isFixed = false,
price = 100,
audioPath = "deleted-paid.mp3",
content = "deleted paid content",
isActive = false
)
saveCommunityOrder(buyer, post, isRefund = false)
flushAndClear()
val buyerPosts = repository.findCommunityPosts(
creator.id!!,
viewerId = buyer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
val nonBuyerPosts = repository.findCommunityPosts(
creator.id!!,
viewerId = nonBuyer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals(listOf(post.id), buyerPosts.map { it.postId })
assertEquals("deleted paid content", buyerPosts.single().content)
assertEquals("deleted-paid.mp3", buyerPosts.single().audioPath)
assertTrue(buyerPosts.single().existOrdered)
assertEquals(emptyList<Long>(), nonBuyerPosts.map { it.postId })
}
@Test
@DisplayName("커뮤니티 댓글 수는 기존 목록처럼 보이는 최상위 댓글만 계산한다")
fun shouldCountVisibleRootCommunityCommentsOnly() {
val creator = saveMember("comment-count-creator", MemberRole.CREATOR)
val viewer = saveMember("comment-count-viewer", MemberRole.USER)
val blockedWriter = saveMember("comment-count-blocked", MemberRole.USER)
val blockingWriter = saveMember("comment-count-blocking", MemberRole.USER)
val secretWriter = saveMember("comment-count-secret", MemberRole.USER)
val post = saveCommunity(creator, isFixed = false, price = 0)
val visibleRoot = saveCommunityComment(viewer, post, isActive = true)
saveCommunityComment(viewer, post, isActive = true, parent = visibleRoot)
saveCommunityComment(viewer, post, isActive = false)
saveCommunityComment(blockedWriter, post, isActive = true)
saveCommunityComment(blockingWriter, post, isActive = true)
saveCommunityComment(secretWriter, post, isActive = true, isSecret = true)
saveBlock(viewer, blockedWriter)
saveBlock(blockingWriter, viewer)
flushAndClear()
val posts = repository.findCommunityPosts(
creator.id!!,
viewerId = viewer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals(1, posts.single().commentCount)
}
@Test
@DisplayName("커뮤니티 댓글 수는 댓글 불가 게시글이면 기존 목록처럼 0으로 계산한다")
fun shouldReturnZeroCommentCountWhenCommunityCommentUnavailable() {
val creator = saveMember("comment-unavailable-creator", MemberRole.CREATOR)
val viewer = saveMember("comment-unavailable-viewer", MemberRole.USER)
val post = saveCommunity(creator, isFixed = false, price = 0, isCommentAvailable = false)
saveCommunityComment(viewer, post, isActive = true)
flushAndClear()
val posts = repository.findCommunityPosts(
creator.id!!,
viewerId = viewer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals(0, posts.single().commentCount)
}
@Test @Test
@DisplayName("채널 후원은 KST 기준 이번 달과 크리에이터의 비밀 후원 열람을 반영한다") @DisplayName("채널 후원은 KST 기준 이번 달과 크리에이터의 비밀 후원 열람을 반영한다")
fun shouldFindKstMonthDonationsAndExposeSecretDonationToCreator() { fun shouldFindKstMonthDonationsAndExposeSecretDonationToCreator() {
@@ -1421,34 +1171,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
return donation return donation
} }
private fun saveCommunity(
creator: Member,
isFixed: Boolean,
fixedAt: LocalDateTime? = null,
price: Int,
imagePath: String? = null,
audioPath: String? = null,
isAdult: Boolean = false,
isCommentAvailable: Boolean = true,
content: String = "community",
isActive: Boolean = true
): CreatorCommunity {
val community = CreatorCommunity(
content = content,
price = price,
isCommentAvailable = isCommentAvailable,
isAdult = isAdult,
audioPath = audioPath,
imagePath = imagePath,
isActive = isActive,
isFixed = isFixed,
fixedAt = fixedAt
)
community.member = creator
entityManager.persist(community)
return community
}
private fun saveAuth(member: Member, gender: Int): Auth { private fun saveAuth(member: Member, gender: Int): Auth {
val auth = Auth( val auth = Auth(
name = member.nickname, name = member.nickname,
@@ -1462,37 +1184,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
return auth return auth
} }
private fun saveCommunityLike(member: Member, community: CreatorCommunity, isActive: Boolean): CreatorCommunityLike {
val like = CreatorCommunityLike(isActive = isActive)
like.member = member
like.creatorCommunity = community
entityManager.persist(like)
return like
}
private fun saveCommunityComment(
member: Member,
community: CreatorCommunity,
isActive: Boolean,
isSecret: Boolean = false,
parent: CreatorCommunityComment? = null
): CreatorCommunityComment {
val comment = CreatorCommunityComment(comment = "comment", isSecret = isSecret, isActive = isActive)
comment.member = member
comment.creatorCommunity = community
comment.parent = parent
entityManager.persist(comment)
return comment
}
private fun saveCommunityOrder(member: Member, community: CreatorCommunity, isRefund: Boolean): UseCan {
val useCan = UseCan(CanUsage.PAID_COMMUNITY_POST, community.price, rewardCan = 0, isRefund = isRefund)
useCan.member = member
useCan.communityPost = community
entityManager.persist(useCan)
return useCan
}
private fun saveOrder( private fun saveOrder(
member: Member, member: Member,
creator: Member, creator: Member,