Skip to content

Commit 693bf6c

Browse files
committed
feat(be) : service의 parsing 기능 테스트 추가
1 parent d1a89f3 commit 693bf6c

File tree

2 files changed

+161
-110
lines changed

2 files changed

+161
-110
lines changed

src/test/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClientTest.kt

Lines changed: 111 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -29,135 +29,136 @@ import kotlin.test.assertTrue
2929
// 실제 API 호출 기반 단위 테스트
3030
@SpringBootTest(classes = [KoreaTravelGuideApplication::class])
3131
@ActiveProfiles("test")
32-
class TourApiClientTest @Autowired constructor(
33-
private val tourApiClient: TourApiClient,
34-
) {
35-
@DisplayName("fetchTourInfo - 실제 관광청 API가 빈 응답을 줄 경우")
36-
@Test
37-
fun fetchTourInfoRealCallEmptyResponse() {
38-
val params =
39-
TourParams(
40-
contentTypeId = "12",
41-
areaCode = "1",
42-
sigunguCode = "1",
43-
)
44-
45-
val result = tourApiClient.fetchTourInfo(params)
46-
47-
// isEmpty가 true인 경우 테스트를 진행, 아닐 경우 메세지 출력
48-
assumeTrue(result.items.isEmpty()) {
49-
"관광청 API가 정상 데이터를 제공하고 있어 장애 시나리오 테스트를 건너뜁니다."
50-
}
51-
52-
assertTrue(result.items.isEmpty())
53-
}
54-
55-
// MockRestServiceServer 기반 단위 테스트
56-
@Nested
57-
inner class MockServerTests {
58-
private lateinit var restTemplate: RestTemplate
59-
private lateinit var mockServer: MockRestServiceServer
60-
private lateinit var mockClient: TourApiClient
61-
62-
private val serviceKey = "test-service-key"
63-
private val baseUrl = "https://example.com"
64-
65-
@BeforeEach
66-
fun setUp() {
67-
restTemplate = RestTemplate()
68-
mockServer = MockRestServiceServer.createServer(restTemplate)
69-
mockClient = TourApiClient(restTemplate, ObjectMapper(), serviceKey, baseUrl)
70-
}
71-
72-
@AfterEach
73-
fun tearDown() {
74-
mockServer.verify()
75-
}
76-
77-
@DisplayName("fetchTourInfo - 외부 API가 정상 응답을 반환하면 파싱된 결과를 제공")
32+
class TourApiClientTest
33+
@Autowired
34+
constructor(
35+
private val tourApiClient: TourApiClient,
36+
) {
37+
@DisplayName("fetchTourInfo - 실제 관광청 API가 빈 응답을 줄 경우")
7838
@Test
79-
fun fetchTourInfoReturnsParsedItems() {
39+
fun fetchTourInfoRealCallEmptyResponse() {
8040
val params =
8141
TourParams(
8242
contentTypeId = "12",
8343
areaCode = "1",
8444
sigunguCode = "1",
8545
)
8646

87-
mockServer.expect(ExpectedCount.once(), requestTo(expectedAreaBasedListUrl(params)))
88-
.andExpect(method(HttpMethod.GET))
89-
.andRespond(withSuccess(SUCCESS_RESPONSE, MediaType.APPLICATION_JSON))
47+
val result = tourApiClient.fetchTourInfo(params)
9048

91-
val result = mockClient.fetchTourInfo(params)
49+
// isEmpty가 true인 경우 테스트를 진행, 아닐 경우 메세지 출력
50+
assumeTrue(result.items.isEmpty()) {
51+
"관광청 API가 정상 데이터를 제공하고 있어 장애 시나리오 테스트를 건너뜁니다."
52+
}
9253

93-
assertEquals(1, result.items.size)
94-
val firstItem = result.items.first()
95-
assertEquals("12345", firstItem.contentId)
96-
assertEquals("테스트 타이틀", firstItem.title)
54+
assertTrue(result.items.isEmpty())
9755
}
9856

99-
@DisplayName("fetchTourInfo - 외부 API가 404를 반환하면 빈 결과를 전달")
100-
@Test
101-
fun fetchTourInfoReturnsEmptyListWhenApiFails() {
102-
val params =
103-
TourParams(
104-
contentTypeId = "12",
105-
areaCode = "1",
106-
sigunguCode = "1",
107-
)
57+
// MockRestServiceServer 기반 단위 테스트
58+
@Nested
59+
inner class MockServerTests {
60+
private lateinit var restTemplate: RestTemplate
61+
private lateinit var mockServer: MockRestServiceServer
62+
private lateinit var mockClient: TourApiClient
63+
64+
private val serviceKey = "test-service-key"
65+
private val baseUrl = "https://example.com"
66+
67+
@BeforeEach
68+
fun setUp() {
69+
restTemplate = RestTemplate()
70+
mockServer = MockRestServiceServer.createServer(restTemplate)
71+
mockClient = TourApiClient(restTemplate, ObjectMapper(), serviceKey, baseUrl)
72+
}
10873

109-
mockServer.expect(ExpectedCount.once(), requestTo(expectedAreaBasedListUrl(params)))
110-
.andExpect(method(HttpMethod.GET))
111-
.andRespond(withStatus(HttpStatus.NOT_FOUND))
74+
@AfterEach
75+
fun tearDown() {
76+
mockServer.verify()
77+
}
11278

113-
val result = mockClient.fetchTourInfo(params)
79+
@DisplayName("fetchTourInfo - 외부 API가 정상 응답을 반환하면 파싱된 결과를 제공")
80+
@Test
81+
fun fetchTourInfoReturnsParsedItems() {
82+
val params =
83+
TourParams(
84+
contentTypeId = "12",
85+
areaCode = "1",
86+
sigunguCode = "1",
87+
)
88+
89+
mockServer.expect(ExpectedCount.once(), requestTo(expectedAreaBasedListUrl(params)))
90+
.andExpect(method(HttpMethod.GET))
91+
.andRespond(withSuccess(SUCCESS_RESPONSE, MediaType.APPLICATION_JSON))
92+
93+
val result = mockClient.fetchTourInfo(params)
94+
95+
assertEquals(1, result.items.size)
96+
val firstItem = result.items.first()
97+
assertEquals("12345", firstItem.contentId)
98+
assertEquals("테스트 타이틀", firstItem.title)
99+
}
114100

115-
assertTrue(result.items.isEmpty())
116-
}
101+
@DisplayName("fetchTourInfo - 외부 API가 404를 반환하면 빈 결과를 전달")
102+
@Test
103+
fun fetchTourInfoReturnsEmptyListWhenApiFails() {
104+
val params =
105+
TourParams(
106+
contentTypeId = "12",
107+
areaCode = "1",
108+
sigunguCode = "1",
109+
)
117110

118-
private fun expectedAreaBasedListUrl(params: TourParams): String =
119-
UriComponentsBuilder.fromUriString(baseUrl)
120-
.path("/areaBasedList2")
121-
.queryParam("serviceKey", serviceKey)
122-
.queryParam("MobileOS", "WEB")
123-
.queryParam("MobileApp", "KoreaTravelGuide")
124-
.queryParam("_type", "json")
125-
.queryParam("contentTypeId", params.contentTypeId)
126-
.queryParam("areaCode", params.areaCode)
127-
.queryParam("sigunguCode", params.sigunguCode)
128-
.build()
129-
.encode()
130-
.toUriString()
111+
mockServer.expect(ExpectedCount.once(), requestTo(expectedAreaBasedListUrl(params)))
112+
.andExpect(method(HttpMethod.GET))
113+
.andRespond(withStatus(HttpStatus.NOT_FOUND))
131114

132-
}
115+
val result = mockClient.fetchTourInfo(params)
116+
117+
assertTrue(result.items.isEmpty())
118+
}
133119

134-
companion object {
135-
private val SUCCESS_RESPONSE =
136-
"""
137-
{
138-
"response": {
139-
"header": {
140-
"resultCode": "0000",
141-
"resultMsg": "OK"
142-
},
143-
"body": {
144-
"items": {
145-
"item": [
146-
{
147-
"contentid": "12345",
148-
"contenttypeid": "12",
149-
"createdtime": "202310010000",
150-
"modifiedtime": "202310020000",
151-
"title": "테스트 타이틀",
152-
"addr1": "서울특별시 종로구",
153-
"areacode": "1",
154-
"firstimage": "https://example.com/image.jpg"
120+
private fun expectedAreaBasedListUrl(params: TourParams): String =
121+
UriComponentsBuilder.fromUriString(baseUrl)
122+
.path("/areaBasedList2")
123+
.queryParam("serviceKey", serviceKey)
124+
.queryParam("MobileOS", "WEB")
125+
.queryParam("MobileApp", "KoreaTravelGuide")
126+
.queryParam("_type", "json")
127+
.queryParam("contentTypeId", params.contentTypeId)
128+
.queryParam("areaCode", params.areaCode)
129+
.queryParam("sigunguCode", params.sigunguCode)
130+
.build()
131+
.encode()
132+
.toUriString()
133+
}
134+
135+
companion object {
136+
private val SUCCESS_RESPONSE =
137+
"""
138+
{
139+
"response": {
140+
"header": {
141+
"resultCode": "0000",
142+
"resultMsg": "OK"
143+
},
144+
"body": {
145+
"items": {
146+
"item": [
147+
{
148+
"contentid": "12345",
149+
"contenttypeid": "12",
150+
"createdtime": "202310010000",
151+
"modifiedtime": "202310020000",
152+
"title": "테스트 타이틀",
153+
"addr1": "서울특별시 종로구",
154+
"areacode": "1",
155+
"firstimage": "https://example.com/image.jpg"
156+
}
157+
]
155158
}
156-
]
159+
}
157160
}
158161
}
159-
}
160-
}
161-
""".trimIndent()
162+
""".trimIndent()
163+
}
162164
}
163-
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.back.koreaTravelGuide.domain.ai.tour.service
2+
3+
import kotlin.test.assertEquals
4+
import kotlin.test.assertNull
5+
import org.junit.jupiter.api.DisplayName
6+
import org.junit.jupiter.api.Test
7+
8+
class TourParamsParserTest {
9+
private val parser = TourParamsParser()
10+
11+
@DisplayName("parse - 공백이 섞인 입력을 정리해 DTO를 만든다")
12+
@Test
13+
fun parseTrimsTokens() {
14+
val result = parser.parse("12", " 6 , 10 ")
15+
16+
assertEquals("12", result.contentTypeId)
17+
assertEquals("6", result.areaCode)
18+
assertEquals("10", result.sigunguCode)
19+
}
20+
21+
@DisplayName("parse - 시군구 코드가 없으면 null 로 남긴다")
22+
@Test
23+
fun parseWhenSigunguMissing() {
24+
val result = parser.parse("15", "7")
25+
26+
assertEquals("15", result.contentTypeId)
27+
assertEquals("7", result.areaCode)
28+
assertNull(result.sigunguCode)
29+
}
30+
31+
@DisplayName("parse - 콤마가 여러 번 등장하면 빈 문자열을 허용한다")
32+
@Test
33+
fun parseWhenCommaRepeated() {
34+
val result = parser.parse("32", "1,,2")
35+
36+
assertEquals("32", result.contentTypeId)
37+
assertEquals("1", result.areaCode)
38+
assertEquals("", result.sigunguCode)
39+
}
40+
41+
@DisplayName("parse - 완전히 비어 있는 입력은 빈 문자열과 null 로 파싱된다")
42+
@Test
43+
fun parseWhenInputBlank() {
44+
val result = parser.parse("25", "")
45+
46+
assertEquals("25", result.contentTypeId)
47+
assertEquals("", result.areaCode)
48+
assertNull(result.sigunguCode)
49+
}
50+
}

0 commit comments

Comments
 (0)