From 815c329e3c2ea1b0b94b805275b09ded661e8373 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Fri, 12 Jun 2026 20:21:01 +0900 Subject: [PATCH] =?UTF-8?q?CI=20-=20=EB=8B=A8=EC=9C=84=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=95=88=EC=A0=95=ED=99=94=20=EB=B0=8F=20e2e=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AccountControllerTest 현 컨트롤러 API 기준 재작성 - Selenium 4종·SignupFileTest @Tag("e2e") 부여 - BaseWebTest headless 옵션, e2e 워크플로 신설 Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/eapim-portal-e2e.yml | 60 ++++++ build.gradle | 8 +- .../apps/user/AccountControllerTest.java | 199 +++++++++--------- .../apim/portal/apps/user/BaseWebTest.java | 3 + .../user/PortalOrgRegistrationRetainTest.java | 2 + .../apps/user/PortalOrgRegistrationTest.java | 2 + .../user/PortalUserRegisterFieldTest.java | 2 + .../apim/portal/apps/user/SignupFileTest.java | 2 + .../apim/portal/apps/user/SignupPageTest.java | 2 + 9 files changed, 183 insertions(+), 97 deletions(-) create mode 100644 .gitea/workflows/eapim-portal-e2e.yml diff --git a/.gitea/workflows/eapim-portal-e2e.yml b/.gitea/workflows/eapim-portal-e2e.yml new file mode 100644 index 0000000..2d33e39 --- /dev/null +++ b/.gitea/workflows/eapim-portal-e2e.yml @@ -0,0 +1,60 @@ +name: eapim-portal E2E Test + +# 수동 트리거 전용. e2e 태그가 붙은 Selenium/통합 테스트만 실행. +# 요구 인프라: +# - Chrome + libgbm/libxkbcommon-x11 (Selenium 4종) +# - Oracle JDBC 또는 testcontainers (SignupFileTest) +# 미설치 환경에서는 SessionNotCreated/DB 에러로 일부 케이스가 실패할 수 있음. + +on: + workflow_dispatch: + +jobs: + e2e: + runs-on: [self-hosted, linux, eapim-portal] + env: + JAVA_HOME: /apps/opts/jdk8 + defaults: + run: + working-directory: eapim-portal + + steps: + - name: Checkout eapim-portal (this branch) + uses: http://172.30.1.50:3000/actions/checkout@v4 + with: + path: eapim-portal + + - name: Checkout elink-portal-common (master, dependency) + working-directory: ${{ github.workspace }} + run: | + rm -rf elink-portal-common + git clone --depth=1 --branch master \ + http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/elink-portal-common.git \ + elink-portal-common + + - name: Checkout elink-online-core-jpa (master, dependency) + working-directory: ${{ github.workspace }} + run: | + mkdir -p eapim-online + rm -rf eapim-online/elink-online-core-jpa + git clone --depth=1 --branch master \ + http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/elink-online-core-jpa.git \ + eapim-online/elink-online-core-jpa + + - name: Verify toolchain + run: | + java -version + gradle --version + + - name: Gradle e2e test + run: gradle test --no-daemon -PincludeE2E=true + + - name: Upload test results + if: always() + uses: http://172.30.1.50:3000/actions/upload-artifact@v3 + with: + name: eapim-portal-e2e-${{ github.sha }} + path: | + eapim-portal/build/reports/tests/test/ + eapim-portal/build/test-results/test/ + if-no-files-found: warn diff --git a/build.gradle b/build.gradle index 6358b69..26be4b9 100644 --- a/build.gradle +++ b/build.gradle @@ -158,7 +158,13 @@ processResources { } test { - useJUnitPlatform() + useJUnitPlatform { + if (project.hasProperty('includeE2E') && project.includeE2E.toBoolean()) { + includeTags 'e2e' + } else { + excludeTags 'e2e' + } + } enabled = true } diff --git a/src/test/java/com/eactive/apim/portal/apps/user/AccountControllerTest.java b/src/test/java/com/eactive/apim/portal/apps/user/AccountControllerTest.java index c0e085e..3a049b9 100644 --- a/src/test/java/com/eactive/apim/portal/apps/user/AccountControllerTest.java +++ b/src/test/java/com/eactive/apim/portal/apps/user/AccountControllerTest.java @@ -1,13 +1,20 @@ package com.eactive.apim.portal.apps.user; +import com.eactive.apim.portal.apps.agreements.service.AgreementsFacade; import com.eactive.apim.portal.apps.user.controller.AccountController; -import com.eactive.apim.portal.apps.user.dto.PasswordChangeRequestDTO; -import com.eactive.apim.portal.apps.user.dto.PortalOrgDTO; -import com.eactive.apim.portal.apps.user.service.PortalUserService; +import com.eactive.apim.portal.apps.user.dto.PortalUserDTO; +import com.eactive.apim.portal.apps.user.facade.AuthFacade; +import com.eactive.apim.portal.apps.user.facade.OrgRegisterFacade; +import com.eactive.apim.portal.apps.user.facade.UserFacade; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.SecurityUtil; -import com.eactive.apim.portal.file.service.FileService; +import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus; +import com.eactive.apim.portal.invitation.repository.UserInvitationRepository; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.ApprovalStatus; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.UserStatus; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -17,29 +24,38 @@ import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.validation.BindingResult; -import org.springframework.validation.Validator; -import org.springframework.web.servlet.ModelAndView; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; @SuppressWarnings("all") @ExtendWith(MockitoExtension.class) class AccountControllerTest { @Mock - private Validator validator; + private UserFacade userFacade; @Mock - private PortalUserService userService; + private OrgRegisterFacade orgRegisterFacade; @Mock - private FileService fileService; + private AgreementsFacade agreementsFacade; + + @Mock + private AuthFacade authFacade; + + @Mock + private UserInvitationRepository userInvitationRepository; @InjectMocks private AccountController accountController; @@ -52,110 +68,101 @@ class AccountControllerTest { } @Test - void testChangePassword() throws Exception { - mockMvc.perform(get("/change_password.do")) + void testShowChangePasswordPage() throws Exception { + mockMvc.perform(get("/change_password")) .andExpect(status().isOk()) - .andExpect(view().name("apps/main/myUpdatePw")) + .andExpect(view().name("apps/mypage/passwordChangeEntry")) .andExpect(model().attributeExists("passwordChangeRequest")); } @Test - void testUpdatePassword() throws Exception { - PasswordChangeRequestDTO dto = new PasswordChangeRequestDTO(); - dto.setOldPassword("old"); - dto.setNewPassword("new"); - dto.setNewPassword2("new"); - + void testUpdatePasswordRedirectsToLogin() throws Exception { try (MockedStatic securityUtil = mockStatic(SecurityUtil.class)) { securityUtil.when(SecurityUtil::getCurrentLoginId).thenReturn("testUser"); -// when(userService.findByLoginId("testUser")).thenReturn(true); + mockMvc.perform(post("/mypage/change_new_password") + .param("newPassword", "NewPass!1") + .param("confirmPassword", "NewPass!1")) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/login")); + } + } - mockMvc.perform(post("/update_password.do") - .flashAttr("passwordChangeRequest", dto)) + @Test + void testUpdatePasswordWithIllegalArgument() throws Exception { + try (MockedStatic securityUtil = mockStatic(SecurityUtil.class)) { + securityUtil.when(SecurityUtil::getCurrentLoginId).thenReturn("testUser"); + doThrow(new IllegalArgumentException("invalid password")) + .when(userFacade).updatePassword(eq("testUser"), anyString(), anyString()); + + mockMvc.perform(post("/mypage/change_new_password") + .param("newPassword", "NewPass!1") + .param("confirmPassword", "Different!1")) .andExpect(status().isOk()) - .andExpect(view().name("apps/main/myUpdatePw")) + .andExpect(view().name("apps/mypage/passwordChange")) + .andExpect(model().attributeExists("error")) .andExpect(model().attributeExists("passwordChangeRequest")); - - verify(validator).validate(any(), any()); } } @Test - void testUpdatePasswordWithErrors() throws Exception { - PasswordChangeRequestDTO dto = new PasswordChangeRequestDTO(); - dto.setOldPassword("old"); - dto.setNewPassword("new"); - dto.setNewPassword2("different"); + void testMypageForUser() throws Exception { + PortalAuthenticatedUser authenticatedUser = new PortalAuthenticatedUser(); + authenticatedUser.setId("user-1"); + authenticatedUser.setLoginId("testUser"); + authenticatedUser.setRoleCode(RoleCode.ROLE_USER); + authenticatedUser.setUserStatus(UserStatus.ACTIVE); + authenticatedUser.setApprovalStatus(ApprovalStatus.COMPLETED); - doAnswer(invocation -> { - BindingResult bindingResult = invocation.getArgument(1); - bindingResult.rejectValue("newPassword2", "error.password", "Passwords do not match"); - return null; - }).when(validator).validate(any(), any(BindingResult.class)); + PortalUserDTO dto = new PortalUserDTO(); + dto.setId("user-1"); + dto.setLoginId("testUser"); + dto.setUserName("테스트유저"); + dto.setMobileNumber("01012345678"); + dto.setEmailAddr("test@example.com"); + dto.setRoleCode(RoleCode.ROLE_USER); - mockMvc.perform(post("/update_password.do") - .flashAttr("passwordChangeRequest", dto)) - .andExpect(status().isOk()) - .andExpect(view().name("apps/main/myUpdatePw")) - .andExpect(model().attributeExists("errors")); - } - - @Test - void testMypage() { try (MockedStatic securityUtil = mockStatic(SecurityUtil.class)) { - PortalAuthenticatedUser user = new PortalAuthenticatedUser(); - securityUtil.when(SecurityUtil::getPortalAuthenticatedUser).thenReturn(user); + securityUtil.when(SecurityUtil::getPortalAuthenticatedUser).thenReturn(authenticatedUser); + when(userFacade.findById("user-1")).thenReturn(dto); + when(userInvitationRepository.findFirstByInvitationEmailAndStatus( + "test@example.com", InvitationStatus.PENDING)) + .thenReturn(Optional.empty()); - ModelAndView mav = accountController.mypage(); - - assertEquals("apps/main/myPageEnt", mav.getViewName()); - assertEquals(user, mav.getModel().get("user")); - } - } - - @Test - void testMyCompanyInfo() throws Exception { - try (MockedStatic securityUtil = mockStatic(SecurityUtil.class)) { - securityUtil.when(SecurityUtil::getPortalAuthenticatedUser).thenReturn(new PortalAuthenticatedUser()); - - mockMvc.perform(get("/my_company_info.do")) + mockMvc.perform(get("/mypage")) .andExpect(status().isOk()) - .andExpect(view().name("apps/main/myPageEntCompany")) + .andExpect(view().name("apps/mypage/updatePersonalUser")) + .andExpect(model().attributeExists("user")) + .andExpect(model().attribute("loginId", "testUser")) + .andExpect(model().attribute("hasPendingInvitation", false)); + } + } + + @Test + void testMypageForCorporateManager() throws Exception { + PortalAuthenticatedUser authenticatedUser = new PortalAuthenticatedUser(); + authenticatedUser.setId("manager-1"); + authenticatedUser.setLoginId("manager"); + authenticatedUser.setRoleCode(RoleCode.ROLE_CORP_MANAGER); + authenticatedUser.setUserStatus(UserStatus.ACTIVE); + authenticatedUser.setApprovalStatus(ApprovalStatus.COMPLETED); + + PortalUserDTO dto = new PortalUserDTO(); + dto.setId("manager-1"); + dto.setLoginId("manager"); + dto.setUserName("매니저"); + dto.setMobileNumber("01098765432"); + dto.setEmailAddr("manager@example.com"); + dto.setRoleCode(RoleCode.ROLE_CORP_MANAGER); + + try (MockedStatic securityUtil = mockStatic(SecurityUtil.class)) { + securityUtil.when(SecurityUtil::getPortalAuthenticatedUser).thenReturn(authenticatedUser); + when(userFacade.findById("manager-1")).thenReturn(dto); + + mockMvc.perform(get("/mypage")) + .andExpect(status().isOk()) + .andExpect(view().name("apps/mypage/updateCorporateManager")) .andExpect(model().attributeExists("user")); } } - - @Test - void testUpdateCompanyInfo() throws Exception { - PortalOrgDTO dto = new PortalOrgDTO(); - dto.setOrgName("Test Company"); - - try (MockedStatic securityUtil = mockStatic(SecurityUtil.class)) { - securityUtil.when(SecurityUtil::getCurrentLoginId).thenReturn("testUser"); - - mockMvc.perform(post("/update_my_company_info.do") - .flashAttr("user", dto)) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/my_company_info.do")); - - verify(validator).validate(eq(dto), any()); - } - } - - @Test - void testUpdateCompanyInfoWithErrors() throws Exception { - PortalOrgDTO dto = new PortalOrgDTO(); - - doAnswer(invocation -> { - BindingResult bindingResult = invocation.getArgument(1); - bindingResult.rejectValue("companyName", "error.companyName", "Company name is required"); - return null; - }).when(validator).validate(any(), any(BindingResult.class)); - - mockMvc.perform(post("/update_my_company_info.do") - .flashAttr("user", dto)) - .andExpect(status().isOk()) - .andExpect(view().name("apps/main/myPageEntCompany")); - } } diff --git a/src/test/java/com/eactive/apim/portal/apps/user/BaseWebTest.java b/src/test/java/com/eactive/apim/portal/apps/user/BaseWebTest.java index a1f0776..42a1cb8 100644 --- a/src/test/java/com/eactive/apim/portal/apps/user/BaseWebTest.java +++ b/src/test/java/com/eactive/apim/portal/apps/user/BaseWebTest.java @@ -40,6 +40,9 @@ public abstract class BaseWebTest { options.addArguments("--no-sandbox"); options.addArguments("--disable-dev-shm-usage"); options.addArguments("--remote-allow-origins=*"); + options.addArguments("--headless=new"); + options.addArguments("--disable-gpu"); + options.addArguments("--window-size=1280,800"); ChromeDriverService service = new ChromeDriverService.Builder() .usingAnyFreePort() diff --git a/src/test/java/com/eactive/apim/portal/apps/user/PortalOrgRegistrationRetainTest.java b/src/test/java/com/eactive/apim/portal/apps/user/PortalOrgRegistrationRetainTest.java index 3254c3d..5be3588 100644 --- a/src/test/java/com/eactive/apim/portal/apps/user/PortalOrgRegistrationRetainTest.java +++ b/src/test/java/com/eactive/apim/portal/apps/user/PortalOrgRegistrationRetainTest.java @@ -8,6 +8,7 @@ import java.io.FileWriter; import java.io.IOException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.openqa.selenium.By; @@ -17,6 +18,7 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; @SuppressWarnings("all") +@Tag("e2e") @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class PortalOrgRegistrationRetainTest extends BaseWebTest { diff --git a/src/test/java/com/eactive/apim/portal/apps/user/PortalOrgRegistrationTest.java b/src/test/java/com/eactive/apim/portal/apps/user/PortalOrgRegistrationTest.java index a0e6086..55c3f08 100644 --- a/src/test/java/com/eactive/apim/portal/apps/user/PortalOrgRegistrationTest.java +++ b/src/test/java/com/eactive/apim/portal/apps/user/PortalOrgRegistrationTest.java @@ -8,6 +8,7 @@ import java.io.FileWriter; import java.io.IOException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.openqa.selenium.By; @@ -17,6 +18,7 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; @SuppressWarnings("all") +@Tag("e2e") @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class PortalOrgRegistrationTest extends BaseWebTest { diff --git a/src/test/java/com/eactive/apim/portal/apps/user/PortalUserRegisterFieldTest.java b/src/test/java/com/eactive/apim/portal/apps/user/PortalUserRegisterFieldTest.java index dd20fdc..ed2f350 100644 --- a/src/test/java/com/eactive/apim/portal/apps/user/PortalUserRegisterFieldTest.java +++ b/src/test/java/com/eactive/apim/portal/apps/user/PortalUserRegisterFieldTest.java @@ -7,6 +7,7 @@ import io.github.bonigarcia.wdm.WebDriverManager; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.openqa.selenium.By; @@ -14,6 +15,7 @@ import org.openqa.selenium.WebElement; import static org.junit.jupiter.api.Assertions.assertTrue; +@Tag("e2e") @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class PortalUserRegisterFieldTest extends BaseWebTest{ diff --git a/src/test/java/com/eactive/apim/portal/apps/user/SignupFileTest.java b/src/test/java/com/eactive/apim/portal/apps/user/SignupFileTest.java index 8813ea4..97589de 100644 --- a/src/test/java/com/eactive/apim/portal/apps/user/SignupFileTest.java +++ b/src/test/java/com/eactive/apim/portal/apps/user/SignupFileTest.java @@ -7,6 +7,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import com.eactive.apim.portal.PortalApplication; import java.util.Objects; import javax.servlet.http.Cookie; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -17,6 +18,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; @SuppressWarnings("all") +@Tag("e2e") @SpringBootTest(classes = PortalApplication.class) @ActiveProfiles({"dev", "unittest"}) @AutoConfigureMockMvc diff --git a/src/test/java/com/eactive/apim/portal/apps/user/SignupPageTest.java b/src/test/java/com/eactive/apim/portal/apps/user/SignupPageTest.java index 3136e9e..df33b24 100644 --- a/src/test/java/com/eactive/apim/portal/apps/user/SignupPageTest.java +++ b/src/test/java/com/eactive/apim/portal/apps/user/SignupPageTest.java @@ -7,12 +7,14 @@ import java.nio.file.Paths; import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.TestFactory; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; @SuppressWarnings("all") +@Tag("e2e") class SignupPageTest extends BaseWebTest { private final String USER_ID_FILE = "user_id_counter.txt";