From 765c087af364b202ddfc4db727a4889a96354271 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 11 Apr 2026 21:05:27 +0900 Subject: [PATCH] =?UTF-8?q?feat(agent-read):=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=EC=97=90=EC=9D=B4=EC=A0=84=ED=8A=B8=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20=EA=B2=80=EC=83=89=20=EC=A1=B0=ED=9A=8C=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../read/AdminAgentReadQueryRepository.kt | 21 +++++++++++++ .../agent/read/AdminAgentReadService.kt | 8 +++++ .../read/AdminAgentReadQueryRepositoryTest.kt | 30 ++++++++++++++----- .../agent/read/AdminAgentReadServiceTest.kt | 28 +++++++++++++++++ 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepository.kt index f520a6f7..b9a241f1 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepository.kt @@ -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.Expressions 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.QMember import kr.co.vividnext.sodalive.member.QMember.member @@ -136,6 +138,25 @@ class AdminAgentReadQueryRepository( .fetch() } + fun searchAgentByNickname(searchWord: String, limit: Long): List { + 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 { return queryFactory .select(agentCreatorRelation.id.count()) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadService.kt index 02212238..08c5d5cd 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadService.kt @@ -1,5 +1,6 @@ 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.member.MemberRepository import kr.co.vividnext.sodalive.member.MemberRole @@ -44,6 +45,13 @@ class AdminAgentReadService( ) } + @Transactional(readOnly = true) + fun searchAgentByNickname(searchWord: String, size: Int = 20): List { + 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) fun getAssignedCreators(agentId: Long, offset: Long, limit: Long): GetAdminAgentAssignedCreatorResponse { validateAgent(agentId) diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepositoryTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepositoryTest.kt index 62540df0..6fb04fd8 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepositoryTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepositoryTest.kt @@ -81,6 +81,20 @@ class AdminAgentReadQueryRepositoryTest @Autowired constructor( 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 @DisplayName("특정 에이전트 소속 크리에이터 목록은 assignedAt을 포함해 현재 활성 구간만 반환한다") fun shouldGetAssignedCreatorsForAdminDetail() { @@ -101,15 +115,15 @@ class AdminAgentReadQueryRepositoryTest @Autowired constructor( assertEquals(now.minusDays(3), items.first().assignedAt) } - private fun saveMember(nickname: String, role: MemberRole): Member { - return memberRepository.saveAndFlush( - Member( - email = "$nickname@test.com", - password = "password", - nickname = nickname, - role = role - ) + private fun saveMember(nickname: String, role: MemberRole, isActive: Boolean = true): Member { + val member = Member( + email = "$nickname@test.com", + password = "password", + nickname = nickname, + role = role ) + member.isActive = isActive + return memberRepository.saveAndFlush(member) } private fun saveRelation(agent: Member, creator: Member, assignedAt: LocalDateTime, unassignedAt: LocalDateTime?) { diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadServiceTest.kt index 63cda4c1..0e37cce5 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadServiceTest.kt @@ -1,5 +1,6 @@ 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.member.Member import kr.co.vividnext.sodalive.member.MemberRepository @@ -41,6 +42,33 @@ class AdminAgentReadServiceTest { 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 @DisplayName("에이전트 목록 조회는 현재 월 summary 필드를 그대로 반환한다") fun shouldReturnAgentListWithCurrentMonthSummaryFields() {