From ec43757f17de39585a60236c9838876fe7b980eb Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 8 Sep 2026 14:00:16 +0900 Subject: [PATCH 1/2] =?UTF-8?q?#441=20[Fix]=20=EA=B4=91=EA=B3=A0=20?= =?UTF-8?q?=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=ED=83=AD=EC=9D=98=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EC=84=9C=EB=B2=84=EB=A5=BC=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=EB=A1=9C=20=EB=B0=94=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Swagger UI 는 서버 목록의 첫 번째를 골라 두기 때문에, dev 주소로 문서를 열어도 광고 Try it out 이 운영으로 나갔다. 운영에는 광고 경로가 아직 없어 401 이 떨어졌고, 배포된 뒤에는 소재 등록·삭제가 운영 데이터를 건드린다. - 광고 그룹에만 dev 를 앞에 둔다. 다른 그룹의 순서는 건드리지 않는다. - 서버 정의가 두 곳에서 필요해져 메서드로 뽑았다. 값은 그대로다. - 서버 순서를 고정하는 테스트를 추가한다. 되돌리면 실패하는 것을 확인했다. --- .../picke/global/config/SwaggerConfig.java | 32 +++++++++++++------ .../global/config/SwaggerAdGroupTest.java | 18 +++++++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/swyp/picke/global/config/SwaggerConfig.java b/src/main/java/com/swyp/picke/global/config/SwaggerConfig.java index 743f5db..c77f41a 100644 --- a/src/main/java/com/swyp/picke/global/config/SwaggerConfig.java +++ b/src/main/java/com/swyp/picke/global/config/SwaggerConfig.java @@ -103,12 +103,18 @@ public GroupedOpenApi adminApi() { * 제휴 광고는 별도 그룹으로 띄운다. * 사용자 그룹은 FE_USED_OPERATIONS 화이트리스트로 걸러지므로 거기에 넣으면 어차피 보이지 않는다. * 앱용과 관리자용을 한 그룹에 모아 광고 연동만 따로 볼 수 있게 한다. + * + *

이 그룹만 개발 서버를 기본으로 둔다. Swagger UI는 서버 목록의 첫 번째를 골라 두므로, + * 공통 순서를 따르면 dev 주소로 문서를 열어도 Try it out이 운영으로 나간다. + * 광고는 소재 등록·삭제가 섞여 있어 잘못 쏘면 운영 데이터가 바뀐다. */ @Bean public GroupedOpenApi adApi() { return GroupedOpenApi.builder() .group("3. 광고 API") .pathsToMatch("/api/v1/ads", "/api/v1/ads/**", "/api/v1/admin/ads", "/api/v1/admin/ads/**") + .addOpenApiCustomizer(openApi -> openApi.setServers( + List.of(devServer(), localServer(), prodServer()))) .build(); } @@ -120,23 +126,29 @@ public GroupedOpenApi allApi() { .build(); } - @Bean - public OpenAPI openAPI() { - // 1. 운영 서버 (8080) - Server prodServer = new Server() + // 1. 운영 서버 (8080) + private Server prodServer() { + return new Server() .url("https://picke.store") .description("Production Server"); + } - // 2. 로컬 개발 서버 (8080) - Server local8080 = new Server() + // 2. 로컬 개발 서버 (8080) + private Server localServer() { + return new Server() .url("http://localhost:8080") .description("Local Development Server (8080)"); + } - // 3. 개발 서버 (8081) - Server devServer = new Server() + // 3. 개발 서버 (8081) + private Server devServer() { + return new Server() .url("https://dev.picke.store") .description("Remote Dev Server (8081)"); + } + @Bean + public OpenAPI openAPI() { SecurityScheme securityScheme = new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme("bearer") @@ -148,8 +160,8 @@ public OpenAPI openAPI() { new SecurityRequirement().addList("bearerAuth"); return new OpenAPI() - // 3. 서버 리스트 등록 - .servers(List.of(prodServer, local8080, devServer)) + // 서버 리스트 등록 + .servers(List.of(prodServer(), localServer(), devServer())) .info(new Info() .title("PIQUE API 명세서") .description("PIQUE 서비스 API 명세서입니다.") diff --git a/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java b/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java index 4767b88..887e15f 100644 --- a/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java +++ b/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java @@ -8,6 +8,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import static org.hamcrest.Matchers.hasItem; 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; @@ -34,6 +35,23 @@ void swaggerConfig_exposesAdGroup() throws Exception { .andExpect(jsonPath("$.urls[?(@.name == '" + AD_GROUP + "')]").exists()); } + @Test + @DisplayName("광고 탭의 기본 서버는 개발 서버다. Try it out이 운영으로 나가면 안 된다") + void adGroup_defaultsToDevServer() throws Exception { + mockMvc.perform(get("/v3/api-docs/" + AD_GROUP)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.servers[0].url").value("https://dev.picke.store")) + .andExpect(jsonPath("$.servers[*].url").value(hasItem("https://picke.store"))); + } + + @Test + @DisplayName("다른 탭의 서버 순서는 그대로 둔다") + void otherGroups_keepProdFirst() throws Exception { + mockMvc.perform(get("/v3/api-docs/0. 모든 API")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.servers[0].url").value("https://picke.store")); + } + @Test @DisplayName("광고 탭에는 앱용 조회·집계와 관리자용 소재 관리가 함께 묶인다") void adGroup_containsAppAndAdminOperations() throws Exception { From b1be160cb63194871c12113f679e401e37407dac Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 8 Sep 2026 14:07:25 +0900 Subject: [PATCH 2/2] =?UTF-8?q?#441=20[Fix]=20=EA=B4=91=EA=B3=A0=20?= =?UTF-8?q?=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=EA=B8=B0=EB=B3=B8=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=EB=A5=BC=20=EB=AC=B8=EC=84=9C=EB=A5=BC=20=EC=97=B0=20?= =?UTF-8?q?=EC=A3=BC=EC=86=8C=EB=A1=9C=20=EB=B0=94=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dev 를 앞에 두면 운영 스웨거에서도 dev 로 나간다. 상대 경로를 첫 번째에 두어 운영에서 연 문서는 운영으로, dev 에서 연 문서는 dev 로 가게 한다. - 프로파일로 가르지 않는다. dev 서버도 prod 프로파일로 돌기 때문에 구분되지 않는다. - 다른 환경을 일부러 고르는 것은 목록에서 그대로 가능하다. --- .../swyp/picke/global/config/SwaggerConfig.java | 15 +++++++++++++-- .../picke/global/config/SwaggerAdGroupTest.java | 7 ++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/swyp/picke/global/config/SwaggerConfig.java b/src/main/java/com/swyp/picke/global/config/SwaggerConfig.java index c77f41a..9b17e2e 100644 --- a/src/main/java/com/swyp/picke/global/config/SwaggerConfig.java +++ b/src/main/java/com/swyp/picke/global/config/SwaggerConfig.java @@ -104,9 +104,13 @@ public GroupedOpenApi adminApi() { * 사용자 그룹은 FE_USED_OPERATIONS 화이트리스트로 걸러지므로 거기에 넣으면 어차피 보이지 않는다. * 앱용과 관리자용을 한 그룹에 모아 광고 연동만 따로 볼 수 있게 한다. * - *

이 그룹만 개발 서버를 기본으로 둔다. Swagger UI는 서버 목록의 첫 번째를 골라 두므로, + *

이 그룹은 문서를 연 주소를 기본 서버로 둔다. Swagger UI는 서버 목록의 첫 번째를 골라 두는데, * 공통 순서를 따르면 dev 주소로 문서를 열어도 Try it out이 운영으로 나간다. * 광고는 소재 등록·삭제가 섞여 있어 잘못 쏘면 운영 데이터가 바뀐다. + * + *

프로파일로 가르지 않는다. dev 서버도 prod 프로파일로 돌기 때문에 구분이 되지 않는다. + * 상대 경로를 쓰면 운영에서 연 문서는 운영으로, dev에서 연 문서는 dev로 나간다. + * 다른 환경을 일부러 고르는 것은 아래 목록에서 여전히 가능하다. */ @Bean public GroupedOpenApi adApi() { @@ -114,7 +118,7 @@ public GroupedOpenApi adApi() { .group("3. 광고 API") .pathsToMatch("/api/v1/ads", "/api/v1/ads/**", "/api/v1/admin/ads", "/api/v1/admin/ads/**") .addOpenApiCustomizer(openApi -> openApi.setServers( - List.of(devServer(), localServer(), prodServer()))) + List.of(currentServer(), prodServer(), localServer(), devServer()))) .build(); } @@ -126,6 +130,13 @@ public GroupedOpenApi allApi() { .build(); } + // 문서를 연 주소. Swagger UI가 상대 경로를 현재 origin으로 풀어 준다. + private Server currentServer() { + return new Server() + .url("/") + .description("현재 접속한 서버"); + } + // 1. 운영 서버 (8080) private Server prodServer() { return new Server() diff --git a/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java b/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java index 887e15f..4356e57 100644 --- a/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java +++ b/src/test/java/com/swyp/picke/global/config/SwaggerAdGroupTest.java @@ -36,11 +36,12 @@ void swaggerConfig_exposesAdGroup() throws Exception { } @Test - @DisplayName("광고 탭의 기본 서버는 개발 서버다. Try it out이 운영으로 나가면 안 된다") - void adGroup_defaultsToDevServer() throws Exception { + @DisplayName("광고 탭은 문서를 연 주소로 요청한다. 운영에서 열면 운영으로, dev에서 열면 dev로 나간다") + void adGroup_defaultsToCurrentOrigin() throws Exception { mockMvc.perform(get("/v3/api-docs/" + AD_GROUP)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.servers[0].url").value("https://dev.picke.store")) + .andExpect(jsonPath("$.servers[0].url").value("/")) + .andExpect(jsonPath("$.servers[*].url").value(hasItem("https://dev.picke.store"))) .andExpect(jsonPath("$.servers[*].url").value(hasItem("https://picke.store"))); }