feat(agent-calculate): 소속 크리에이터 응답에 프로필 이미지를 추가한다

This commit is contained in:
2026-04-13 18:51:33 +09:00
parent f740041dca
commit 2c19e4b76c
9 changed files with 75 additions and 11 deletions

View File

@@ -0,0 +1,20 @@
# 에이전트 소속 크리에이터 프로필 이미지 추가
- [x] `getAssignedCreators` 흐름과 기존 프로필 이미지 응답 패턴을 확인한다.
- [x] `GetAgentAssignedCreatorItem`에 프로필 이미지 URL 필드를 추가한다.
- [x] `AgentCalculateQueryRepository.getAssignedCreators` projection에 프로필 이미지 URL을 포함한다.
- [x] `AgentCalculateQueryRepositoryTest`에 프로필 이미지 URL 및 기본 이미지 fallback 검증을 추가한다.
- [x] `AgentCalculateServiceTest`, `AgentCalculateControllerTest` fixture와 검증을 갱신한다.
- [x] 정적 진단과 관련 테스트를 실행해 변경을 검증한다.
## 검증 기록
### 1차 구현
- 무엇을: 에이전트 소속 크리에이터 조회 응답에 프로필 이미지 URL 필드 추가 구현 및 검증
- 왜: 크리에이터 목록 응답에서 프로필 이미지를 함께 내려주기 위해
- 어떻게:
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest"` 실행 시 `profileImageUrl``cloudFrontHost` 관련 컴파일 실패를 확인해 red 단계 검증
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
- `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest.shouldGetAssignedCreatorsWithProfileImageUrl" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateControllerTest.shouldForwardAssignedCreatorsRequestToService"` 실행 결과 `BUILD SUCCESSFUL`
- Kotlin LSP 서버가 현재 환경에 없어 `lsp_diagnostics`는 수행 불가였고, 대신 compile/test/ktlint 결과로 정적 검증 대체

View File

@@ -15,6 +15,7 @@ import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
import kr.co.vividnext.sodalive.member.QMember.member
import kr.co.vividnext.sodalive.partner.agent.assignment.QAgentCreatorRelation.agentCreatorRelation
import kr.co.vividnext.sodalive.partner.agent.ratio.QAgentSettlementRatio.agentSettlementRatio
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
import javax.persistence.EntityManager
@@ -22,7 +23,10 @@ import javax.persistence.EntityManager
@Repository
class AgentCalculateQueryRepository(
private val queryFactory: JPAQueryFactory,
private val entityManager: EntityManager
private val entityManager: EntityManager,
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String
) {
fun getAssignedCreatorTotalCount(agentId: Long, currentTime: LocalDateTime): Int {
return queryFactory
@@ -46,7 +50,11 @@ class AgentCalculateQueryRepository(
.select(
QGetAgentAssignedCreatorItem(
agentCreatorRelation.creator.id,
agentCreatorRelation.creator.nickname
agentCreatorRelation.creator.nickname,
agentCreatorRelation.creator.profileImage
.coalesce("profile/default-profile.png")
.prepend("/")
.prepend(cloudFrontHost)
)
)
.from(agentCreatorRelation)

View File

@@ -9,5 +9,6 @@ data class GetAgentAssignedCreatorResponse(
data class GetAgentAssignedCreatorItem @QueryProjection constructor(
val creatorId: Long,
val creatorNickname: String
val creatorNickname: String,
val profileImageUrl: String
)

View File

@@ -58,12 +58,14 @@ class AdminAgentReadCurrentMonthListSummaryTest @Autowired constructor(
private val snapshotRepository: AgentSettlementSnapshotRepository,
private val entityManager: EntityManager
) {
private val cloudFrontHost = "https://cdn.test"
private lateinit var repository: AdminAgentReadQueryRepository
private lateinit var calculateService: AgentCalculateService
@BeforeEach
fun setup() {
val calculateQueryRepository = AgentCalculateQueryRepository(queryFactory, entityManager)
val calculateQueryRepository = AgentCalculateQueryRepository(queryFactory, entityManager, cloudFrontHost)
repository = AdminAgentReadQueryRepository(queryFactory, calculateQueryRepository)
calculateService = AgentCalculateService(calculateQueryRepository, snapshotRepository)
registerMysqlDateFunctions()

View File

@@ -55,6 +55,8 @@ class AdminAgentReadParityTest @Autowired constructor(
private val snapshotRepository: AgentSettlementSnapshotRepository,
private val entityManager: EntityManager
) {
private val cloudFrontHost = "https://cdn.test"
private var seededAgentId: Long = 0L
private lateinit var agentService: AgentCalculateService
private lateinit var adminService: AdminAgentReadService
@@ -62,7 +64,7 @@ class AdminAgentReadParityTest @Autowired constructor(
@BeforeEach
fun setup() {
registerMysqlDateFunctions()
val queryRepository = AgentCalculateQueryRepository(queryFactory, entityManager)
val queryRepository = AgentCalculateQueryRepository(queryFactory, entityManager, cloudFrontHost)
val adminQueryRepository = AdminAgentReadQueryRepository(queryFactory, queryRepository)
agentService = AgentCalculateService(queryRepository, snapshotRepository)
adminService = AdminAgentReadService(adminQueryRepository, memberRepository, agentService)

View File

@@ -28,13 +28,15 @@ class AdminAgentReadQueryRepositoryTest @Autowired constructor(
private val relationRepository: AgentCreatorRelationRepository,
private val entityManager: EntityManager
) {
private val cloudFrontHost = "https://cdn.test"
private lateinit var repository: AdminAgentReadQueryRepository
@BeforeEach
fun setup() {
repository = AdminAgentReadQueryRepository(
queryFactory,
AgentCalculateQueryRepository(queryFactory, entityManager)
AgentCalculateQueryRepository(queryFactory, entityManager, cloudFrontHost)
)
}

View File

@@ -43,7 +43,8 @@ class AgentCalculateControllerTest {
items = listOf(
GetAgentAssignedCreatorItem(
creatorId = 21L,
creatorNickname = "creator-a"
creatorNickname = "creator-a",
profileImageUrl = "https://cdn.test/profile/creator-a.png"
)
)
)
@@ -58,6 +59,7 @@ class AgentCalculateControllerTest {
assertEquals(true, apiResponse.success)
assertEquals(1, apiResponse.data!!.totalCount)
assertEquals(21L, apiResponse.data!!.items[0].creatorId)
assertEquals("https://cdn.test/profile/creator-a.png", apiResponse.data!!.items[0].profileImageUrl)
Mockito.verify(service).getAssignedCreators(agentId = 7L, offset = 10L, limit = 5L)
}

View File

@@ -56,13 +56,15 @@ class AgentCalculateQueryRepositoryTest @Autowired constructor(
private val useCanCalculateRepository: UseCanCalculateRepository,
private val entityManager: EntityManager
) {
private val cloudFrontHost = "https://cdn.test"
private lateinit var repository: AgentCalculateQueryRepository
private lateinit var service: AgentCalculateService
@BeforeEach
fun setup() {
registerMysqlDateFunctions()
repository = AgentCalculateQueryRepository(queryFactory, entityManager)
repository = AgentCalculateQueryRepository(queryFactory, entityManager, cloudFrontHost)
service = AgentCalculateService(repository, snapshotRepository)
}
@@ -110,6 +112,28 @@ class AgentCalculateQueryRepositoryTest @Autowired constructor(
assertEquals(listOf(endingFutureCreator.id, currentCreator.id), items.map { it.creatorId })
}
@Test
@DisplayName("소속 크리에이터 목록 조회는 프로필 이미지 URL과 기본 이미지를 함께 반환한다")
fun shouldGetAssignedCreatorsWithProfileImageUrl() {
val agent = saveMember("agent-image", MemberRole.AGENT)
val creatorWithImage = saveMember("creator-image", MemberRole.CREATOR, profileImage = "profile/creator-image.png")
val creatorWithoutImage = saveMember("creator-default", MemberRole.CREATOR)
val currentTime = LocalDateTime.of(2026, 2, 20, 12, 0, 0)
saveRelation(agent, creatorWithImage)
saveRelation(agent, creatorWithoutImage)
val items = repository.getAssignedCreators(agent.id!!, offset = 0, limit = 10, currentTime = currentTime)
assertEquals(
listOf(
"$cloudFrontHost/profile/default-profile.png",
"$cloudFrontHost/profile/creator-image.png"
),
items.map { it.profileImageUrl }
)
}
@Test
@DisplayName("라이브 크리에이터별 조회는 소속된 크리에이터만 집계한다")
fun shouldGetLiveSummaryRowsOnlyForAssignedCreators() {
@@ -683,13 +707,14 @@ class AgentCalculateQueryRepositoryTest @Autowired constructor(
assertEquals(zeroTotal, repository.getCalculateContentDonationByCreatorTotal(startDate, endDate, agent.id!!))
}
private fun saveMember(nickname: String, role: MemberRole): Member {
private fun saveMember(nickname: String, role: MemberRole, profileImage: String? = null): Member {
return memberRepository.saveAndFlush(
Member(
email = "$nickname@test.com",
password = "password",
nickname = nickname,
role = role
role = role,
profileImage = profileImage
)
)
}

View File

@@ -41,7 +41,8 @@ class AgentCalculateServiceTest {
val items = listOf(
GetAgentAssignedCreatorItem(
creatorId = 21L,
creatorNickname = "creator-a"
creatorNickname = "creator-a",
profileImageUrl = "https://cdn.test/profile/creator-a.png"
)
)
@@ -64,6 +65,7 @@ class AgentCalculateServiceTest {
assertEquals(1, response.totalCount)
assertEquals(21L, response.items[0].creatorId)
assertEquals("https://cdn.test/profile/creator-a.png", response.items[0].profileImageUrl)
Mockito.verify(repository).getAssignedCreatorTotalCount(
Mockito.eq(7L),
Mockito.any(LocalDateTime::class.java) ?: LocalDateTime.now()