feat(agent-read): 관리자 에이전트 닉네임 검색 조회를 추가한다

This commit is contained in:
2026-04-11 21:05:27 +09:00
parent 535f5d16cc
commit 765c087af3
4 changed files with 79 additions and 8 deletions

View File

@@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.admin.partner.agent.read
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.admin.member.AdminSimpleMemberResponse
import kr.co.vividnext.sodalive.admin.member.QAdminSimpleMemberResponse
import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.QMember import kr.co.vividnext.sodalive.member.QMember
import kr.co.vividnext.sodalive.member.QMember.member import kr.co.vividnext.sodalive.member.QMember.member
@@ -136,6 +138,25 @@ class AdminAgentReadQueryRepository(
.fetch() .fetch()
} }
fun searchAgentByNickname(searchWord: String, limit: Long): List<AdminSimpleMemberResponse> {
return queryFactory
.select(
QAdminSimpleMemberResponse(
member.id,
member.nickname
)
)
.from(member)
.where(
member.role.eq(MemberRole.AGENT)
.and(member.nickname.contains(searchWord))
.and(member.isActive.isTrue)
)
.orderBy(member.id.desc())
.limit(limit)
.fetch()
}
fun getAssignedCreatorTotalCount(agentId: Long, currentTime: LocalDateTime): Int { fun getAssignedCreatorTotalCount(agentId: Long, currentTime: LocalDateTime): Int {
return queryFactory return queryFactory
.select(agentCreatorRelation.id.count()) .select(agentCreatorRelation.id.count())

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.admin.partner.agent.read package kr.co.vividnext.sodalive.admin.partner.agent.read
import kr.co.vividnext.sodalive.admin.member.AdminSimpleMemberResponse
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.MemberRepository import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.MemberRole
@@ -44,6 +45,13 @@ class AdminAgentReadService(
) )
} }
@Transactional(readOnly = true)
fun searchAgentByNickname(searchWord: String, size: Int = 20): List<AdminSimpleMemberResponse> {
if (searchWord.length < 2) throw SodaException(messageKey = "admin.member.search_word_min_length")
val limit = if (size <= 0) 20 else size
return queryRepository.searchAgentByNickname(searchWord = searchWord, limit = limit.toLong())
}
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun getAssignedCreators(agentId: Long, offset: Long, limit: Long): GetAdminAgentAssignedCreatorResponse { fun getAssignedCreators(agentId: Long, offset: Long, limit: Long): GetAdminAgentAssignedCreatorResponse {
validateAgent(agentId) validateAgent(agentId)

View File

@@ -81,6 +81,20 @@ class AdminAgentReadQueryRepositoryTest @Autowired constructor(
assertEquals(listOf(null, "agent-search"), items.map { it.currentAgentNickname }) assertEquals(listOf(null, "agent-search"), items.map { it.currentAgentNickname })
} }
@Test
@DisplayName("에이전트 닉네임 검색은 활성 AGENT만 id와 nickname으로 반환한다")
fun shouldSearchActiveAgentsByNickname() {
val matchedAgent = saveMember("agent-alpha", MemberRole.AGENT)
saveMember("agent-beta", MemberRole.AGENT, isActive = false)
saveMember("creator-agent", MemberRole.CREATOR)
val items = repository.searchAgentByNickname(searchWord = "agent", limit = 20)
assertEquals(1, items.size)
assertEquals(matchedAgent.id, items.first().id)
assertEquals("agent-alpha", items.first().nickname)
}
@Test @Test
@DisplayName("특정 에이전트 소속 크리에이터 목록은 assignedAt을 포함해 현재 활성 구간만 반환한다") @DisplayName("특정 에이전트 소속 크리에이터 목록은 assignedAt을 포함해 현재 활성 구간만 반환한다")
fun shouldGetAssignedCreatorsForAdminDetail() { fun shouldGetAssignedCreatorsForAdminDetail() {
@@ -101,15 +115,15 @@ class AdminAgentReadQueryRepositoryTest @Autowired constructor(
assertEquals(now.minusDays(3), items.first().assignedAt) assertEquals(now.minusDays(3), items.first().assignedAt)
} }
private fun saveMember(nickname: String, role: MemberRole): Member { private fun saveMember(nickname: String, role: MemberRole, isActive: Boolean = true): Member {
return memberRepository.saveAndFlush( val member = Member(
Member(
email = "$nickname@test.com", email = "$nickname@test.com",
password = "password", password = "password",
nickname = nickname, nickname = nickname,
role = role role = role
) )
) member.isActive = isActive
return memberRepository.saveAndFlush(member)
} }
private fun saveRelation(agent: Member, creator: Member, assignedAt: LocalDateTime, unassignedAt: LocalDateTime?) { private fun saveRelation(agent: Member, creator: Member, assignedAt: LocalDateTime, unassignedAt: LocalDateTime?) {

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.admin.partner.agent.read package kr.co.vividnext.sodalive.admin.partner.agent.read
import kr.co.vividnext.sodalive.admin.member.AdminSimpleMemberResponse
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository import kr.co.vividnext.sodalive.member.MemberRepository
@@ -41,6 +42,33 @@ class AdminAgentReadServiceTest {
assertEquals("admin.member.search_word_min_length", exception.messageKey) assertEquals("admin.member.search_word_min_length", exception.messageKey)
} }
@Test
@DisplayName("에이전트 닉네임 검색은 두 글자 미만 검색어를 거부한다")
fun shouldRejectTooShortAgentSearchWord() {
val exception = assertThrows(SodaException::class.java) {
service.searchAgentByNickname(searchWord = "a", size = 20)
}
assertEquals("admin.member.search_word_min_length", exception.messageKey)
}
@Test
@DisplayName("에이전트 닉네임 검색은 size가 0 이하이면 20으로 보정한다")
fun shouldDefaultAgentSearchSizeToTwenty() {
val expected = listOf(
AdminSimpleMemberResponse(
id = 11L,
nickname = "agent-a"
)
)
Mockito.`when`(queryRepository.searchAgentByNickname(searchWord = "agent", limit = 20L)).thenReturn(expected)
val actual = service.searchAgentByNickname(searchWord = "agent", size = 0)
assertEquals(expected, actual)
Mockito.verify(queryRepository).searchAgentByNickname(searchWord = "agent", limit = 20L)
}
@Test @Test
@DisplayName("에이전트 목록 조회는 현재 월 summary 필드를 그대로 반환한다") @DisplayName("에이전트 목록 조회는 현재 월 summary 필드를 그대로 반환한다")
fun shouldReturnAgentListWithCurrentMonthSummaryFields() { fun shouldReturnAgentListWithCurrentMonthSummaryFields() {