From 56e152f8146cd3545cd7852e408dfa8b1944e8b6 Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 8 Sep 2026 13:32:43 +0900 Subject: [PATCH] =?UTF-8?q?#441=20[Fix]=20=EC=BF=A0=ED=8C=A1=20=ED=8C=8C?= =?UTF-8?q?=ED=8A=B8=EB=84=88=EC=8A=A4=20ID=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EB=88=84=EB=9D=BD=20=EC=8B=9C=20=EC=95=B1=20?= =?UTF-8?q?=EB=B6=80=ED=8C=85=20=EC=8B=A4=ED=8C=A8=20=ED=95=B4=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - application.yml 의 COUPANG_PARTNERS_ID 에 기본값을 준다. 기본값이 없어 환경변수가 없는 서버에서 PlaceholderResolutionException 이 나고, adminAdService 빈 생성 실패가 컨텍스트 전체를 무너뜨려 광고와 무관한 API 까지 502 로 내려갔다. - 빈 값이면 쿠팡 lptag 대조를 건너뛴다. AdminAdService 가 이미 그렇게 동작한다. - 광고 환경변수에 기본값이 있는지 검사하는 테스트를 추가한다. application-test.yml 이 값을 박아 둬서 컨텍스트 기동만으로는 재발을 못 잡는다. - 광고 API 스웨거 그룹 구성을 고정하는 테스트를 함께 추가한다. --- src/main/resources/application.yml | 2 +- .../global/config/AdPropertyDefaultTest.java | 46 ++++++++++++++++++ .../global/config/SwaggerAdGroupTest.java | 48 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/swyp/picke/global/config/AdPropertyDefaultTest.java create mode 100644 src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bff7a3b8..9d197bac 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -162,4 +162,4 @@ media: coupang: partners: # 제휴 링크에 lptag로 노출되는 공개 식별자다. 소재 등록 시 남의 링크가 아닌지 대조하는 데 쓴다. - id: ${COUPANG_PARTNERS_ID} + id: ${COUPANG_PARTNERS_ID:} diff --git a/src/test/java/com/swyp/picke/global/config/AdPropertyDefaultTest.java b/src/test/java/com/swyp/picke/global/config/AdPropertyDefaultTest.java new file mode 100644 index 00000000..6d43f335 --- /dev/null +++ b/src/test/java/com/swyp/picke/global/config/AdPropertyDefaultTest.java @@ -0,0 +1,46 @@ +package com.swyp.picke.global.config; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * 광고 환경변수에 기본값이 없으면 값이 빠진 서버에서 앱 전체가 뜨지 않는다. + * 광고와 무관한 API까지 같이 죽는다. 실제로 COUPANG_PARTNERS_ID 때문에 dev가 502로 내려간 적이 있다. + * + *

테스트 설정(application-test.yml)이 값을 박아 두기 때문에 컨텍스트를 띄우는 것만으로는 + * 이 경우를 재현할 수 없다. 그래서 설정 파일의 플레이스홀더 자체를 검사한다. + */ +class AdPropertyDefaultTest { + + private static final Pattern PLACEHOLDER = Pattern.compile("\\$\\{(COUPANG_[A-Z0-9_]+|ADPICK_[A-Z0-9_]+)(:[^}]*)?}"); + + @Test + @DisplayName("광고 환경변수는 모두 기본값을 갖는다. 값이 없어도 앱은 떠야 한다") + void adPlaceholders_haveDefaults() throws IOException { + String yml = new String(new ClassPathResource("application.yml").getInputStream().readAllBytes(), + StandardCharsets.UTF_8); + + Matcher matcher = PLACEHOLDER.matcher(yml); + List missingDefaults = new ArrayList<>(); + int found = 0; + while (matcher.find()) { + found++; + if (matcher.group(2) == null) { + missingDefaults.add(matcher.group(1)); + } + } + + assertThat(found).isPositive(); + assertThat(missingDefaults).isEmpty(); + } +} diff --git a/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java b/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java new file mode 100644 index 00000000..4767b884 --- /dev/null +++ b/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java @@ -0,0 +1,48 @@ +package com.swyp.picke.global.config; + +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.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * 사용자 그룹은 FE_USED_OPERATIONS 화이트리스트로, 관리자 그룹은 pathsToExclude 로 광고를 걷어낸다. + * 둘 중 하나만 손대도 광고 탭이 조용히 비어 버리므로 그룹 구성 자체를 고정해 둔다. + */ +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +class SwaggerAdGroupTest { + + private static final String AD_GROUP = "3. 광고 API"; + + @Autowired + private MockMvc mockMvc; + + @Test + @DisplayName("광고 API는 스웨거 그룹 선택 목록에 별도 탭으로 나온다") + void swaggerConfig_exposesAdGroup() throws Exception { + mockMvc.perform(get("/v3/api-docs/swagger-config")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.urls[?(@.name == '" + AD_GROUP + "')]").exists()); + } + + @Test + @DisplayName("광고 탭에는 앱용 조회·집계와 관리자용 소재 관리가 함께 묶인다") + void adGroup_containsAppAndAdminOperations() throws Exception { + mockMvc.perform(get("/v3/api-docs/" + AD_GROUP)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.paths['/api/v1/ads'].get").exists()) + .andExpect(jsonPath("$.paths['/api/v1/ads/impressions'].post").exists()) + .andExpect(jsonPath("$.paths['/api/v1/admin/ads'].get").exists()) + .andExpect(jsonPath("$.paths['/api/v1/admin/ads/stats'].get").exists()) + .andExpect(jsonPath("$.paths['/api/v1/admin/ads/clicks'].get").exists()); + } +}