From 38595ee88a052a56df53e2256c6304df496204e6 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 27 Jun 2026 00:05:49 +0900 Subject: [PATCH] =?UTF-8?q?feat(home-live):=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=20=EC=B6=94=EC=B2=9C=20=EC=A1=B0=ED=9A=8C=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=A5=BC=20=ED=99=95=EC=9E=A5=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...efaultHomeRecommendationQueryRepository.kt | 5 +- .../port/out/HomeRecommendationQueryPort.kt | 5 +- ...ltHomeRecommendationQueryRepositoryTest.kt | 71 ++++++++++++++++++- .../HomeRecommendationQueryServiceTest.kt | 22 +++++- 4 files changed, 97 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt index b73990c8..3a2e00d2 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt @@ -62,7 +62,10 @@ class DefaultHomeRecommendationQueryRepository( HomeLiveRecommendationRecord::class.java, liveRoom.id, member.nickname, - member.profileImage + member.profileImage, + liveRoom.title, + liveRoom.price, + liveRoom.beginDateTime ) ) .from(liveRoom) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt index ba010547..25afd146 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt @@ -80,7 +80,10 @@ interface HomeRecommendationQueryPort { data class HomeLiveRecommendationRecord( val liveRoomId: Long, val creatorNickname: String, - val creatorProfileImage: String? + val creatorProfileImage: String?, + val title: String, + val price: Int, + val beginDateTime: LocalDateTime ) data class HomeBannerRecommendationRecord( diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt index df6ea8a5..38c1a8a2 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt @@ -106,6 +106,66 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor( assertEquals(listOf(oldest.id), page1.map { it.liveRoomId }) } + @Test + @DisplayName("라이브 추천 조회는 진행 중인 라이브의 제목, 가격, 시작 시간을 함께 반환한다") + fun shouldReturnLiveTitlePriceAndBeginDateTimeForOnAirLiveQuery() { + val beginDateTime = LocalDateTime.of(2026, 6, 26, 12, 30) + val viewer = saveMember("on-air-viewer", MemberRole.USER) + val creator = saveMember("on-air-creator", MemberRole.CREATOR) + val live = saveLiveRoom( + creator = creator, + beginDateTime = beginDateTime, + channelName = "channel", + title = "paid live", + price = 30 + ) + flushAndClear() + + val lives = repository.findLiveRecommendations( + offset = 0, + limit = 1, + memberId = viewer.id, + includeAdultLives = true + ) + + assertEquals(listOf(live.id), lives.map { it.liveRoomId }) + assertEquals("paid live", lives.single().title) + assertEquals(30, lives.single().price) + assertEquals(beginDateTime, lives.single().beginDateTime) + } + + @Test + @DisplayName("진행 중 라이브 조회 정책은 활성 방송자, 채널명, 활성 라이브, 성인 노출, 차단 관계를 적용한다") + fun shouldApplyOnAirLiveVisibilityPolicy() { + val baseAt = LocalDateTime.of(2026, 6, 26, 12, 0) + val viewer = saveMember("policy-viewer", MemberRole.USER) + val visibleCreator = saveMember("policy-visible", MemberRole.CREATOR) + val inactiveCreator = saveMember("policy-inactive", MemberRole.CREATOR, isActive = false) + val viewerBlockedCreator = saveMember("policy-viewer-blocked", MemberRole.CREATOR) + val creatorBlockedViewer = saveMember("policy-creator-blocked", MemberRole.CREATOR) + val olderVisibleLive = saveLiveRoom(visibleCreator, baseAt, channelName = "older") + val newerVisibleLive = saveLiveRoom(visibleCreator, baseAt.plusMinutes(1), channelName = "newer") + saveLiveRoom(inactiveCreator, baseAt.plusMinutes(6), channelName = "inactive-creator") + saveLiveRoom(visibleCreator, baseAt.plusMinutes(5), channelName = "inactive-live", isActive = false) + saveLiveRoom(visibleCreator, baseAt.plusMinutes(5), channelName = null) + saveLiveRoom(visibleCreator, baseAt.plusMinutes(4), channelName = "") + saveLiveRoom(visibleCreator, baseAt.plusMinutes(3), channelName = "adult", isAdult = true) + saveLiveRoom(viewerBlockedCreator, baseAt.plusMinutes(2), channelName = "viewer-blocked") + saveLiveRoom(creatorBlockedViewer, baseAt.plusMinutes(2), channelName = "creator-blocked") + saveBlock(viewer, viewerBlockedCreator) + saveBlock(creatorBlockedViewer, viewer) + flushAndClear() + + val lives = repository.findLiveRecommendations( + offset = 0, + limit = 10, + memberId = viewer.id, + includeAdultLives = false + ) + + assertEquals(listOf(newerVisibleLive.id, olderVisibleLive.id), lives.map { it.liveRoomId }) + } + @Test @DisplayName("라이브 추천은 회원과 크리에이터의 양방향 차단 관계를 제외한다") fun shouldExcludeBidirectionalBlockedCreatorsFromLiveRecommendations() { @@ -2082,17 +2142,22 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor( creator: Member, beginDateTime: LocalDateTime, channelName: String?, - isAdult: Boolean = false + isAdult: Boolean = false, + title: String = "live-${creator.nickname}-$beginDateTime", + price: Int = 0, + isActive: Boolean = true ): LiveRoom { val room = LiveRoom( - title = "live-${creator.nickname}-$beginDateTime", + title = title, notice = "notice", beginDateTime = beginDateTime, numberOfPeople = 0, - isAdult = isAdult + isAdult = isAdult, + price = price ) room.member = creator room.channelName = channelName + room.isActive = isActive entityManager.persist(room) return room } diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt index e3609e43..10da843e 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt @@ -65,6 +65,23 @@ class HomeRecommendationQueryServiceTest { assertEquals(port.liveRecommendations, recommendations) } + @Test + @DisplayName("라이브 추천 조회는 paging과 성인 노출 여부를 조회 포트에 그대로 위임한다") + fun shouldDelegateLiveRecommendationQueryWithPagingAndAdultFlag() { + val recommendations = service.findLiveRecommendations( + offset = 40, + limit = 21, + memberId = 100L, + includeAdultLives = true + ) + + assertEquals(40, port.liveOffset) + assertEquals(21, port.liveLimit) + assertEquals(100L, port.liveMemberId) + assertEquals(true, port.liveIncludeAdultLives) + assertEquals(port.liveRecommendations, recommendations) + } + @Test @DisplayName("홈 배너 추천은 기본 20개를 활성 배너 조회 포트에 위임한다") fun shouldFindHomeBannersWithDefaultLimit() { @@ -628,7 +645,10 @@ class HomeRecommendationQueryServiceTest { HomeLiveRecommendationRecord( liveRoomId = 1L, creatorNickname = "creator", - creatorProfileImage = "profile.png" + creatorProfileImage = "profile.png", + title = "live", + price = 10, + beginDateTime = LocalDateTime.of(2026, 6, 26, 12, 30) ) ) val banners = listOf(