Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,7 @@ dump.rdb

### Coverage reports ###
build/reports/
coverage/
coverage/

# Test file
index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.back.koreaTravelGuide.domain.ai.aiChat.tool

import com.back.koreaTravelGuide.common.logging.log
import com.back.koreaTravelGuide.domain.guide.service.GuideService
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.ai.tool.annotation.Tool
import org.springframework.ai.tool.annotation.ToolParam
import org.springframework.stereotype.Component

@Component
class GuideFinderTool(
private val guideService: GuideService,
private val objectMapper: ObjectMapper,
) {
@Tool(description = "특정 지역(region)에서 활동하는 여행 가이드 목록을 검색합니다.")
fun findGuidesByRegion(
@ToolParam(description = "검색할 지역 이름. 예: '서울', '부산', '강남구'", required = true)
region: String,
): String {
log.info("🔧 [TOOL CALLED] findGuidesByRegion - region: $region")

val guides = guideService.findGuidesByRegion(region)

return try {
if (guides.isEmpty()) {
log.info("✅ [TOOL RESULT] findGuidesByRegion - 결과 없음")
return "해당 지역에서 활동하는 가이드를 찾을 수 없습니다."
}
val result = objectMapper.writeValueAsString(guides)
log.info("✅ [TOOL RESULT] findGuidesByRegion - 결과: ${result.take(200)}...")
result
} catch (e: Exception) {
log.error("❌ [TOOL ERROR] findGuidesByRegion - 예외 발생: ${e.javaClass.name}", e)
"가이드 정보를 JSON으로 변환하는 중 오류가 발생했습니다."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ interface UserRepository : JpaRepository<User, Long> {
): User?

fun findByEmail(email: String): User?

fun findByRoleAndLocationContains(
role: UserRole,
location: String,
): List<User>
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,10 @@ class GuideService(

return GuideResponse.from(userRepository.save(user))
}

@Transactional(readOnly = true)
fun findGuidesByRegion(region: String): List<GuideResponse> {
val guides = userRepository.findByRoleAndLocationContains(UserRole.GUIDE, region)
return guides.map { GuideResponse.from(it) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.back.koreaTravelGuide.domain.rate.controller

import com.back.koreaTravelGuide.common.security.JwtTokenProvider
import com.back.koreaTravelGuide.domain.ai.aiChat.entity.AiChatSession
import com.back.koreaTravelGuide.domain.ai.aiChat.repository.AiChatSessionRepository
import com.back.koreaTravelGuide.domain.rate.dto.RateRequest
import com.back.koreaTravelGuide.domain.user.entity.User
import com.back.koreaTravelGuide.domain.user.enums.UserRole
import com.back.koreaTravelGuide.domain.user.repository.UserRepository
import com.fasterxml.jackson.databind.ObjectMapper
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.handler
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.transaction.annotation.Transactional

@ActiveProfiles("test")
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class RateControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc

@Autowired
private lateinit var userRepository: UserRepository

@Autowired
private lateinit var aiChatSessionRepository: AiChatSessionRepository

@Autowired
private lateinit var jwtTokenProvider: JwtTokenProvider

@Autowired
private lateinit var objectMapper: ObjectMapper

private lateinit var raterUser: User
private lateinit var guideUser: User
private lateinit var adminUser: User
private lateinit var aiSession: AiChatSession

private lateinit var raterUserToken: String
private lateinit var guideUserToken: String
private lateinit var adminUserToken: String

@BeforeEach
fun setUp() {
raterUser =
userRepository.save(
User(email = "[email protected]", nickname = "rater", role = UserRole.USER, oauthProvider = "test", oauthId = "rater123"),
)
guideUser =
userRepository.save(
User(email = "[email protected]", nickname = "guide", role = UserRole.GUIDE, oauthProvider = "test", oauthId = "guide123"),
)
adminUser =
userRepository.save(
User(email = "[email protected]", nickname = "admin", role = UserRole.ADMIN, oauthProvider = "test", oauthId = "admin123"),
)

aiSession =
aiChatSessionRepository.save(
AiChatSession(userId = raterUser.id!!, sessionTitle = "테스트 세션"),
)

raterUserToken = jwtTokenProvider.createAccessToken(raterUser.id!!, raterUser.role)
guideUserToken = jwtTokenProvider.createAccessToken(guideUser.id!!, guideUser.role)
adminUserToken = jwtTokenProvider.createAccessToken(adminUser.id!!, adminUser.role)
}

@Test
@DisplayName("가이드 평가 생성/수정 성공")
fun t1() {
val request = RateRequest(rating = 5, comment = "최고의 가이드")

mockMvc.perform(
put("/api/rate/guides/{guideId}", guideUser.id)
.header("Authorization", "Bearer $raterUserToken")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)),
)
.andDo(print())
.andExpect(status().isOk)
.andExpect(handler().handlerType(RateController::class.java))
.andExpect(handler().methodName("rateGuide"))
.andExpect(jsonPath("$.data.rating").value(5))
.andExpect(jsonPath("$.data.comment").value("최고의 가이드"))
}

@Test
@DisplayName("AI 채팅 세션 평가 성공")
fun t2() {
val request = RateRequest(rating = 4, comment = "AI가 똑똑하네요.")

mockMvc.perform(
put("/api/rate/aichat/sessions/{sessionId}", aiSession.id)
.header("Authorization", "Bearer $raterUserToken")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)),
)
.andDo(print())
.andExpect(status().isOk)
.andExpect(handler().handlerType(RateController::class.java))
.andExpect(handler().methodName("rateAiSession"))
.andExpect(jsonPath("$.data.rating").value(4))
}

@Test
@DisplayName("내 가이드 평점 조회 성공")
fun t3() {
// given
val request = RateRequest(rating = 5, comment = "최고의 가이드")
mockMvc.perform(
put("/api/rate/guides/{guideId}", guideUser.id)
.header("Authorization", "Bearer $raterUserToken")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)),
)

// when & then
mockMvc.perform(
get("/api/rate/guides/my")
.header("Authorization", "Bearer $guideUserToken"),
)
.andDo(print())
.andExpect(status().isOk)
.andExpect(handler().handlerType(RateController::class.java))
.andExpect(handler().methodName("getMyGuideRatings"))
.andExpect(jsonPath("$.data.totalRatings").value(1))
.andExpect(jsonPath("$.data.averageRating").value(5.0))
}

@Test
@DisplayName("관리자의 AI 채팅 평가 목록 조회 성공")
fun t4() {
// given
val request = RateRequest(rating = 4, comment = "AI가 똑똑하네요.")
mockMvc.perform(
put("/api/rate/aichat/sessions/{sessionId}", aiSession.id)
.header("Authorization", "Bearer $raterUserToken")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)),
)

// when & then
mockMvc.perform(
get("/api/rate/admin/aichat/sessions?page=0&size=10")
.header("Authorization", "Bearer $adminUserToken"),
)
.andDo(print())
.andExpect(status().isOk)
.andExpect(handler().handlerType(RateController::class.java))
.andExpect(handler().methodName("getAllAiSessionRatings"))
.andExpect(jsonPath("$.data.content").isArray)
.andExpect(jsonPath("$.data.content.length()").value(1))
.andExpect(jsonPath("$.data.totalElements").value(1))
}
}