diff --git a/.github/workflows/ci-java.yml b/.github/workflows/ci-java.yml index 2505b59e..05006c2f 100644 --- a/.github/workflows/ci-java.yml +++ b/.github/workflows/ci-java.yml @@ -79,9 +79,12 @@ jobs: - name: Run Tests run: | if [ "${{ github.base_ref }}" == "main" ]; then - ./gradlew allTests + ./gradlew unitTest + ./gradlew integrationTest else - ./gradlew allTests + ./gradlew unitTest + ./gradlew integrationTest + ./gradlew e2eTest fi working-directory: apps/user-service diff --git a/apps/user-service/build.gradle b/apps/user-service/build.gradle index 45abf367..c76b49f2 100644 --- a/apps/user-service/build.gradle +++ b/apps/user-service/build.gradle @@ -91,15 +91,26 @@ ext { snippetsDir = file('build/generated-snippets') } -tasks.named('test') { +tasks.register('unitTest', Test) { outputs.dir snippetsDir useJUnitPlatform { - // 기본적으로는 e2e 태그 제외하고 실행 - excludeTags 'e2e' + includeTags 'unit' } + systemProperty 'spring.profiles.active', 'test-unit' } +tasks.register('integrationTest', Test) { + outputs.dir snippetsDir + useJUnitPlatform { + includeTags 'integration' + } + + systemProperty 'spring.profiles.active', 'test-integration' + + timeout = Duration.ofMinutes(10) +} + // E2E 테스트 전용 task 추가 tasks.register('e2eTest', Test) { outputs.dir snippetsDir @@ -113,12 +124,6 @@ tasks.register('e2eTest', Test) { timeout = Duration.ofMinutes(10) } -// 모든 테스트 실행 task -tasks.register('allTests', Test) { - outputs.dir snippetsDir - useJUnitPlatform() -} - // AsciiDoctor 설정 (REST Docs 문서 생성) asciidoctor { inputs.dir snippetsDir diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/domain/email/service/MockEmailService.java b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/email/service/MockEmailService.java index 6ccaffc9..527bb752 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/domain/email/service/MockEmailService.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/email/service/MockEmailService.java @@ -8,7 +8,7 @@ import lombok.extern.slf4j.Slf4j; @Service -@Profile({"test-unit", "test-e2e", "local", "develop"}) +@Profile({"test-unit", "test-e2e", "test-integration", "local", "develop"}) @Slf4j public class MockEmailService implements EmailService { diff --git a/apps/user-service/src/main/resources/application-test-integration.yml b/apps/user-service/src/main/resources/application-test-integration.yml index 95faf0f3..6625974a 100644 --- a/apps/user-service/src/main/resources/application-test-integration.yml +++ b/apps/user-service/src/main/resources/application-test-integration.yml @@ -10,7 +10,7 @@ spring: password: driver-class-name: org.h2.Driver hikari: - connection-init-sql: "SET MODE MariaDB" + connection-init-sql: "SET MODE MariaDB; SET NON_KEYWORDS USER;" connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 @@ -23,15 +23,6 @@ spring: console: enabled: true - # JPA 설정 (H2용) - jpa: - hibernate: - ddl-auto: create-drop - show-sql: true - properties: - hibernate: - dialect: org.hibernate.dialect.H2Dialect - # SQL 스크립트 초기화 설정 sql: init: @@ -48,4 +39,4 @@ mybatis: map-underscore-to-camel-case: true logging: - config: classpath:log4j2-test-unit.yml \ No newline at end of file + config: classpath:log4j2-develop.yml \ No newline at end of file diff --git a/apps/user-service/src/main/resources/application-test-unit.yml b/apps/user-service/src/main/resources/application-test-unit.yml index 4b36c77f..cd4e018f 100644 --- a/apps/user-service/src/main/resources/application-test-unit.yml +++ b/apps/user-service/src/main/resources/application-test-unit.yml @@ -24,15 +24,6 @@ spring: console: enabled: true - # JPA 설정 (H2용) - jpa: - hibernate: - ddl-auto: create-drop - show-sql: true - properties: - hibernate: - dialect: org.hibernate.dialect.H2Dialect - # SQL 스크립트 초기화 설정 sql: init: diff --git a/apps/user-service/src/main/resources/application.yml b/apps/user-service/src/main/resources/application.yml index e852951b..c8314375 100644 --- a/apps/user-service/src/main/resources/application.yml +++ b/apps/user-service/src/main/resources/application.yml @@ -3,6 +3,10 @@ spring: name: mvp profiles: active: develop + test: + context: + cache: + maxSize: 1 mybatis: # Mapper XML 파일 위치 mapper-locations: classpath:mapper/**/*.xml diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/scenario/ContextLoadE2eTests.java b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/scenario/ContextLoadE2eTests.java new file mode 100644 index 00000000..ad6bfbf0 --- /dev/null +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/scenario/ContextLoadE2eTests.java @@ -0,0 +1,11 @@ +package com.gltkorea.icebang.e2e.scenario; + +import org.junit.jupiter.api.Test; + +import com.gltkorea.icebang.e2e.setup.support.E2eTestSupport; + +class ContextLoadE2eTests extends E2eTestSupport { + + @Test + void contextLoads() {} +} diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/scenario/UserRegistrationFlowE2eTest.java b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/scenario/UserRegistrationFlowE2eTest.java index f0fd3244..762d5ca4 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/scenario/UserRegistrationFlowE2eTest.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/scenario/UserRegistrationFlowE2eTest.java @@ -13,7 +13,7 @@ import org.springframework.http.*; import org.springframework.test.context.jdbc.Sql; -import com.gltkorea.icebang.e2e.support.E2eTestSupport; +import com.gltkorea.icebang.e2e.setup.support.E2eTestSupport; @Sql( value = "classpath:sql/01-insert-internal-users.sql", diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/scenario/UserServiceApplicationE2eTests.java b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/scenario/UserServiceApplicationE2eTests.java deleted file mode 100644 index 2379e450..00000000 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/scenario/UserServiceApplicationE2eTests.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.gltkorea.icebang.e2e.scenario; - -import org.junit.jupiter.api.Test; - -import com.gltkorea.icebang.e2e.support.E2eTestSupport; - -class UserServiceApplicationE2eTests extends E2eTestSupport { - - @Test - void contextLoads() {} -} diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/annotation/E2eTest.java b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/annotation/E2eTest.java similarity index 88% rename from apps/user-service/src/test/java/com/gltkorea/icebang/e2e/annotation/E2eTest.java rename to apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/annotation/E2eTest.java index 0840a996..0f087064 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/annotation/E2eTest.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/annotation/E2eTest.java @@ -1,4 +1,4 @@ -package com.gltkorea.icebang.e2e.annotation; +package com.gltkorea.icebang.e2e.setup.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/config/E2eTestConfiguration.java b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/config/E2eTestConfiguration.java similarity index 97% rename from apps/user-service/src/test/java/com/gltkorea/icebang/e2e/config/E2eTestConfiguration.java rename to apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/config/E2eTestConfiguration.java index 7ebe181d..4ee26803 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/config/E2eTestConfiguration.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/config/E2eTestConfiguration.java @@ -1,4 +1,4 @@ -package com.gltkorea.icebang.e2e.config; +package com.gltkorea.icebang.e2e.setup.config; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/support/E2eTestSupport.java b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/support/E2eTestSupport.java similarity index 91% rename from apps/user-service/src/test/java/com/gltkorea/icebang/e2e/support/E2eTestSupport.java rename to apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/support/E2eTestSupport.java index 12a44848..b72ac031 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/support/E2eTestSupport.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/support/E2eTestSupport.java @@ -1,4 +1,4 @@ -package com.gltkorea.icebang.e2e.support; +package com.gltkorea.icebang.e2e.setup.support; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -9,8 +9,8 @@ import org.springframework.web.context.WebApplicationContext; import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; -import com.gltkorea.icebang.e2e.annotation.E2eTest; -import com.gltkorea.icebang.e2e.config.E2eTestConfiguration; +import com.gltkorea.icebang.e2e.setup.annotation.E2eTest; +import com.gltkorea.icebang.e2e.setup.config.E2eTestConfiguration; @Import(E2eTestConfiguration.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/support/E2eTestSupportTest.java b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/support/E2eTestSupportTest.java similarity index 90% rename from apps/user-service/src/test/java/com/gltkorea/icebang/e2e/support/E2eTestSupportTest.java rename to apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/support/E2eTestSupportTest.java index 7eccdd4e..33bfd4dc 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/support/E2eTestSupportTest.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/e2e/setup/support/E2eTestSupportTest.java @@ -1,4 +1,4 @@ -package com.gltkorea.icebang.e2e.support; +package com.gltkorea.icebang.e2e.setup.support; import static org.assertj.core.api.Assertions.assertThat; diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/auth/testa.java b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/auth/testa.java deleted file mode 100644 index 9f273f56..00000000 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/auth/testa.java +++ /dev/null @@ -1,287 +0,0 @@ -// package com.gltkorea.icebang.e2e.scenario; -// -// import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document; -// import static com.epages.restdocs.apispec.ResourceDocumentation.*; -// import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -// import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; -// import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; -// import static org.springframework.restdocs.payload.PayloadDocumentation.*; -// import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -// -// import java.util.Arrays; -// import java.util.HashMap; -// import java.util.Map; -// -// import org.junit.jupiter.api.DisplayName; -// import org.junit.jupiter.api.Test; -// import org.springframework.http.*; -// import org.springframework.restdocs.payload.JsonFieldType; -// import org.springframework.test.annotation.DirtiesContext; -// import org.springframework.test.context.jdbc.Sql; -// -// import com.epages.restdocs.apispec.ResourceSnippetParameters; -// import com.gltkorea.icebang.e2e.support.E2eTestSupport; -// -// @Sql("classpath:sql/01-insert-internal-users.sql") -// @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -// class UserRegistrationFlowE2eTest extends E2eTestSupport { -// -// @Test -// @DisplayName("조직 목록 조회 성공") -// void getOrganizations_success() throws Exception { -// mockMvc -// .perform( -// get(getApiUrlForDocs("/v0/organizations")) -// .contentType(MediaType.APPLICATION_JSON) -// .header("Origin", "https://admin.icebang.site") -// .header("Referer", "https://admin.icebang.site/")) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.success").value(true)) -// .andExpect(jsonPath("$.status").value("OK")) -// .andExpect(jsonPath("$.message").value("OK")) -// .andExpect(jsonPath("$.data").isArray()) -// .andDo( -// document( -// "organizations-list", -// preprocessRequest(prettyPrint()), -// preprocessResponse(prettyPrint()), -// resource( -// ResourceSnippetParameters.builder() -// .tag("Organization") -// .summary("조직 목록 조회") -// .description("시스템에 등록된 모든 조직의 목록을 조회합니다") -// .responseFields( -// fieldWithPath("success") -// .type(JsonFieldType.BOOLEAN) -// .description("요청 성공 여부"), -// -// fieldWithPath("data[]").type(JsonFieldType.ARRAY).description("조직 목록"), -// fieldWithPath("data[].id") -// .type(JsonFieldType.NUMBER) -// .description("조직 ID"), -// fieldWithPath("data[].organizationName") -// .type(JsonFieldType.STRING) -// .description("조직명"), -// fieldWithPath("message") -// .type(JsonFieldType.STRING) -// .description("응답 메시지"), -// fieldWithPath("status") -// .type(JsonFieldType.STRING) -// .description("HTTP 상태")) -// .build()))); -// } -// -// @Test -// @DisplayName("조직별 옵션 조회 성공") -// void getOrganizationOptions_success() throws Exception { -// mockMvc -// .perform( -// get(getApiUrlForDocs("/v0/organizations/{orgId}/options"), 1) -// .contentType(MediaType.APPLICATION_JSON) -// .header("Origin", "https://admin.icebang.site") -// .header("Referer", "https://admin.icebang.site/")) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.success").value(true)) -// .andExpect(jsonPath("$.status").value("OK")) -// .andExpect(jsonPath("$.message").value("OK")) -// .andExpect(jsonPath("$.data.departments").isArray()) -// .andExpect(jsonPath("$.data.positions").isArray()) -// .andExpect(jsonPath("$.data.roles").isArray()) -// .andDo( -// document( -// "organization-options", -// preprocessRequest(prettyPrint()), -// preprocessResponse(prettyPrint()), -// resource( -// ResourceSnippetParameters.builder() -// .tag("Organization") -// .summary("조직별 옵션 조회") -// .description("특정 조직의 부서, 직급, 역할 정보를 조회합니다") -// .responseFields( -// fieldWithPath("success") -// .type(JsonFieldType.BOOLEAN) -// .description("요청 성공 여부"), -// fieldWithPath("data") -// .type(JsonFieldType.OBJECT) -// .description("조직 옵션 데이터"), -// fieldWithPath("data.departments[]") -// .type(JsonFieldType.ARRAY) -// .description("부서 목록"), -// fieldWithPath("data.departments[].id") -// .type(JsonFieldType.NUMBER) -// .description("부서 ID"), -// fieldWithPath("data.departments[].name") -// .type(JsonFieldType.STRING) -// .description("부서명"), -// fieldWithPath("data.positions[]") -// .type(JsonFieldType.ARRAY) -// .description("직급 목록"), -// fieldWithPath("data.positions[].id") -// .type(JsonFieldType.NUMBER) -// .description("직급 ID"), -// fieldWithPath("data.positions[].title") -// .type(JsonFieldType.STRING) -// .description("직급명"), -// fieldWithPath("data.roles[]") -// .type(JsonFieldType.ARRAY) -// .description("역할 목록"), -// fieldWithPath("data.roles[].id") -// .type(JsonFieldType.NUMBER) -// .description("역할 ID"), -// fieldWithPath("data.roles[].name") -// .type(JsonFieldType.STRING) -// .description("역할 코드명"), -// fieldWithPath("data.roles[].description") -// .type(JsonFieldType.STRING) -// .description("역할 설명"), -// fieldWithPath("message") -// .type(JsonFieldType.STRING) -// .description("응답 메시지"), -// fieldWithPath("status") -// .type(JsonFieldType.STRING) -// .description("HTTP 상태")) -// .build()))); -// } -// -// @Test -// @DisplayName("사용자 로그인 성공") -// void login_success() throws Exception { -// // given -// Map loginRequest = new HashMap<>(); -// loginRequest.put("email", "admin@icebang.site"); -// loginRequest.put("password", "qwer1234!A"); -// -// // MockMvc로 REST Docs + OpenAPI 생성 -// mockMvc -// .perform( -// post(getApiUrlForDocs("/v0/auth/login")) -// .contentType(MediaType.APPLICATION_JSON) -// .header("Origin", "https://admin.icebang.site") -// .header("Referer", "https://admin.icebang.site/") -// .content(objectMapper.writeValueAsString(loginRequest))) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.success").value(true)) -// .andExpect(jsonPath("$.status").value("OK")) -// .andExpect(jsonPath("$.message").value("OK")) -// .andExpect(jsonPath("$.data").isEmpty()) -// .andDo( -// document( -// "auth-login", -// preprocessRequest(prettyPrint()), -// preprocessResponse(prettyPrint()), -// resource( -// ResourceSnippetParameters.builder() -// .tag("Authentication") -// .summary("사용자 로그인") -// .description("이메일과 비밀번호로 사용자 인증을 수행합니다") -// .requestFields( -// fieldWithPath("email") -// .type(JsonFieldType.STRING) -// .description("사용자 이메일 주소"), -// fieldWithPath("password") -// .type(JsonFieldType.STRING) -// .description("사용자 비밀번호")) -// .responseFields( -// fieldWithPath("success") -// .type(JsonFieldType.BOOLEAN) -// .description("요청 성공 여부"), -// fieldWithPath("data") -// .type(JsonFieldType.NULL) -// .description("응답 데이터 (로그인 성공 시 -// null)"), -// fieldWithPath("message") -// .type(JsonFieldType.STRING) -// .description("응답 메시지"), -// fieldWithPath("status") -// .type(JsonFieldType.STRING) -// .description("HTTP 상태")) -// .build()))); -// } -// -// @Test -// @DisplayName("사용자 회원가입 성공") -// void register_success() throws Exception { -// // given - 먼저 로그인하여 인증 토큰 획득 -// Map loginRequest = new HashMap<>(); -// loginRequest.put("email", "admin@icebang.site"); -// loginRequest.put("password", "qwer1234!A"); -// -// // 로그인 수행 (실제 환경에서는 토큰을 헤더에 추가해야 할 수 있음) -// mockMvc -// .perform( -// post("/v0/auth/login") -// .contentType(MediaType.APPLICATION_JSON) -// .content(objectMapper.writeValueAsString(loginRequest))) -// .andExpect(status().isOk()); -// -// // 회원가입 요청 데이터 -// Map registerRequest = new HashMap<>(); -// registerRequest.put("name", "김철수"); -// registerRequest.put("email", "kim.chulsoo@example.com"); -// registerRequest.put("orgId", 1); -// registerRequest.put("deptId", 2); -// registerRequest.put("positionId", 5); -// registerRequest.put("roleIds", Arrays.asList(6, 7, 8)); -// registerRequest.put("password", null); -// -// // when & then -// mockMvc -// .perform( -// post(getApiUrlForDocs("/v0/auth/register")) -// .contentType(MediaType.APPLICATION_JSON) -// .header("Origin", "https://admin.icebang.site") -// .header("Referer", "https://admin.icebang.site/") -// .content(objectMapper.writeValueAsString(registerRequest))) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.success").value(true)) -// .andExpect(jsonPath("$.status").value("OK")) -// .andExpect(jsonPath("$.message").value("OK")) -// .andDo( -// document( -// "auth-register", -// preprocessRequest(prettyPrint()), -// preprocessResponse(prettyPrint()), -// resource( -// ResourceSnippetParameters.builder() -// .tag("Authentication") -// .summary("사용자 회원가입") -// .description("새로운 사용자를 등록합니다. 관리자 로그인 후에만 사용 -// 가능합니다.") -// .requestFields( -// -// fieldWithPath("name").type(JsonFieldType.STRING).description("사용자 이름"), -// fieldWithPath("email") -// .type(JsonFieldType.STRING) -// .description("사용자 이메일 주소"), -// -// fieldWithPath("orgId").type(JsonFieldType.NUMBER).description("조직 ID"), -// -// fieldWithPath("deptId").type(JsonFieldType.NUMBER).description("부서 ID"), -// fieldWithPath("positionId") -// .type(JsonFieldType.NUMBER) -// .description("직급 ID"), -// fieldWithPath("roleIds[]") -// .type(JsonFieldType.ARRAY) -// .description("역할 ID 목록"), -// fieldWithPath("password") -// .type(JsonFieldType.NULL) -// .description("비밀번호 (null인 경우 시스템에서 -// 자동 생성)") -// .optional()) -// .responseFields( -// fieldWithPath("success") -// .type(JsonFieldType.BOOLEAN) -// .description("요청 성공 여부"), -// fieldWithPath("data") -// .type(JsonFieldType.VARIES) -// .description("응답 데이터 (회원가입 결과 -// 정보)"), -// fieldWithPath("message") -// .type(JsonFieldType.STRING) -// .description("응답 메시지"), -// fieldWithPath("status") -// .type(JsonFieldType.STRING) -// .description("HTTP 상태")) -// .build()))); -// } -// } diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/annotation/IntegrationTest.java b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/setup/annotation/IntegrationTest.java similarity index 87% rename from apps/user-service/src/test/java/com/gltkorea/icebang/integration/annotation/IntegrationTest.java rename to apps/user-service/src/test/java/com/gltkorea/icebang/integration/setup/annotation/IntegrationTest.java index ca4e4046..ec111866 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/annotation/IntegrationTest.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/setup/annotation/IntegrationTest.java @@ -1,4 +1,4 @@ -package com.gltkorea.icebang.integration.annotation; +package com.gltkorea.icebang.integration.setup.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/config/RestDocsConfiguration.java b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/setup/config/RestDocsConfiguration.java similarity index 94% rename from apps/user-service/src/test/java/com/gltkorea/icebang/integration/config/RestDocsConfiguration.java rename to apps/user-service/src/test/java/com/gltkorea/icebang/integration/setup/config/RestDocsConfiguration.java index 319860ad..eeb97ffc 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/config/RestDocsConfiguration.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/setup/config/RestDocsConfiguration.java @@ -1,4 +1,4 @@ -package com.gltkorea.icebang.integration.config; +package com.gltkorea.icebang.integration.setup.config; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/setup/support/IntegrationTestSupport.java b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/setup/support/IntegrationTestSupport.java new file mode 100644 index 00000000..037f37e5 --- /dev/null +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/setup/support/IntegrationTestSupport.java @@ -0,0 +1,35 @@ +package com.gltkorea.icebang.integration.setup.support; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Import; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gltkorea.icebang.integration.setup.annotation.IntegrationTest; +import com.gltkorea.icebang.integration.setup.config.RestDocsConfiguration; + +@IntegrationTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc +@AutoConfigureRestDocs +@Import(RestDocsConfiguration.class) +public abstract class IntegrationTestSupport { + + @Autowired protected MockMvc mockMvc; + + @Autowired protected ObjectMapper objectMapper; + + @LocalServerPort protected int port; + + /** RestDocs에서 실제 API 호출 주소를 표기할 때 사용 */ + protected String getApiUrlForDocs(String path) { + if (path.startsWith("/")) { + return "http://localhost:" + port + path; + } + return "http://localhost:" + port + "/" + path; + } +} diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/support/IntegrationTestSupport.java b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/support/IntegrationTestSupport.java deleted file mode 100644 index 9eb0b356..00000000 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/support/IntegrationTestSupport.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.gltkorea.icebang.integration.support; - -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.restdocs.RestDocumentationExtension; -import org.springframework.test.web.servlet.MockMvc; - -import com.gltkorea.icebang.integration.annotation.IntegrationTest; - -@IntegrationTest -@ExtendWith(RestDocumentationExtension.class) -public abstract class IntegrationTestSupport { - protected MockMvc mockMvc; -} diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/tests/auth/AuthApiIntegrationTest.java b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/tests/auth/AuthApiIntegrationTest.java new file mode 100644 index 00000000..0d1e5d19 --- /dev/null +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/tests/auth/AuthApiIntegrationTest.java @@ -0,0 +1,81 @@ +package com.gltkorea.icebang.integration.tests.auth; + +import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document; +import static com.epages.restdocs.apispec.ResourceDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.*; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.gltkorea.icebang.integration.setup.support.IntegrationTestSupport; + +@Sql( + value = "classpath:sql/01-insert-internal-users.sql", + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) +@Transactional +class AuthApiIntegrationTest extends IntegrationTestSupport { + @Test + @DisplayName("사용자 로그인 성공") + void login_success() throws Exception { + // given + Map loginRequest = new HashMap<>(); + loginRequest.put("email", "admin@icebang.site"); + loginRequest.put("password", "qwer1234!A"); + + // MockMvc로 REST Docs + OpenAPI 생성 + mockMvc + .perform( + post(getApiUrlForDocs("/v0/auth/login")) + .contentType(MediaType.APPLICATION_JSON) + .header("Origin", "https://admin.icebang.site") + .header("Referer", "https://admin.icebang.site/") + .content(objectMapper.writeValueAsString(loginRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value("OK")) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.data").isEmpty()) + .andDo( + document( + "auth-login", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Authentication") + .summary("사용자 로그인") + .description("이메일과 비밀번호로 사용자 인증을 수행합니다") + .requestFields( + fieldWithPath("email") + .type(JsonFieldType.STRING) + .description("사용자 이메일 주소"), + fieldWithPath("password") + .type(JsonFieldType.STRING) + .description("사용자 비밀번호")) + .responseFields( + fieldWithPath("success") + .type(JsonFieldType.BOOLEAN) + .description("요청 성공 여부"), + fieldWithPath("data") + .type(JsonFieldType.NULL) + .description("응답 데이터 (로그인 성공 시 null)"), + fieldWithPath("message") + .type(JsonFieldType.STRING) + .description("응답 메시지"), + fieldWithPath("status") + .type(JsonFieldType.STRING) + .description("HTTP 상태")) + .build()))); + } +} diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/integration/tests/organization/OrganizationApiIntegrationTest.java b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/tests/organization/OrganizationApiIntegrationTest.java new file mode 100644 index 00000000..5d458146 --- /dev/null +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/integration/tests/organization/OrganizationApiIntegrationTest.java @@ -0,0 +1,170 @@ +package com.gltkorea.icebang.integration.tests.organization; + +import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document; +import static com.epages.restdocs.apispec.ResourceDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.*; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.transaction.annotation.Transactional; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.fasterxml.jackson.databind.JsonNode; +import com.gltkorea.icebang.integration.setup.support.IntegrationTestSupport; + +@Sql( + value = { + "classpath:sql/01-insert-internal-users.sql", + "classpath:sql/02-insert-external-users.sql" + }, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) +@Transactional +class OrganizationApiIntegrationTest extends IntegrationTestSupport { + + @Test + @DisplayName("조직 목록 조회 성공") + void getOrganizations_success() throws Exception { + // when & then + mockMvc + .perform( + get(getApiUrlForDocs("/v0/organizations")) + .contentType(MediaType.APPLICATION_JSON) + .header("Origin", "https://admin.icebang.site") + .header("Referer", "https://admin.icebang.site/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value("OK")) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.data").isArray()) + .andExpect(jsonPath("$.data[0].id").exists()) + .andExpect(jsonPath("$.data[0].organizationName").exists()) + .andDo( + document( + "organizations-list", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Organization") + .summary("조직 목록 조회") + .description("시스템에 등록된 모든 조직의 목록을 조회합니다") + .responseFields( + fieldWithPath("success") + .type(JsonFieldType.BOOLEAN) + .description("요청 성공 여부"), + fieldWithPath("data[]").type(JsonFieldType.ARRAY).description("조직 목록"), + fieldWithPath("data[].id") + .type(JsonFieldType.NUMBER) + .description("조직 ID"), + fieldWithPath("data[].organizationName") + .type(JsonFieldType.STRING) + .description("조직명"), + fieldWithPath("message") + .type(JsonFieldType.STRING) + .description("응답 메시지"), + fieldWithPath("status") + .type(JsonFieldType.STRING) + .description("HTTP 상태")) + .build()))); + } + + @Test + @DisplayName("조직 옵션 정보 조회 성공") + void getOrganizationOptions_success() throws Exception { + // given - 먼저 조직 목록을 조회해서 실제 존재하는 ID를 가져옴 + MvcResult organizationsResult = + mockMvc + .perform(get("/v0/organizations").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + String responseBody = organizationsResult.getResponse().getContentAsString(); + JsonNode jsonNode = objectMapper.readTree(responseBody); + JsonNode organizations = jsonNode.get("data"); + + // 첫 번째 조직의 ID를 가져옴 + Long organizationId = organizations.get(0).get("id").asLong(); + + // when & then + mockMvc + .perform( + get(getApiUrlForDocs("/v0/organizations/{organizationId}/options"), organizationId) + .contentType(MediaType.APPLICATION_JSON) + .header("Origin", "https://admin.icebang.site") + .header("Referer", "https://admin.icebang.site/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value("OK")) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.data.departments").isArray()) + .andExpect(jsonPath("$.data.positions").isArray()) + .andExpect(jsonPath("$.data.roles").isArray()) + .andExpect(jsonPath("$.data.departments[0].id").exists()) + .andExpect(jsonPath("$.data.departments[0].name").exists()) + .andExpect(jsonPath("$.data.positions[0].id").exists()) + .andExpect(jsonPath("$.data.positions[0].title").exists()) + .andExpect(jsonPath("$.data.roles[0].id").exists()) + .andExpect(jsonPath("$.data.roles[0].name").exists()) + .andExpect(jsonPath("$.data.roles[0].description").exists()) + .andDo( + document( + "organizations-options", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Organization") + .summary("조직 옵션 정보 조회") + .description("특정 조직의 부서, 직급, 역할 옵션 정보를 조회합니다") + .pathParameters(parameterWithName("organizationId").description("조직 ID")) + .responseFields( + fieldWithPath("success") + .type(JsonFieldType.BOOLEAN) + .description("요청 성공 여부"), + fieldWithPath("data").type(JsonFieldType.OBJECT).description("옵션 데이터"), + fieldWithPath("data.departments[]") + .type(JsonFieldType.ARRAY) + .description("부서 목록"), + fieldWithPath("data.departments[].id") + .type(JsonFieldType.NUMBER) + .description("부서 ID"), + fieldWithPath("data.departments[].name") + .type(JsonFieldType.STRING) + .description("부서명"), + fieldWithPath("data.positions[]") + .type(JsonFieldType.ARRAY) + .description("직급 목록"), + fieldWithPath("data.positions[].id") + .type(JsonFieldType.NUMBER) + .description("직급 ID"), + fieldWithPath("data.positions[].title") + .type(JsonFieldType.STRING) + .description("직급명"), + fieldWithPath("data.roles[]") + .type(JsonFieldType.ARRAY) + .description("역할 목록"), + fieldWithPath("data.roles[].id") + .type(JsonFieldType.NUMBER) + .description("역할 ID"), + fieldWithPath("data.roles[].name") + .type(JsonFieldType.STRING) + .description("역할명"), + fieldWithPath("data.roles[].description") + .type(JsonFieldType.STRING) + .description("역할 설명"), + fieldWithPath("message") + .type(JsonFieldType.STRING) + .description("응답 메시지"), + fieldWithPath("status") + .type(JsonFieldType.STRING) + .description("HTTP 상태")) + .build()))); + } +} diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/unit/annotation/UnitTest.java b/apps/user-service/src/test/java/com/gltkorea/icebang/unit/setup/annotation/UnitTest.java similarity index 88% rename from apps/user-service/src/test/java/com/gltkorea/icebang/unit/annotation/UnitTest.java rename to apps/user-service/src/test/java/com/gltkorea/icebang/unit/setup/annotation/UnitTest.java index 117a5cb2..cb2f975c 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/unit/annotation/UnitTest.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/unit/setup/annotation/UnitTest.java @@ -1,4 +1,4 @@ -package com.gltkorea.icebang.unit.annotation; +package com.gltkorea.icebang.unit.setup.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/unit/support/UnitTestSupport.java b/apps/user-service/src/test/java/com/gltkorea/icebang/unit/setup/support/UnitTestSupport.java similarity index 60% rename from apps/user-service/src/test/java/com/gltkorea/icebang/unit/support/UnitTestSupport.java rename to apps/user-service/src/test/java/com/gltkorea/icebang/unit/setup/support/UnitTestSupport.java index be4c8660..9bc71657 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/unit/support/UnitTestSupport.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/unit/setup/support/UnitTestSupport.java @@ -1,6 +1,6 @@ -package com.gltkorea.icebang.unit.support; +package com.gltkorea.icebang.unit.setup.support; -import com.gltkorea.icebang.unit.annotation.UnitTest; +import com.gltkorea.icebang.unit.setup.annotation.UnitTest; @UnitTest public abstract class UnitTestSupport {