From 9679a851ad81330a71054b570a511816e980c8a0 Mon Sep 17 00:00:00 2001 From: "bottlenote-app[bot]" <289617182+bottlenote-app[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:28:56 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=81=90=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=EB=A5=BC=20Kakao=20Place=20ID=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/openapi/curation/program.json | 14 +++--- .../curation/whisky_tasting_event.json | 28 +++++------ .../CurationPlaceFieldContractTest.java | 47 ++++++++++++------- 3 files changed, 51 insertions(+), 38 deletions(-) diff --git a/bottlenote-mono/src/main/resources/openapi/curation/program.json b/bottlenote-mono/src/main/resources/openapi/curation/program.json index 39aceafef..4c5c90b8d 100644 --- a/bottlenote-mono/src/main/resources/openapi/curation/program.json +++ b/bottlenote-mono/src/main/resources/openapi/curation/program.json @@ -41,15 +41,15 @@ "x-field-style": "address-search", "x-display-name": "장소명" }, - "zipCode": { + "kakaoPlaceId": { "type": "string", - "description": "행사 장소 우편번호", - "example": "06164", - "minLength": 5, - "maxLength": 5, - "pattern": "^\\d{5}$", + "description": "Kakao Places 장소 ID", + "example": "27288225", + "minLength": 1, + "maxLength": 20, + "pattern": "^\\d+$", "x-field-style": "address-search", - "x-display-name": "우편번호" + "x-display-name": "Kakao 장소 ID" }, "address": { "type": "string", diff --git a/bottlenote-mono/src/main/resources/openapi/curation/whisky_tasting_event.json b/bottlenote-mono/src/main/resources/openapi/curation/whisky_tasting_event.json index 6d529de29..df5da76ac 100644 --- a/bottlenote-mono/src/main/resources/openapi/curation/whisky_tasting_event.json +++ b/bottlenote-mono/src/main/resources/openapi/curation/whisky_tasting_event.json @@ -42,15 +42,15 @@ "x-field-style": "address-search", "x-display-name": "장소명" }, - "zipCode": { + "kakaoPlaceId": { "type": "string", - "description": "장소 우편번호", - "example": "06236", - "minLength": 5, - "maxLength": 5, - "pattern": "^\\d{5}$", + "description": "Kakao Places 장소 ID", + "example": "27288225", + "minLength": 1, + "maxLength": 20, + "pattern": "^\\d+$", "x-field-style": "address-search", - "x-display-name": "우편번호" + "x-display-name": "Kakao 장소 ID" }, "barAddress": { "type": "string", @@ -312,15 +312,15 @@ "description": "피드에서 시음회 장소명을 표시하기 위해 포함한다." } }, - "zipCode": { + "kakaoPlaceId": { "type": "string", - "description": "장소 우편번호", - "example": "06236", - "minLength": 5, - "maxLength": 5, - "pattern": "^\\d{5}$", + "description": "Kakao Places 장소 ID", + "example": "27288225", + "minLength": 1, + "maxLength": 20, + "pattern": "^\\d+$", "x-field-style": "address-search", - "x-display-name": "우편번호" + "x-display-name": "Kakao 장소 ID" }, "barAddress": { "type": "string", diff --git a/bottlenote-mono/src/test/java/app/bottlenote/curation/service/CurationPlaceFieldContractTest.java b/bottlenote-mono/src/test/java/app/bottlenote/curation/service/CurationPlaceFieldContractTest.java index 8d6b38ef6..36573fc77 100644 --- a/bottlenote-mono/src/test/java/app/bottlenote/curation/service/CurationPlaceFieldContractTest.java +++ b/bottlenote-mono/src/test/java/app/bottlenote/curation/service/CurationPlaceFieldContractTest.java @@ -34,22 +34,24 @@ void requestSpec_placeSearchFields_useAddressSearchStyle() throws IOException { JsonNode tasting = OBJECT_MAPPER.valueToTree(schema(TASTING, "Request")).path("properties"); assertThat(tasting.path("placeName").path("x-field-style").asText()) .isEqualTo("address-search"); - assertThat(tasting.path("zipCode").path("x-field-style").asText()).isEqualTo("address-search"); + assertThat(tasting.path("kakaoPlaceId").path("x-field-style").asText()) + .isEqualTo("address-search"); JsonNode program = OBJECT_MAPPER.valueToTree(schema(PROGRAM, "Request")).path("properties"); assertThat(program.path("placeName").path("x-field-style").asText()) .isEqualTo("address-search"); assertThat(program.path("address").path("x-field-style").asText()).isEqualTo("address-search"); - assertThat(program.path("zipCode").path("x-field-style").asText()).isEqualTo("address-search"); + assertThat(program.path("kakaoPlaceId").path("x-field-style").asText()) + .isEqualTo("address-search"); } @Test - @DisplayName("placeName과 zipCode는 optional이라 기존 required 계약을 늘리지 않는다") + @DisplayName("placeName과 kakaoPlaceId는 optional이라 기존 required 계약을 늘리지 않는다") void requestSpec_newFields_areOptional() throws IOException { JsonNode required = OBJECT_MAPPER.valueToTree(schema(TASTING, "Request")).path("required"); assertThat(required).hasSize(8); - assertThat(required.toString()).doesNotContain("placeName").doesNotContain("zipCode"); + assertThat(required.toString()).doesNotContain("placeName").doesNotContain("kakaoPlaceId"); } @Test @@ -62,10 +64,21 @@ void validate_whenLegacyPayloadHasNoPlaceName_passes() throws IOException { } @Test - @DisplayName("zipCode가 5자가 아니면 검증이 거부한다") - void validate_whenZipCodeLengthIsNotFive_rejects() throws IOException { + @DisplayName("kakaoPlaceId가 빈 문자열이면 검증이 거부한다") + void validate_whenKakaoPlaceIdIsBlank_rejects() throws IOException { + Map payload = legacyPayload(); + payload.put("kakaoPlaceId", ""); + + var errors = validator.validate(new MapBackedSchema(schema(TASTING, "Request")), payload); + + assertThat(errors).isNotEmpty(); + } + + @Test + @DisplayName("kakaoPlaceId가 숫자가 아니면 검증이 거부한다") + void validate_whenKakaoPlaceIdIsNotDigits_rejects() throws IOException { Map payload = legacyPayload(); - payload.put("zipCode", "123"); + payload.put("kakaoPlaceId", "2728A225"); var errors = validator.validate(new MapBackedSchema(schema(TASTING, "Request")), payload); @@ -73,10 +86,10 @@ void validate_whenZipCodeLengthIsNotFive_rejects() throws IOException { } @Test - @DisplayName("zipCode가 숫자 5자리가 아니면 검증이 거부한다") - void validate_whenZipCodeIsNotFiveDigits_rejects() throws IOException { + @DisplayName("kakaoPlaceId가 20자리를 넘으면 검증이 거부한다") + void validate_whenKakaoPlaceIdExceedsMaxLength_rejects() throws IOException { Map payload = legacyPayload(); - payload.put("zipCode", "0623A"); + payload.put("kakaoPlaceId", "123456789012345678901"); var errors = validator.validate(new MapBackedSchema(schema(TASTING, "Request")), payload); @@ -84,10 +97,10 @@ void validate_whenZipCodeIsNotFiveDigits_rejects() throws IOException { } @Test - @DisplayName("숫자 5자리 zipCode는 통과한다") - void validate_whenZipCodeIsFiveDigits_passes() throws IOException { + @DisplayName("20자리 숫자 kakaoPlaceId는 통과한다") + void validate_whenKakaoPlaceIdAtMaxLength_passes() throws IOException { Map payload = legacyPayload(); - payload.put("zipCode", "06236"); + payload.put("kakaoPlaceId", "12345678901234567890"); var errors = validator.validate(new MapBackedSchema(schema(TASTING, "Request")), payload); @@ -131,18 +144,18 @@ void responseSpec_placeNameFeedOrder_sitsBetweenTimeAndAddress() throws IOExcept } @Test - @DisplayName("피드에는 placeName이 실리고 zipCode는 실리지 않는다") - void extractFeedPayload_exposesPlaceNameButNotZipCode() throws IOException { + @DisplayName("피드에는 placeName이 실리고 kakaoPlaceId는 실리지 않는다") + void extractFeedPayload_exposesPlaceNameButNotKakaoPlaceId() throws IOException { Map payload = legacyPayload(); payload.put("placeName", "보틀노트 테이스팅룸"); - payload.put("zipCode", "06236"); + payload.put("kakaoPlaceId", "27288225"); JsonNode feedPayload = OBJECT_MAPPER.valueToTree( projector.extractFeedPayload(schema(TASTING, "Response"), payload)); assertThat(feedPayload.path("placeName").asText()).isEqualTo("보틀노트 테이스팅룸"); - assertThat(feedPayload.has("zipCode")).isFalse(); + assertThat(feedPayload.has("kakaoPlaceId")).isFalse(); } @Test