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..9b17e2e 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,22 @@ public GroupedOpenApi adminApi() { * 제휴 광고는 별도 그룹으로 띄운다. * 사용자 그룹은 FE_USED_OPERATIONS 화이트리스트로 걸러지므로 거기에 넣으면 어차피 보이지 않는다. * 앱용과 관리자용을 한 그룹에 모아 광고 연동만 따로 볼 수 있게 한다. + * + *

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

프로파일로 가르지 않는다. dev 서버도 prod 프로파일로 돌기 때문에 구분이 되지 않는다. + * 상대 경로를 쓰면 운영에서 연 문서는 운영으로, dev에서 연 문서는 dev로 나간다. + * 다른 환경을 일부러 고르는 것은 아래 목록에서 여전히 가능하다. */ @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(currentServer(), prodServer(), localServer(), devServer()))) .build(); } @@ -120,23 +130,36 @@ public GroupedOpenApi allApi() { .build(); } - @Bean - public OpenAPI openAPI() { - // 1. 운영 서버 (8080) - Server prodServer = new Server() + // 문서를 연 주소. Swagger UI가 상대 경로를 현재 origin으로 풀어 준다. + private Server currentServer() { + return new Server() + .url("/") + .description("현재 접속한 서버"); + } + + // 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 +171,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..4356e57 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,24 @@ void swaggerConfig_exposesAdGroup() throws Exception { .andExpect(jsonPath("$.urls[?(@.name == '" + AD_GROUP + "')]").exists()); } + @Test + @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("/")) + .andExpect(jsonPath("$.servers[*].url").value(hasItem("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 {