- AppRequestRepository에 findAllByOrgAndClientName 메서드 추가
- 클라이언트 신청 해지 프로세스 및 테스트 클린업 로직 구현 - Playwright API 키 관리/클라이언트 관리 경로 및 셀렉터 업데이트
This commit is contained in:
@@ -41,7 +41,13 @@ public class ApprovalService {
|
||||
approval.setApprovalType(ApprovalType.APP);
|
||||
approval.setTargetId(request.getId());
|
||||
approval.setRequester(SecurityUtil.getPortalAuthenticatedUser());
|
||||
approval.setApprovalSubject("[" + request.getOrg().getOrgName() + "] " + request.getType().getDescription() + " 승인");
|
||||
// 동일 법인의 신규/변경/해지 요청이 관리자 목록에서 같은 제목으로 보이면 대상 식별이 불가능하다.
|
||||
// 클라이언트 이름을 제목에 포함해 운영자와 E2E 모두 정확한 승인 건을 검색할 수 있게 한다.
|
||||
String clientName = request.getClientName();
|
||||
String clientNamePart = clientName == null || clientName.trim().isEmpty()
|
||||
? "" : " [" + clientName.trim() + "]";
|
||||
approval.setApprovalSubject("[" + request.getOrg().getOrgName() + "]" + clientNamePart
|
||||
+ " " + request.getType().getDescription() + " 승인");
|
||||
|
||||
for (PortalApprovalLineUser user : optLine.get().getPortalApprovalLineUsers()) {
|
||||
this.addApprover(approval, user.getUser(), user.getApprovalOrder());
|
||||
|
||||
+68
@@ -5,11 +5,14 @@ import com.eactive.apim.portal.common.util.IpAddressMatcher;
|
||||
import com.eactive.apim.portal.djb.testcleanup.service.OrphanCleanupService;
|
||||
import com.eactive.apim.portal.djb.testcleanup.service.TestCleanupResult;
|
||||
import com.eactive.apim.portal.djb.testcleanup.service.TestCleanupService;
|
||||
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
|
||||
import com.eactive.apim.portal.portaluser.entity.PortalUser;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -131,6 +134,71 @@ public class TestCleanupInternalController {
|
||||
return ResponseEntity.ok(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* 2000 실행 전 법인 계정의 실제 선행 상태를 조회한다. 조회 전용이며 cleanup을 수행하지 않는다.
|
||||
* API 신청 권한의 최종 렌더링 여부는 보안 권한 매핑에 따르므로, E2E는 이 응답으로 데이터 선행조건을
|
||||
* 확인한 다음 /clients 화면의 생성 버튼 노출까지 함께 검증한다.
|
||||
*/
|
||||
@GetMapping("/user/status")
|
||||
public ResponseEntity<Map<String, Object>> getUserStatus(
|
||||
@RequestParam String email, HttpServletRequest request) {
|
||||
ResponseEntity<Map<String, Object>> guardFailure = checkGuards(request);
|
||||
if (guardFailure != null) {
|
||||
return guardFailure;
|
||||
}
|
||||
if (StringUtils.isBlank(email)) {
|
||||
return badRequest("email 은 필수입니다.");
|
||||
}
|
||||
|
||||
Optional<PortalUser> userOpt = testCleanupService.findUserForStatus(email);
|
||||
Map<String, Object> body = baseBody();
|
||||
body.put("email", email);
|
||||
body.put("userFound", userOpt.isPresent());
|
||||
if (userOpt.isPresent()) {
|
||||
PortalUser user = userOpt.get();
|
||||
PortalOrg org = user.getPortalOrg();
|
||||
body.put("userId", user.getId());
|
||||
body.put("roleCode", user.getRoleCode() == null ? null : user.getRoleCode().name());
|
||||
body.put("userStatus", user.getUserStatus() == null ? null : user.getUserStatus().name());
|
||||
body.put("userApprovalStatus", user.getApprovalStatus() == null ? null : user.getApprovalStatus().name());
|
||||
body.put("authCompletedYn", user.getAuthCompletedYn());
|
||||
body.put("orgId", org == null ? null : org.getId());
|
||||
body.put("orgStatus", org == null || org.getOrgStatus() == null ? null : org.getOrgStatus().name());
|
||||
body.put("orgApprovalStatus", org == null || org.getApprovalStatus() == null
|
||||
? null : org.getApprovalStatus().name());
|
||||
}
|
||||
log.info("테스트 정리(user status) 조회 - email: {}, found: {}, from: {}",
|
||||
email, userOpt.isPresent(), request.getRemoteAddr());
|
||||
return ResponseEntity.ok(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* 2000 재실행 전 동일 이름으로 남은 테스트 클라이언트 신청만 취소한다.
|
||||
* 대상은 {@code 단위테스트앱-} 접두사와 이메일 소속 법인으로 이중 한정한다.
|
||||
*/
|
||||
@PostMapping("/app-request")
|
||||
public ResponseEntity<Map<String, Object>> cancelPendingTestAppRequests(
|
||||
@RequestParam String email, @RequestParam String clientName, HttpServletRequest request) {
|
||||
ResponseEntity<Map<String, Object>> guardFailure = checkGuards(request);
|
||||
if (guardFailure != null) {
|
||||
return guardFailure;
|
||||
}
|
||||
if (StringUtils.isBlank(email) || StringUtils.isBlank(clientName)) {
|
||||
return badRequest("email 과 clientName 은 필수입니다.");
|
||||
}
|
||||
|
||||
TestCleanupResult result = testCleanupService.cancelPendingTestAppRequests(email, clientName);
|
||||
log.info("테스트 정리(app request) 실행 - email: {}, clientName: {}, found: {}, from: {}",
|
||||
email, clientName, result.isFound(), request.getRemoteAddr());
|
||||
|
||||
Map<String, Object> body = baseBody();
|
||||
body.put("email", email);
|
||||
body.put("clientName", clientName);
|
||||
body.put("appRequestFound", result.isFound());
|
||||
body.put("deletedCounts", result.getDeletedCounts());
|
||||
return ResponseEntity.ok(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴대폰 번호로 남아 있는 초대 레코드를 삭제한다. 1020 재실행 전 초대중 중복을 정리하는 용도다.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.eactive.apim.portal.djb.testcleanup.service;
|
||||
|
||||
import com.eactive.apim.portal.app.repository.CredentialRepository;
|
||||
import com.eactive.apim.portal.apprequest.entity.AppRequest;
|
||||
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
|
||||
import com.eactive.apim.portal.approval.statemachine.ProcessingState;
|
||||
import com.eactive.apim.portal.approval.statemachine.RequestedState;
|
||||
import com.eactive.apim.portal.apps.approval.service.ApprovalService;
|
||||
import com.eactive.apim.portal.apps.community.partnership.repository.PartnershipApplicationRepository;
|
||||
import com.eactive.apim.portal.apps.community.qna.repository.InquiryRepository;
|
||||
import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository;
|
||||
@@ -56,6 +61,8 @@ public class TestCleanupService {
|
||||
private final UserPasswordHistoryRepository userPasswordHistoryRepository;
|
||||
private final UserLogRepository userLogRepository;
|
||||
private final CredentialRepository credentialRepository;
|
||||
private final AppRequestRepository appRequestRepository;
|
||||
private final ApprovalService approvalService;
|
||||
private final WebhookRequestRepository webhookRequestRepository;
|
||||
private final WebhookRequestApiRepository webhookRequestApiRepository;
|
||||
private final WebhookRequestEventRepository webhookRequestEventRepository;
|
||||
@@ -142,6 +149,47 @@ public class TestCleanupService {
|
||||
.orElseGet(TestCleanupResult::notFound);
|
||||
}
|
||||
|
||||
/**
|
||||
* E2E 실행 가능 여부를 판단하기 위한 계정 조회다. 삭제나 상태 변경은 수행하지 않는다.
|
||||
* 호출자는 법인 소속/승인/역할을 확인해 2000 선행 시나리오(1010/1011)가 갖춰졌는지 판단한다.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<PortalUser> findUserForStatus(String email) {
|
||||
assertNonProdProfile();
|
||||
|
||||
return portalUserRepository.findPortalUserByEmailAddr(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* 동일 이름으로 재실행할 때 남은 테스트 APP 신청만 취소한다.
|
||||
* 운영 데이터 오삭제를 막기 위해 {@code 단위테스트앱-} 접두사, 이메일의 법인 소속, 정확한 클라이언트명 세 조건을 모두 요구한다.
|
||||
*/
|
||||
public TestCleanupResult cancelPendingTestAppRequests(String email, String clientName) {
|
||||
assertNonProdProfile();
|
||||
if (clientName == null || !clientName.startsWith("단위테스트앱-")) {
|
||||
throw new IllegalArgumentException("테스트 앱 이름(단위테스트앱-*)만 정리할 수 있습니다.");
|
||||
}
|
||||
|
||||
Optional<PortalUser> userOpt = portalUserRepository.findPortalUserByEmailAddr(email);
|
||||
if (!userOpt.isPresent() || userOpt.get().getPortalOrg() == null) {
|
||||
return TestCleanupResult.notFound();
|
||||
}
|
||||
|
||||
TestCleanupResult result = TestCleanupResult.notFound();
|
||||
long cancelled = 0L;
|
||||
for (AppRequest request : appRequestRepository.findAllByOrgAndClientName(userOpt.get().getPortalOrg(), clientName)) {
|
||||
if (request.getApproval() == null || !(request.getApproval().getApprovalStatus() instanceof RequestedState
|
||||
|| request.getApproval().getApprovalStatus() instanceof ProcessingState)) {
|
||||
continue;
|
||||
}
|
||||
approvalService.cancelAppApproval(request);
|
||||
result = TestCleanupResult.found(request.getId());
|
||||
cancelled++;
|
||||
}
|
||||
result.put("PTL_APP_REQUEST_CANCELLED", cancelled);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴대폰 번호로 남은 초대 레코드를 전부 삭제한다. 1020 재실행 전 PENDING 초대 중복을 방지한다.
|
||||
*/
|
||||
|
||||
+39
-1
@@ -14,6 +14,11 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.eactive.apim.portal.app.repository.CredentialRepository;
|
||||
import com.eactive.apim.portal.apprequest.entity.AppRequest;
|
||||
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
|
||||
import com.eactive.apim.portal.approval.entity.Approval;
|
||||
import com.eactive.apim.portal.approval.statemachine.RequestedState;
|
||||
import com.eactive.apim.portal.apps.approval.service.ApprovalService;
|
||||
import com.eactive.apim.portal.apps.community.partnership.repository.PartnershipApplicationRepository;
|
||||
import com.eactive.apim.portal.apps.community.qna.repository.InquiryRepository;
|
||||
import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository;
|
||||
@@ -58,6 +63,8 @@ class TestCleanupServiceTest {
|
||||
@Mock private UserPasswordHistoryRepository userPasswordHistoryRepository;
|
||||
@Mock private UserLogRepository userLogRepository;
|
||||
@Mock private CredentialRepository credentialRepository;
|
||||
@Mock private AppRequestRepository appRequestRepository;
|
||||
@Mock private ApprovalService approvalService;
|
||||
@Mock private WebhookRequestRepository webhookRequestRepository;
|
||||
@Mock private WebhookRequestApiRepository webhookRequestApiRepository;
|
||||
@Mock private WebhookRequestEventRepository webhookRequestEventRepository;
|
||||
@@ -78,7 +85,8 @@ class TestCleanupServiceTest {
|
||||
service = new TestCleanupService(environment, portalOrgRepository, portalUserRepository,
|
||||
inquiryRepository, inquiryCommentRepository, partnershipApplicationRepository,
|
||||
userRoleHistoryRepository, portalUserPrivacyAgreementRepository, userPasswordHistoryRepository,
|
||||
userLogRepository, credentialRepository, webhookRequestRepository, webhookRequestApiRepository,
|
||||
userLogRepository, credentialRepository, appRequestRepository, approvalService,
|
||||
webhookRequestRepository, webhookRequestApiRepository,
|
||||
webhookRequestEventRepository, webhookService, userInvitationRepository, nativeQueries);
|
||||
when(environment.acceptsProfiles(Profiles.of("stage", "prod"))).thenReturn(false);
|
||||
}
|
||||
@@ -226,6 +234,36 @@ class TestCleanupServiceTest {
|
||||
verify(portalUserRepository, never()).delete(target);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelPendingTestAppRequests_cancelsOnlyExactTestAppRequest() {
|
||||
PortalUser target = user();
|
||||
PortalOrg org = org();
|
||||
target.setPortalOrg(org);
|
||||
AppRequest request = new AppRequest();
|
||||
request.setId("APP_REQ_1");
|
||||
request.setClientName("단위테스트앱-20260824-144907");
|
||||
Approval approval = new Approval();
|
||||
approval.setApprovalStatus(new RequestedState());
|
||||
request.setApproval(approval);
|
||||
when(portalUserRepository.findPortalUserByEmailAddr(EMAIL)).thenReturn(Optional.of(target));
|
||||
when(appRequestRepository.findAllByOrgAndClientName(org, request.getClientName()))
|
||||
.thenReturn(Collections.singletonList(request));
|
||||
|
||||
TestCleanupResult result = service.cancelPendingTestAppRequests(EMAIL, request.getClientName());
|
||||
|
||||
assertTrue(result.isFound());
|
||||
assertEquals("APP_REQ_1", result.getTargetId());
|
||||
assertEquals(1L, result.getDeletedCounts().get("PTL_APP_REQUEST_CANCELLED"));
|
||||
verify(approvalService).cancelAppApproval(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelPendingTestAppRequests_rejectsNonTestName() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> service.cancelPendingTestAppRequests(EMAIL, "운영앱"));
|
||||
verifyNoInteractions(appRequestRepository, approvalService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteUserCascade_found_deletesOrphanDataAndUser() {
|
||||
PortalUser target = user();
|
||||
|
||||
Reference in New Issue
Block a user