From 46cd22f6640d763d1b54dece9c88c9c5b0df69f0 Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Thu, 25 Sep 2025 18:58:28 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat(be)=20Tour=EC=97=94=ED=8B=B0=ED=8B=B0?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ai/tour/dto/InternalData.kt | 13 ++++++++++ .../domain/ai/tour/dto/TourResponse.kt | 25 +++++++++++++++---- 2 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt new file mode 100644 index 0000000..ec3f894 --- /dev/null +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt @@ -0,0 +1,13 @@ +package com.back.koreaTravelGuide.domain.ai.tour.dto + +// 09.25 양현준 +// 관광 정보 호출용 파라미터 +// 생략 가능한 필드는 생략 하였음 (arrange : 제목 순으로 정렬, cat : 대,중,소 분류, crpyrhtDivCd: 저작권유형) +data class TourSearchParams( + val numOfRows: Int = 10, // 한 페이지 데이터 수, 미 입력시 10 + val pageNo: Int = 1, // 페이지 번호, 미 입력시 10 + + val contentTypeId: String = "12", // 관광타입 ID, 미 입력시 전체 조회 (12:관광지, 38 : 쇼핑...), 우선 관광지로 하드코딩 + val areaCode: String, // 지역코드, 미 입력시 지역 전체 (1:서울, 2:인천...) + val sigunguCode: String, // 시군구코드, 미 입력시 전체 +) \ No newline at end of file diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/TourResponse.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/TourResponse.kt index 1874ef2..0b51f90 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/TourResponse.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/TourResponse.kt @@ -1,8 +1,23 @@ package com.back.koreaTravelGuide.domain.ai.tour.dto -// TODO: 관광 정보 응답 DTO - 관광지 정보 및 메타데이터 반환 +// 09.25 양현준 +// 관광 정보 응답 DTOd +// API 매뉴얼에서 필수로 받는 값은 NonNull로 지정. data class TourResponse( - val name: String, - val location: String, - val description: String, -) + val contentId: String, // 콘텐츠ID (고유 번호) + val contentTypeId: String, // 관광타입 ID (12: 관광지, 14: 문화시설 ..) + val createdTime: String, // 등록일 + val modifiedTime: String, // 수정일 + + val title: String, // 제목 + val addr1 : String?, // 주소 + val areaCode : String?, // 지역코드 + val firstimage: String?, // 이미지 (URL) + val firstimage2: String?, // 썸네일 이미지 (URL) + val mapX: String?, // 경도 + val mapY: String?, // 위도 + val mlevel: String?, // 지도 레벨 + val sigunguCode: String?, // 시군구코드 + val lDongRegnCd: String?, // 법정동 시도 코드, 응답 코드가 IDongRegnCd 이므로, + val lDongSignguCd: String? // 법정동 시군구 코드 +) \ No newline at end of file From b4c6f5c0927e51ad2baaa20c6237bf725c637a9d Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Thu, 25 Sep 2025 19:05:03 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat(be):=20TourClient=20=EB=B0=8F=20Test?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84,=20Dto=EB=A5=BC=20Klint=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ai/tour/client/TourApiClient.kt | 81 ++++++++- .../domain/ai/tour/dto/InternalData.kt | 27 +-- .../domain/ai/tour/dto/TourResponse.kt | 56 ++++--- .../ai/tour/client/TourApiClientTest.kt | 156 ++++++++++++++++++ .../resources/application-test.properties | 4 +- 5 files changed, 290 insertions(+), 34 deletions(-) create mode 100644 src/test/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClientTest.kt diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClient.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClient.kt index a9cc3ef..e1d7283 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClient.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClient.kt @@ -1,4 +1,81 @@ package com.back.koreaTravelGuide.domain.ai.tour.client -// TODO: 관광청 API 클라이언트 - 관광 정보 API 호출 및 응답 처리 -class TourApiClient +import com.back.koreaTravelGuide.domain.ai.tour.dto.InternalData +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.beans.factory.annotation.Value +import org.springframework.web.client.RestTemplate +import org.springframework.web.util.UriComponentsBuilder +import java.net.URI + +// 09.25 양현준 +class TourApiClient( + private val restTemplate: RestTemplate, + private val objectMapper: ObjectMapper, + @Value("\${tour.api.key}") private val serviceKey: String, + @Value("\${tour.api.base-url}") private val apiUrl: String, +) { + // 요청 URL 구성 + private fun buildUrl(params: InternalData): URI = + UriComponentsBuilder.fromUri(URI.create(apiUrl)) + .path("/areaBasedList2") + .queryParam("serviceKey", serviceKey) + .queryParam("MobileOS", "WEB") + .queryParam("MobileApp", "KoreaTravelGuide") + .queryParam("_type", "json") + .queryParam("numOfRows", params.numOfRows) + .queryParam("pageNo", params.pageNo) + .queryParam("contentTypeId", params.contentTypeId) + .queryParam("areaCode", params.areaCode) + .queryParam("sigunguCode", params.sigunguCode) + .build() + .encode() + .toUri() + + // 지역 기반 관광 정보 조회 (areaBasedList2) + fun fetchTourInfo(params: InternalData): TourResponse? { + println("URL 생성") + val url = buildUrl(params) + + println("관광 정보 조회 API 호출: $url") + + return try { + val jsonResponse = restTemplate.getForObject(url, String::class.java) + println("관광 정보 응답 길이: ${jsonResponse?.length ?: 0}") + + if (jsonResponse.isNullOrBlank()) return null // HTTP 호출 결과가 null이거나 공백 문자열일 때 + + val root = objectMapper.readTree(jsonResponse) // 문자열을 Jackson 트리 구조(JsonNode)로 변환 + val itemsNode = + root // path("키") 형태로 노드를 탐색, 응답 Json 형태의 순서에 따라 순차적으로 내려감 + .path("response") + .path("body") + .path("items") + .path("item") + + if (!itemsNode.isArray || itemsNode.isEmpty) return null // 탐색 결과가 비어 있는 경우 + + val firstItem = itemsNode.first() + TourResponse( + contentId = firstItem.path("contentid").asText(), + contentTypeId = firstItem.path("contenttypeid").asText(), + createdTime = firstItem.path("createdtime").asText(), + modifiedTime = firstItem.path("modifiedtime").asText(), + title = firstItem.path("title").asText(), + addr1 = firstItem.path("addr1").takeIf { it.isTextual }?.asText(), + areaCode = firstItem.path("areacode").takeIf { it.isTextual }?.asText(), + firstimage = firstItem.path("firstimage").takeIf { it.isTextual }?.asText(), + firstimage2 = firstItem.path("firstimage2").takeIf { it.isTextual }?.asText(), + mapX = firstItem.path("mapx").takeIf { it.isTextual }?.asText(), + mapY = firstItem.path("mapy").takeIf { it.isTextual }?.asText(), + mlevel = firstItem.path("mlevel").takeIf { it.isTextual }?.asText(), + sigunguCode = firstItem.path("sigungucode").takeIf { it.isTextual }?.asText(), + lDongRegnCd = firstItem.path("lDongRegnCd").takeIf { it.isTextual }?.asText(), + lDongSignguCd = firstItem.path("lDongSignguCd").takeIf { it.isTextual }?.asText(), + ) + } catch (e: Exception) { + println("관광 정보 조회 오류: ${e.message}") + null + } + } +} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt index ec3f894..283dedd 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt @@ -1,13 +1,20 @@ package com.back.koreaTravelGuide.domain.ai.tour.dto -// 09.25 양현준 -// 관광 정보 호출용 파라미터 -// 생략 가능한 필드는 생략 하였음 (arrange : 제목 순으로 정렬, cat : 대,중,소 분류, crpyrhtDivCd: 저작권유형) -data class TourSearchParams( - val numOfRows: Int = 10, // 한 페이지 데이터 수, 미 입력시 10 - val pageNo: Int = 1, // 페이지 번호, 미 입력시 10 +/** + * 9.25 양현준 + * 관광 정보 호출용 파라미터 + * 기능상, 생략 가능한 필드는 생략 (arrange : 제목 순으로 정렬, cat : 대,중,소 분류, crpyrhtDivCd: 저작권유형) + */ - val contentTypeId: String = "12", // 관광타입 ID, 미 입력시 전체 조회 (12:관광지, 38 : 쇼핑...), 우선 관광지로 하드코딩 - val areaCode: String, // 지역코드, 미 입력시 지역 전체 (1:서울, 2:인천...) - val sigunguCode: String, // 시군구코드, 미 입력시 전체 -) \ No newline at end of file +data class InternalData( + // 한 페이지 데이터 수, 미 입력시 10 + val numOfRows: Int = 10, + // 페이지 번호, 미 입력시 10 + val pageNo: Int = 1, + // 관광타입 ID, 미 입력시 전체 조회 (12:관광지, 38 : 쇼핑...), 우선 관광지로 하드코딩 + val contentTypeId: String = "12", + // 지역코드, 미 입력시 지역 전체 (1:서울, 2:인천...) + val areaCode: String, + // 시군구코드, 미 입력시 전체 + val sigunguCode: String, +) diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/TourResponse.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/TourResponse.kt index 0b51f90..63a0a16 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/TourResponse.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/TourResponse.kt @@ -1,23 +1,39 @@ package com.back.koreaTravelGuide.domain.ai.tour.dto -// 09.25 양현준 -// 관광 정보 응답 DTOd -// API 매뉴얼에서 필수로 받는 값은 NonNull로 지정. +/** + * 9.25 양현준 + * 관광 정보 응답 DTO + * API 매뉴얼에서 필수인 값은 NonNull로 지정. + */ data class TourResponse( - val contentId: String, // 콘텐츠ID (고유 번호) - val contentTypeId: String, // 관광타입 ID (12: 관광지, 14: 문화시설 ..) - val createdTime: String, // 등록일 - val modifiedTime: String, // 수정일 - - val title: String, // 제목 - val addr1 : String?, // 주소 - val areaCode : String?, // 지역코드 - val firstimage: String?, // 이미지 (URL) - val firstimage2: String?, // 썸네일 이미지 (URL) - val mapX: String?, // 경도 - val mapY: String?, // 위도 - val mlevel: String?, // 지도 레벨 - val sigunguCode: String?, // 시군구코드 - val lDongRegnCd: String?, // 법정동 시도 코드, 응답 코드가 IDongRegnCd 이므로, - val lDongSignguCd: String? // 법정동 시군구 코드 -) \ No newline at end of file + // 콘텐츠ID (고유 번호) + val contentId: String, + // 관광타입 ID (12: 관광지, 14: 문화시설 ..) + val contentTypeId: String, + // 등록일 + val createdTime: String, + // 수정일 + val modifiedTime: String, + // 제목 + val title: String, + // 주소 + val addr1: String?, + // 지역코드 + val areaCode: String?, + // 이미지 (URL) + val firstimage: String?, + // 썸네일 이미지 (URL) + val firstimage2: String?, + // 경도 + val mapX: String?, + // 위도 + val mapY: String?, + // 지도 레벨 + val mlevel: String?, + // 시군구코드 + val sigunguCode: String?, + // 법정동 시도 코드, 응답 코드가 IDongRegnCd 이므로, + val lDongRegnCd: String?, + // 법정동 시군구 코드 + val lDongSignguCd: String?, +) diff --git a/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClientTest.kt b/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClientTest.kt new file mode 100644 index 0000000..ecac3a4 --- /dev/null +++ b/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClientTest.kt @@ -0,0 +1,156 @@ +package com.back.koreaTravelGuide.domain.ai.tour.client + +import com.back.koreaTravelGuide.application.KoreaTravelGuideApplication +import com.back.koreaTravelGuide.domain.ai.tour.dto.InternalData +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse +import com.fasterxml.jackson.databind.ObjectMapper +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.boot.web.client.RestTemplateBuilder +import org.springframework.context.annotation.Bean +import org.springframework.http.HttpMethod +import org.springframework.http.MediaType +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.test.web.client.MockRestServiceServer +import org.springframework.test.web.client.match.MockRestRequestMatchers.method +import org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo +import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess +import org.springframework.web.client.RestTemplate +import org.springframework.web.util.UriComponentsBuilder +import java.net.URI +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +// 09.25 양현준 +@ExtendWith(SpringExtension::class) +// 패키지 경로에서 메인 설정을 찾지 못하는 오류를 해결하기 위해 애플리케이션 클래스를 명시. +@SpringBootTest(classes = [KoreaTravelGuideApplication::class]) +@ActiveProfiles("test") +class TourApiClientTest { + @Autowired private lateinit var restTemplateBuilder: RestTemplateBuilder + + @Autowired private lateinit var objectMapper: ObjectMapper + + @Value("\${tour.api.key}") + private lateinit var serviceKey: String + + @Value("\${tour.api.base-url}") + private lateinit var apiUrl: String + + private lateinit var restTemplate: RestTemplate + private lateinit var mockServer: MockRestServiceServer + private lateinit var tourApiClient: TourApiClient + + // 테스트마다 클라이언트와 Mock 서버를 새로 구성해 호출 상태를 초기화. + @BeforeEach + fun setUp() { + restTemplate = restTemplateBuilder.build() + mockServer = MockRestServiceServer.createServer(restTemplate) + tourApiClient = TourApiClient(restTemplate, objectMapper, serviceKey, apiUrl) + } + + // 첫 번째 관광 정보를 반환하는지. + @Test + fun testReturnsFirstTourInfo() { + val params = InternalData(numOfRows = 2, pageNo = 1, areaCode = "1", sigunguCode = "7") + expectTourRequest(params, responseWithItems(sampleTourItem())) + + val result: TourResponse? = tourApiClient.fetchTourInfo(params) + + mockServer.verify() + assertNotNull(result) + assertEquals("2591792", result.contentId) + assertEquals("개봉유수지 생태공원", result.title) + assertEquals("7", result.sigunguCode) + } + + // item 배열이 비어 있으면 null을 돌려주는지. + @Test + fun testReturnsNullWhenItemsMissing() { + val params = InternalData(numOfRows = 1, pageNo = 1, areaCode = "1", sigunguCode = "7") + expectTourRequest(params, responseWithItems()) + + val result = tourApiClient.fetchTourInfo(params) + + mockServer.verify() + assertNull(result) + } + + // 요청 URL과 응답 바디를 미리 세팅해 Mock 서버가 기대한 호출을 검증. + private fun expectTourRequest( + params: InternalData, + responseBody: String, + ) { + mockServer.expect(requestTo(buildExpectedUrl(params))) + .andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)) + } + + // 실제 클라이언트가 조합하는 URL과 동일한 형태를 만들어 비교한다. + private fun buildExpectedUrl(params: InternalData): String = + UriComponentsBuilder.fromUri(URI.create(apiUrl)) + .path("/areaBasedList2") + .queryParam("serviceKey", serviceKey) + .queryParam("MobileOS", "WEB") + .queryParam("MobileApp", "KoreaTravelGuide") + .queryParam("_type", "json") + .queryParam("numOfRows", params.numOfRows) + .queryParam("pageNo", params.pageNo) + .queryParam("contentTypeId", params.contentTypeId) + .queryParam("areaCode", params.areaCode) + .queryParam("sigunguCode", params.sigunguCode) + .build() + .encode() + .toUriString() + + // Jackson을 활용해 테스트 응답 JSON을 손쉽게 조립한다. + private fun responseWithItems(vararg items: Map): String { + val response = + mapOf( + "response" to + mapOf( + "header" to mapOf("resultCode" to "0000", "resultMsg" to "OK"), + "body" to + mapOf( + "items" to mapOf("item" to items.toList()), + ), + ), + ) + + return objectMapper.writeValueAsString(response) + } + + // 테스트용 샘플 관광지 정의한다. + private fun sampleTourItem(): Map = + mapOf( + "contentid" to "2591792", + "contenttypeid" to "12", + "createdtime" to "20190313221125", + "modifiedtime" to "20250316162225", + "title" to "개봉유수지 생태공원", + "addr1" to "서울특별시 구로구 개봉동", + "areacode" to "1", + "firstimage" to "", + "firstimage2" to "", + "mapx" to "126.8632141714", + "mapy" to "37.4924524597", + "mlevel" to "6", + "sigungucode" to "7", + "lDongRegnCd" to "11", + "lDongSignguCd" to "530", + ) + + // 테스트에서 RestTemplateBuilder 빈을 보장해 컨텍스트 로딩 실패 해결. + @TestConfiguration + class TestConfig { + @Bean + fun restTemplateBuilder(): RestTemplateBuilder = RestTemplateBuilder() + } +} diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties index 483426f..8154d0f 100644 --- a/src/test/resources/application-test.properties +++ b/src/test/resources/application-test.properties @@ -6,8 +6,8 @@ weather.api.key=8298f7760d58f7d4a0104d016d5792a33b0048953afda89d5c0bb3fef251141c weather.api.base-url=https://apis.data.go.kr/1360000/MidFcstInfoService # Tour API Configuration -tour.api.key=${TOUR_API_KEY:test-key} -tour.api.base-url=${TOUR_API_BASE_URL:https://apis.data.go.kr/B551011/KorService1} +tour.api.key=${TOUR_API_KEY:Pp8aOoKZql09DdDfb4r9SsFWIepIqaocvCQzJphWcvmBj0ff9KuvikfKjgxrXqK03JNrmOIjOZyLyZhjlY43AQ==} +tour.api.base-url=${TOUR_API_BASE_URL:https://apis.data.go.kr/B551011/KorService2} # Spring AI Configuration spring.ai.openai.api-key=${OPENAI_API_KEY:test-key} From 4e2a1d522c217d6952b857b5b47edee31625fbc1 Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Fri, 26 Sep 2025 10:15:40 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix(be):=20component=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?AI=20PR=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreaTravelGuide/domain/ai/tour/client/TourApiClient.kt | 2 ++ .../domain/ai/tour/client/TourApiClientTest.kt | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClient.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClient.kt index e1d7283..678af78 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClient.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClient.kt @@ -4,11 +4,13 @@ import com.back.koreaTravelGuide.domain.ai.tour.dto.InternalData import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse import com.fasterxml.jackson.databind.ObjectMapper import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component import org.springframework.web.client.RestTemplate import org.springframework.web.util.UriComponentsBuilder import java.net.URI // 09.25 양현준 +@Component class TourApiClient( private val restTemplate: RestTemplate, private val objectMapper: ObjectMapper, diff --git a/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClientTest.kt b/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClientTest.kt index ecac3a4..d742685 100644 --- a/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClientTest.kt +++ b/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClientTest.kt @@ -5,6 +5,7 @@ import com.back.koreaTravelGuide.domain.ai.tour.dto.InternalData import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse 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.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired @@ -57,6 +58,7 @@ class TourApiClientTest { } // 첫 번째 관광 정보를 반환하는지. + @DisplayName("TourApiClient - fetchTourInfo") @Test fun testReturnsFirstTourInfo() { val params = InternalData(numOfRows = 2, pageNo = 1, areaCode = "1", sigunguCode = "7") @@ -72,6 +74,7 @@ class TourApiClientTest { } // item 배열이 비어 있으면 null을 돌려주는지. + @DisplayName("TourApiClient - fetchTourInfo") @Test fun testReturnsNullWhenItemsMissing() { val params = InternalData(numOfRows = 1, pageNo = 1, areaCode = "1", sigunguCode = "7") From 91c637128ffc5d02bf705f74e0bd3dafa116af17 Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Fri, 26 Sep 2025 10:17:17 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix(be)=20=C3=AC=C2=9D:=20=EC=9D=B4?= =?UTF-8?q?=EC=A0=84=20=EC=BB=A4=EB=B0=8B=EC=97=90=20=EB=AF=B8=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=EB=90=9C=20=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ai/tour/dto/InternalData.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt index 283dedd..87a8a03 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/InternalData.kt @@ -7,14 +7,14 @@ package com.back.koreaTravelGuide.domain.ai.tour.dto */ data class InternalData( - // 한 페이지 데이터 수, 미 입력시 10 + // 한 페이지 데이터 수, 10으로 지정 val numOfRows: Int = 10, - // 페이지 번호, 미 입력시 10 + // 페이지 번호, 1로 지정 val pageNo: Int = 1, - // 관광타입 ID, 미 입력시 전체 조회 (12:관광지, 38 : 쇼핑...), 우선 관광지로 하드코딩 - val contentTypeId: String = "12", - // 지역코드, 미 입력시 지역 전체 (1:서울, 2:인천...) - val areaCode: String, - // 시군구코드, 미 입력시 전체 - val sigunguCode: String, + // 관광타입 ID, 미 입력시 전체 조회 (12:관광지, 38 : 쇼핑...), + val contentTypeId: String? = "", + // 지역코드, 미 입력시 지역 전체 조회 (1:서울, 2:인천...) + val areaCode: String? = "", + // 시군구코드, 미 입력시 전체 조회 + val sigunguCode: String? = "", )