Skip to content

Commit 02769ce

Browse files
jungdongha정동하
andauthored
tool, test 작성 (#86)
Co-authored-by: 정동하 <[email protected]>
1 parent 5ee84fa commit 02769ce

File tree

5 files changed

+221
-1
lines changed

5 files changed

+221
-1
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,7 @@ dump.rdb
7272

7373
### Coverage reports ###
7474
build/reports/
75-
coverage/
75+
coverage/
76+
77+
# Test file
78+
index.html
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.back.koreaTravelGuide.domain.ai.aiChat.tool
2+
3+
import com.back.koreaTravelGuide.common.logging.log
4+
import com.back.koreaTravelGuide.domain.guide.service.GuideService
5+
import com.fasterxml.jackson.databind.ObjectMapper
6+
import org.springframework.ai.tool.annotation.Tool
7+
import org.springframework.ai.tool.annotation.ToolParam
8+
import org.springframework.stereotype.Component
9+
10+
@Component
11+
class GuideFinderTool(
12+
private val guideService: GuideService,
13+
private val objectMapper: ObjectMapper,
14+
) {
15+
@Tool(description = "특정 지역(region)에서 활동하는 여행 가이드 목록을 검색합니다.")
16+
fun findGuidesByRegion(
17+
@ToolParam(description = "검색할 지역 이름. 예: '서울', '부산', '강남구'", required = true)
18+
region: String,
19+
): String {
20+
log.info("🔧 [TOOL CALLED] findGuidesByRegion - region: $region")
21+
22+
val guides = guideService.findGuidesByRegion(region)
23+
24+
return try {
25+
if (guides.isEmpty()) {
26+
log.info("✅ [TOOL RESULT] findGuidesByRegion - 결과 없음")
27+
return "해당 지역에서 활동하는 가이드를 찾을 수 없습니다."
28+
}
29+
val result = objectMapper.writeValueAsString(guides)
30+
log.info("✅ [TOOL RESULT] findGuidesByRegion - 결과: ${result.take(200)}...")
31+
result
32+
} catch (e: Exception) {
33+
log.error("❌ [TOOL ERROR] findGuidesByRegion - 예외 발생: ${e.javaClass.name}", e)
34+
"가이드 정보를 JSON으로 변환하는 중 오류가 발생했습니다."
35+
}
36+
}
37+
}

src/main/kotlin/com/back/koreaTravelGuide/domain/user/repository/UserRepository.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,9 @@ interface UserRepository : JpaRepository<User, Long> {
1515
): User?
1616

1717
fun findByEmail(email: String): User?
18+
19+
fun findByRoleAndLocationContains(
20+
role: UserRole,
21+
location: String,
22+
): List<User>
1823
}

src/main/kotlin/com/back/koreaTravelGuide/domain/user/service/GuideService.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,10 @@ class GuideService(
4949

5050
return GuideResponse.from(userRepository.save(user))
5151
}
52+
53+
@Transactional(readOnly = true)
54+
fun findGuidesByRegion(region: String): List<GuideResponse> {
55+
val guides = userRepository.findByRoleAndLocationContains(UserRole.GUIDE, region)
56+
return guides.map { GuideResponse.from(it) }
57+
}
5258
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package com.back.koreaTravelGuide.domain.rate.controller
2+
3+
import com.back.koreaTravelGuide.common.security.JwtTokenProvider
4+
import com.back.koreaTravelGuide.domain.ai.aiChat.entity.AiChatSession
5+
import com.back.koreaTravelGuide.domain.ai.aiChat.repository.AiChatSessionRepository
6+
import com.back.koreaTravelGuide.domain.rate.dto.RateRequest
7+
import com.back.koreaTravelGuide.domain.user.entity.User
8+
import com.back.koreaTravelGuide.domain.user.enums.UserRole
9+
import com.back.koreaTravelGuide.domain.user.repository.UserRepository
10+
import com.fasterxml.jackson.databind.ObjectMapper
11+
import org.junit.jupiter.api.BeforeEach
12+
import org.junit.jupiter.api.DisplayName
13+
import org.junit.jupiter.api.Test
14+
import org.springframework.beans.factory.annotation.Autowired
15+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
16+
import org.springframework.boot.test.context.SpringBootTest
17+
import org.springframework.http.MediaType
18+
import org.springframework.test.context.ActiveProfiles
19+
import org.springframework.test.web.servlet.MockMvc
20+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
21+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
22+
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
23+
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.handler
24+
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
25+
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
26+
import org.springframework.transaction.annotation.Transactional
27+
28+
@ActiveProfiles("test")
29+
@SpringBootTest
30+
@AutoConfigureMockMvc
31+
@Transactional
32+
class RateControllerTest {
33+
@Autowired
34+
private lateinit var mockMvc: MockMvc
35+
36+
@Autowired
37+
private lateinit var userRepository: UserRepository
38+
39+
@Autowired
40+
private lateinit var aiChatSessionRepository: AiChatSessionRepository
41+
42+
@Autowired
43+
private lateinit var jwtTokenProvider: JwtTokenProvider
44+
45+
@Autowired
46+
private lateinit var objectMapper: ObjectMapper
47+
48+
private lateinit var raterUser: User
49+
private lateinit var guideUser: User
50+
private lateinit var adminUser: User
51+
private lateinit var aiSession: AiChatSession
52+
53+
private lateinit var raterUserToken: String
54+
private lateinit var guideUserToken: String
55+
private lateinit var adminUserToken: String
56+
57+
@BeforeEach
58+
fun setUp() {
59+
raterUser =
60+
userRepository.save(
61+
User(email = "[email protected]", nickname = "rater", role = UserRole.USER, oauthProvider = "test", oauthId = "rater123"),
62+
)
63+
guideUser =
64+
userRepository.save(
65+
User(email = "[email protected]", nickname = "guide", role = UserRole.GUIDE, oauthProvider = "test", oauthId = "guide123"),
66+
)
67+
adminUser =
68+
userRepository.save(
69+
User(email = "[email protected]", nickname = "admin", role = UserRole.ADMIN, oauthProvider = "test", oauthId = "admin123"),
70+
)
71+
72+
aiSession =
73+
aiChatSessionRepository.save(
74+
AiChatSession(userId = raterUser.id!!, sessionTitle = "테스트 세션"),
75+
)
76+
77+
raterUserToken = jwtTokenProvider.createAccessToken(raterUser.id!!, raterUser.role)
78+
guideUserToken = jwtTokenProvider.createAccessToken(guideUser.id!!, guideUser.role)
79+
adminUserToken = jwtTokenProvider.createAccessToken(adminUser.id!!, adminUser.role)
80+
}
81+
82+
@Test
83+
@DisplayName("가이드 평가 생성/수정 성공")
84+
fun t1() {
85+
val request = RateRequest(rating = 5, comment = "최고의 가이드")
86+
87+
mockMvc.perform(
88+
put("/api/rate/guides/{guideId}", guideUser.id)
89+
.header("Authorization", "Bearer $raterUserToken")
90+
.contentType(MediaType.APPLICATION_JSON)
91+
.content(objectMapper.writeValueAsString(request)),
92+
)
93+
.andDo(print())
94+
.andExpect(status().isOk)
95+
.andExpect(handler().handlerType(RateController::class.java))
96+
.andExpect(handler().methodName("rateGuide"))
97+
.andExpect(jsonPath("$.data.rating").value(5))
98+
.andExpect(jsonPath("$.data.comment").value("최고의 가이드"))
99+
}
100+
101+
@Test
102+
@DisplayName("AI 채팅 세션 평가 성공")
103+
fun t2() {
104+
val request = RateRequest(rating = 4, comment = "AI가 똑똑하네요.")
105+
106+
mockMvc.perform(
107+
put("/api/rate/aichat/sessions/{sessionId}", aiSession.id)
108+
.header("Authorization", "Bearer $raterUserToken")
109+
.contentType(MediaType.APPLICATION_JSON)
110+
.content(objectMapper.writeValueAsString(request)),
111+
)
112+
.andDo(print())
113+
.andExpect(status().isOk)
114+
.andExpect(handler().handlerType(RateController::class.java))
115+
.andExpect(handler().methodName("rateAiSession"))
116+
.andExpect(jsonPath("$.data.rating").value(4))
117+
}
118+
119+
@Test
120+
@DisplayName("내 가이드 평점 조회 성공")
121+
fun t3() {
122+
// given
123+
val request = RateRequest(rating = 5, comment = "최고의 가이드")
124+
mockMvc.perform(
125+
put("/api/rate/guides/{guideId}", guideUser.id)
126+
.header("Authorization", "Bearer $raterUserToken")
127+
.contentType(MediaType.APPLICATION_JSON)
128+
.content(objectMapper.writeValueAsString(request)),
129+
)
130+
131+
// when & then
132+
mockMvc.perform(
133+
get("/api/rate/guides/my")
134+
.header("Authorization", "Bearer $guideUserToken"),
135+
)
136+
.andDo(print())
137+
.andExpect(status().isOk)
138+
.andExpect(handler().handlerType(RateController::class.java))
139+
.andExpect(handler().methodName("getMyGuideRatings"))
140+
.andExpect(jsonPath("$.data.totalRatings").value(1))
141+
.andExpect(jsonPath("$.data.averageRating").value(5.0))
142+
}
143+
144+
@Test
145+
@DisplayName("관리자의 AI 채팅 평가 목록 조회 성공")
146+
fun t4() {
147+
// given
148+
val request = RateRequest(rating = 4, comment = "AI가 똑똑하네요.")
149+
mockMvc.perform(
150+
put("/api/rate/aichat/sessions/{sessionId}", aiSession.id)
151+
.header("Authorization", "Bearer $raterUserToken")
152+
.contentType(MediaType.APPLICATION_JSON)
153+
.content(objectMapper.writeValueAsString(request)),
154+
)
155+
156+
// when & then
157+
mockMvc.perform(
158+
get("/api/rate/admin/aichat/sessions?page=0&size=10")
159+
.header("Authorization", "Bearer $adminUserToken"),
160+
)
161+
.andDo(print())
162+
.andExpect(status().isOk)
163+
.andExpect(handler().handlerType(RateController::class.java))
164+
.andExpect(handler().methodName("getAllAiSessionRatings"))
165+
.andExpect(jsonPath("$.data.content").isArray)
166+
.andExpect(jsonPath("$.data.content.length()").value(1))
167+
.andExpect(jsonPath("$.data.totalElements").value(1))
168+
}
169+
}

0 commit comments

Comments
 (0)