优化
This commit is contained in:
@@ -76,6 +76,12 @@ class IamOpenApiContractTest {
|
||||
.contains("id: {type: string, pattern: '^\\d+$'")
|
||||
.contains("/api/platform/v1/iam/tenants/{tenantId}/addresses:")
|
||||
.contains("/api/platform/v1/iam/tenants/{tenantId}/qualifications:")
|
||||
.contains("/api/platform/v1/iam/tenants/{tenantId}/qualifications/{qualificationId}:")
|
||||
.contains("/api/merchant/v1/iam/tenant/qualifications/{qualificationId}:")
|
||||
.contains("originalFileName: {type: string, description: 上传时原始文件名;前端不得从 URL 或内部 ID 推断}")
|
||||
.contains("updatedAt: {type: string, format: date-time}")
|
||||
.contains("仅客户状态为 `PROFILE_REQUIRED` 或 `REJECTED` 时允许")
|
||||
.contains("merchant:qualification:upload")
|
||||
.contains("platform:customer:list")
|
||||
.doesNotContain("platform:customer:view")
|
||||
.contains("TenantAddressListApiResponse:")
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baiyue.common.security.AuthenticatedUser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@@ -17,6 +18,22 @@ class PlatformCustomerResourceContractTest {
|
||||
"/api/platform/v1/iam/tenants/{tenantId}/addresses");
|
||||
assertListPermissionContract("platformQualifications",
|
||||
"/api/platform/v1/iam/tenants/{tenantId}/qualifications");
|
||||
Method platformDetail = ProfileResourceController.class.getMethod(
|
||||
"platformQualification", long.class, long.class, AuthenticatedUser.class);
|
||||
assertEquals("/api/platform/v1/iam/tenants/{tenantId}/qualifications/{qualificationId}",
|
||||
platformDetail.getAnnotation(GetMapping.class).value()[0]);
|
||||
assertEquals("hasAuthority('platform:customer:list')",
|
||||
platformDetail.getAnnotation(PreAuthorize.class).value());
|
||||
Method merchantDetail = ProfileResourceController.class.getMethod(
|
||||
"qualification", long.class, AuthenticatedUser.class);
|
||||
assertEquals("hasAuthority('merchant:profile:view')",
|
||||
merchantDetail.getAnnotation(PreAuthorize.class).value());
|
||||
Method merchantDelete = ProfileResourceController.class.getMethod(
|
||||
"deleteQualification", long.class, AuthenticatedUser.class);
|
||||
assertEquals("/api/merchant/v1/iam/tenant/qualifications/{qualificationId}",
|
||||
merchantDelete.getAnnotation(DeleteMapping.class).value()[0]);
|
||||
assertEquals("hasAuthority('merchant:qualification:upload')",
|
||||
merchantDelete.getAnnotation(PreAuthorize.class).value());
|
||||
}
|
||||
|
||||
private void assertListPermissionContract(String methodName, String path) throws Exception {
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ class PlatformTenantControllerContractTest {
|
||||
assertNotNull(annotation(method, 0, Min.class));
|
||||
assertNotNull(annotation(method, 1, Min.class));
|
||||
assertEquals(200, annotation(method, 1, Max.class).value());
|
||||
assertEquals("PROFILE_REQUIRED|PENDING|ACTIVE|REJECTED|DISABLED|ARCHIVED",
|
||||
assertEquals("PROFILE_REQUIRED|PENDING|ACTIVE|REJECTED|DISABLED",
|
||||
annotation(method, 3, Pattern.class).regexp());
|
||||
|
||||
Set<String> statuses = Arrays.stream(method.getAnnotation(ApiResponses.class).value())
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.baiyue.iam.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ArchivedCustomerListContractTest {
|
||||
@Test
|
||||
void customerListAndExportExcludeArchivedTenants() throws Exception {
|
||||
String source = Files.readString(Path.of(
|
||||
"src/main/java/com/baiyue/iam/repository/TenantRepository.java"));
|
||||
|
||||
assertThat(source.split("\\.ne\\(IamTenant::getStatus, \\\"ARCHIVED\\\"\\)", -1))
|
||||
.hasSizeGreaterThanOrEqualTo(3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.baiyue.iam.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class QualificationSchemaContractTest {
|
||||
@Test
|
||||
void baselineAndUnifiedSqlPersistTheUploadOriginalFileName() throws Exception {
|
||||
String baseline = new ClassPathResource("db/iam.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
Path unifiedPath = Path.of("deploy", "sql", "头程数据库统一增量-MySQL8.sql");
|
||||
if (!Files.exists(unifiedPath)) {
|
||||
unifiedPath = Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql");
|
||||
}
|
||||
String unified = Files.readString(unifiedPath, StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(baseline).contains("original_file_name VARCHAR(255) NOT NULL COMMENT '上传时原始文件名'");
|
||||
assertThat(unified).contains("original_file_name VARCHAR(255) NOT NULL COMMENT '上传时原始文件名'");
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import jakarta.annotation.Resource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -103,6 +104,52 @@ class MerchantOnboardingServiceTest {
|
||||
assertNotNull(result.tenantId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientRegistrationAcceptsAnOmittedLegalRepresentative() {
|
||||
IamRole ownerRole = new IamRole();
|
||||
ownerRole.setId(4000100L);
|
||||
ownerRole.setTenantId(0L);
|
||||
ownerRole.setAppCode("MERCHANT");
|
||||
ownerRole.setRoleCode("MERCHANT_OWNER");
|
||||
ownerRole.setStatus("ACTIVE");
|
||||
when(roles.selectOne(any())).thenReturn(ownerRole);
|
||||
|
||||
service.register(new MerchantRegistrationRequest(
|
||||
9001L, "注册人", "测试企业", null, "13800138000",
|
||||
"owner@example.com", "CN", "深圳市宝安区"));
|
||||
|
||||
ArgumentCaptor<IamTenant> tenant = ArgumentCaptor.forClass(IamTenant.class);
|
||||
verify(tenants).insert(tenant.capture());
|
||||
assertNull(tenant.getValue().getLegalRepresentative());
|
||||
}
|
||||
|
||||
@Test
|
||||
void registrationLookupReturnsTheExistingQualificationDraftState() {
|
||||
IamUser user = new IamUser();
|
||||
user.setId(20001L);
|
||||
user.setAuthUserId(9001L);
|
||||
IamMembership membership = new IamMembership();
|
||||
membership.setId(30001L);
|
||||
membership.setTenantId(10001L);
|
||||
membership.setUserId(20001L);
|
||||
membership.setMemberType("OWNER");
|
||||
IamTenant tenant = new IamTenant();
|
||||
tenant.setId(10001L);
|
||||
tenant.setTenantType("MERCHANT");
|
||||
tenant.setStatus("PROFILE_REQUIRED");
|
||||
tenant.setDimensionInitializationStatus("READY");
|
||||
when(users.selectOne(any())).thenReturn(user);
|
||||
when(memberships.selectOne(any())).thenReturn(membership);
|
||||
when(tenants.selectById(10001L)).thenReturn(tenant);
|
||||
|
||||
var result = service.registration(9001L);
|
||||
|
||||
assertEquals(10001L, result.tenantId());
|
||||
assertEquals(30001L, result.membershipId());
|
||||
assertEquals("PROFILE_REQUIRED", result.tenantStatus());
|
||||
assertEquals("READY", result.dimensionInitializationStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void compensationRemovesOnlyAnUnstartedRegistration() {
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -43,7 +44,8 @@ class MerchantQualificationUploadTest {
|
||||
|
||||
var response = service.submitQualification(new QualificationSubmissionRequest(
|
||||
10001L, "FREIGHT_FORWARDER_CERTIFICATE", null,
|
||||
"/api/merchant/v1/files/qualifications/freight-forwarder.png", LocalDate.of(2030, 1, 1), false));
|
||||
"/api/merchant/v1/files/qualifications/freight-forwarder.png", "freight-forwarder.png",
|
||||
LocalDate.of(2030, 1, 1), false));
|
||||
|
||||
assertEquals("PROFILE_REQUIRED", response.tenantStatus());
|
||||
assertNull(response.reviewId());
|
||||
@@ -68,6 +70,7 @@ class MerchantQualificationUploadTest {
|
||||
var response = service.submitQualification(new QualificationSubmissionRequest(
|
||||
10001L, "LEGAL_PERSON_ID", null,
|
||||
"/api/merchant/v1/files/qualifications/new-legal-person.png",
|
||||
"new-legal-person.png",
|
||||
LocalDate.of(2035, 12, 31), true));
|
||||
|
||||
assertEquals("/api/merchant/v1/files/qualifications/old-legal-person.png",
|
||||
@@ -92,7 +95,7 @@ class MerchantQualificationUploadTest {
|
||||
when(tenantRepository.findTenantForUpdate(10001L)).thenReturn(tenant);
|
||||
assertThrows(BizException.class, () -> service.submitQualification(new QualificationSubmissionRequest(
|
||||
10001L, "BUSINESS_LICENSE", "91310101abcde12345",
|
||||
"/api/merchant/v1/files/qualifications/license.png", null, false)));
|
||||
"/api/merchant/v1/files/qualifications/license.png", "license.png", null, false)));
|
||||
|
||||
verify(profileRepository, never()).insertQualification(any());
|
||||
}
|
||||
@@ -108,8 +111,8 @@ class MerchantQualificationUploadTest {
|
||||
|
||||
var response = service.submitRequiredQualificationPackage(new RequiredQualificationPackageRequest(
|
||||
10001L, "91310101abcde12345",
|
||||
"/api/merchant/v1/files/qualifications/license.png", null, false,
|
||||
"/api/merchant/v1/files/qualifications/legal-person.png", null, false));
|
||||
"/api/merchant/v1/files/qualifications/license.png", "license.png", null, false,
|
||||
"/api/merchant/v1/files/qualifications/legal-person.png", "legal-person.png", null, false));
|
||||
|
||||
assertEquals("PROFILE_REQUIRED", response.tenantStatus());
|
||||
assertEquals("91310101ABCDE12345", tenant.getUnifiedSocialCreditCode());
|
||||
@@ -128,8 +131,8 @@ class MerchantQualificationUploadTest {
|
||||
|
||||
var response = service.submitRequiredQualificationPackage(new RequiredQualificationPackageRequest(
|
||||
10001L, "91310101abcde12345",
|
||||
"/api/merchant/v1/files/qualifications/license.png", null, false,
|
||||
null, null, null));
|
||||
"/api/merchant/v1/files/qualifications/license.png", "license.png", null, false,
|
||||
null, null, null, null));
|
||||
|
||||
assertNull(response.legalPersonIdQualificationId());
|
||||
verify(profileRepository, times(1)).insertQualification(any());
|
||||
@@ -147,12 +150,66 @@ class MerchantQualificationUploadTest {
|
||||
|
||||
assertThrows(BizException.class, () -> service.submitRequiredQualificationPackage(
|
||||
new RequiredQualificationPackageRequest(10001L, "91310101ABCDE12345",
|
||||
"/api/merchant/v1/files/qualifications/license.png", null, true,
|
||||
"/api/merchant/v1/files/qualifications/legal-person.png", null, true)));
|
||||
"/api/merchant/v1/files/qualifications/license.png", "license.png", null, true,
|
||||
"/api/merchant/v1/files/qualifications/legal-person.png", "legal-person.png", null, true)));
|
||||
|
||||
verify(profileRepository, never()).insertQualification(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualificationListReturnsPersistedOriginalFileNameAndUpdatedTime() {
|
||||
IamTenantQualification item = qualification("LEGAL_PERSON_ID", "/files/qualification/1");
|
||||
item.setOriginalFileName("法人身份证.png");
|
||||
item.setUpdatedAt(LocalDateTime.of(2026, 9, 11, 10, 30));
|
||||
when(profileRepository.findQualifications(10001L)).thenReturn(List.of(item));
|
||||
|
||||
var response = service.qualifications(10001L);
|
||||
|
||||
assertEquals("法人身份证.png", response.get(0).originalFileName());
|
||||
assertEquals(LocalDateTime.of(2026, 9, 11, 10, 30), response.get(0).updatedAt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletesDraftQualificationWithTenantLock() {
|
||||
IamTenant tenant = new IamTenant();
|
||||
tenant.setId(10001L); tenant.setTenantType("MERCHANT"); tenant.setStatus("PROFILE_REQUIRED");
|
||||
IamTenantQualification item = qualification("LEGAL_PERSON_ID", "/files/qualification/1");
|
||||
when(tenantRepository.findTenantForUpdate(10001L)).thenReturn(tenant);
|
||||
when(profileRepository.findQualificationForUpdate(10001L, 1L)).thenReturn(item);
|
||||
|
||||
var response = service.deleteQualification(10001L, 1L);
|
||||
|
||||
assertEquals(1L, response.qualificationId());
|
||||
assertEquals("/files/qualification/1", response.deletedDocumentUrl());
|
||||
verify(profileRepository).deleteQualification(item);
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatedOrCrossTenantDeleteIsIdempotentAndDoesNotLeakOwnership() {
|
||||
IamTenant tenant = new IamTenant();
|
||||
tenant.setId(10001L); tenant.setTenantType("MERCHANT"); tenant.setStatus("REJECTED");
|
||||
when(tenantRepository.findTenantForUpdate(10001L)).thenReturn(tenant);
|
||||
when(profileRepository.findQualificationForUpdate(10001L, 99L)).thenReturn(null);
|
||||
|
||||
var response = service.deleteQualification(10001L, 99L);
|
||||
|
||||
assertEquals(99L, response.qualificationId());
|
||||
assertNull(response.deletedDocumentUrl());
|
||||
verify(profileRepository, never()).deleteQualification(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDeleteAfterReviewSubmission() {
|
||||
IamTenant tenant = new IamTenant();
|
||||
tenant.setId(10001L); tenant.setTenantType("MERCHANT"); tenant.setStatus("PENDING");
|
||||
when(tenantRepository.findTenantForUpdate(10001L)).thenReturn(tenant);
|
||||
|
||||
assertThrows(BizException.class, () -> service.deleteQualification(10001L, 1L));
|
||||
|
||||
verify(profileRepository, never()).findQualificationForUpdate(10001L, 1L);
|
||||
verify(profileRepository, never()).deleteQualification(any());
|
||||
}
|
||||
|
||||
private static IamTenantQualification qualification(String type, String documentUrl) {
|
||||
IamTenantQualification qualification = new IamTenantQualification();
|
||||
qualification.setId(1L);
|
||||
|
||||
@@ -35,7 +35,7 @@ class PlatformQualificationRegistrationTest {
|
||||
|
||||
var response = service.registerPlatformQualification(new PlatformQualificationRegistrationRequest(
|
||||
10001L, "BANK_ACCOUNT_LICENSE", null, "/api/merchant/v1/files/qualifications/file.pdf",
|
||||
LocalDate.of(2030, 12, 31), false));
|
||||
"file.pdf", LocalDate.of(2030, 12, 31), false));
|
||||
|
||||
ArgumentCaptor<IamTenantQualification> captor = ArgumentCaptor.forClass(IamTenantQualification.class);
|
||||
verify(repository).insertQualification(captor.capture());
|
||||
@@ -60,7 +60,7 @@ class PlatformQualificationRegistrationTest {
|
||||
|
||||
var response = service.registerPlatformQualification(new PlatformQualificationRegistrationRequest(
|
||||
10001L, "BANK_ACCOUNT_LICENSE", null, "/api/merchant/v1/files/qualifications/new-file.pdf",
|
||||
LocalDate.of(2031, 12, 31), true));
|
||||
"new-file.pdf", LocalDate.of(2031, 12, 31), true));
|
||||
|
||||
ArgumentCaptor<IamTenantQualification> captor = ArgumentCaptor.forClass(IamTenantQualification.class);
|
||||
verify(repository, times(2)).saveQualification(captor.capture());
|
||||
|
||||
Reference in New Issue
Block a user