This commit is contained in:
2026-08-04 09:05:28 +08:00
parent fdb1e8af1c
commit 3afcff320a
116 changed files with 6749 additions and 749 deletions
@@ -0,0 +1,57 @@
package com.baiyue.iam.controller;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.core.export.ExportContracts.ExportBatchRequest;
import com.baiyue.common.core.export.ExportContracts.ExportBatchResponse;
import com.baiyue.iam.service.CustomerListExportProviderService;
import org.junit.jupiter.api.Test;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class CustomerListExportProviderControllerTest {
@Test
void exposesTheGenericCustomerListProviderPath() throws Exception {
assertEquals("/internal/exports/v1/CUSTOMER_LIST",
CustomerListExportProviderController.class.getAnnotation(RequestMapping.class).value()[0]);
assertEquals("/batches", CustomerListExportProviderController.class
.getMethod("batch", String.class, ExportBatchRequest.class)
.getAnnotation(PostMapping.class).value()[0]);
}
@Test
void requiresInternalTokenBeforeCallingProvider() {
CustomerListExportProviderService service = mock(CustomerListExportProviderService.class);
CustomerListExportProviderController controller =
new CustomerListExportProviderController(service, "internal-token");
ExportBatchRequest request = request();
BizException missing = assertThrows(BizException.class, () -> controller.batch(null, request));
BizException wrong = assertThrows(BizException.class, () -> controller.batch("wrong", request));
assertEquals(ResultCode.FORBIDDEN, missing.getResultCode());
assertEquals(ResultCode.FORBIDDEN, wrong.getResultCode());
ExportBatchResponse batch = new ExportBatchResponse(1, "sha256:test", "snapshot",
List.of(), List.of(), null, false, 0L);
when(service.exportBatch(request)).thenReturn(batch);
var response = controller.batch("internal-token", request);
assertEquals(ResultCode.SUCCESS.code(), response.code());
assertEquals(batch, response.data());
verify(service).exportBatch(request);
}
private ExportBatchRequest request() {
return new ExportBatchRequest("190000000000000001", "0", "PLATFORM",
"190000000000000009", 1, Map.of(), null, null, 500);
}
}
@@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
@@ -55,6 +56,16 @@ class PlatformTenantControllerContractTest {
method.getAnnotation(PreAuthorize.class).value());
}
@Test
void 审核通过接口返回一次性管理员凭据结果() throws Exception {
Method method = PlatformTenantController.class.getMethod("approve", long.class,
com.baiyue.iam.dto.TenantDtos.ReviewRequest.class, AuthenticatedUser.class);
ParameterizedType returnType = (ParameterizedType) method.getGenericReturnType();
assertEquals(com.baiyue.iam.dto.TenantDtos.ApprovalResponse.class,
returnType.getActualTypeArguments()[0]);
}
private <T extends Annotation> T annotation(Method method, int parameter, Class<T> type) {
return method.getParameters()[parameter].getAnnotation(type);
}
@@ -0,0 +1,50 @@
package com.baiyue.iam.repository;
import com.baiyue.iam.entity.IamTenant;
import com.baiyue.iam.mapper.IamTenantMapper;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.junit.jupiter.api.Test;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.mockito.ArgumentCaptor;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class CustomerListExportProviderRepositoryTest {
@Test
void batchQueryAlwaysConstrainsMerchantSnapshotAndAscendingCursor() {
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "test"),
IamTenant.class);
IamTenantMapper mapper = mock(IamTenantMapper.class);
when(mapper.selectList(any())).thenReturn(List.of());
CustomerListExportProviderRepository repository =
new CustomerListExportProviderRepository(mapper);
repository.findBatch(100L, 200L, 501, "柏悦", "ACTIVE");
@SuppressWarnings("unchecked")
ArgumentCaptor<Wrapper<IamTenant>> captor = ArgumentCaptor.forClass(Wrapper.class);
verify(mapper).selectList(captor.capture());
Wrapper<IamTenant> query = captor.getValue();
assertThat(query.getSqlSegment())
.contains("tenant_type")
.contains("status")
.contains("tenant_code")
.contains("tenant_name")
.contains("id >")
.contains("id <=")
.contains("ORDER BY id ASC")
.endsWith("LIMIT 501");
assertThat(((AbstractWrapper<?, ?, ?>) query).getParamNameValuePairs().values())
.contains("MERCHANT", "ACTIVE", 100L, 200L)
.anyMatch(value -> "%柏悦%".equals(value));
}
}
@@ -10,6 +10,21 @@ import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class CustomerProfileMigrationContractTest {
@Test
void 企业审核初始化按角色自然键为全局管理员模板授权() throws IOException {
String sql;
try (var stream = getClass().getResourceAsStream("/db/phase1-onboarding.sql")) {
assertThat(stream).isNotNull();
sql = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
}
assertThat(sql)
.contains("'MERCHANT_OWNER'")
.contains("SELECT owner_role.id, permission.id, 0")
.contains("owner_role.role_code='MERCHANT_OWNER'")
.doesNotContain("SELECT 4000100, id, 0");
}
@Test
void 客户档案迁移覆盖全部字段并通过元数据判断保持可重复执行() throws IOException {
String sql;
@@ -46,17 +61,32 @@ class CustomerProfileMigrationContractTest {
assertThat(sql)
.contains("CALL regression_assert_table('iam_tenant')")
.contains("CALL regression_assert_table('iam_tenant_review')")
.contains("CALL regression_assert_table('iam_tenant_qualification')")
.contains("DEFAULT 'PROFILE_REQUIRED'")
.contains("CREATE PROCEDURE regression_add_iam_column")
.contains("regression_add_iam_column('approved_at'")
.contains("regression_add_iam_column('approved_by'")
.contains("regression_add_iam_column('reject_reason'")
.contains("regression_add_iam_related_column('iam_tenant_qualification', 'review_comment'")
.contains("'IAM注册审核字段'")
.contains("'IAM资质与成员审计字段'")
.contains("platform:customer:view")
.contains("platform:customer:export")
.contains("platform:download:list")
.contains("'MERCHANT_OWNER'")
.contains("IAM全局企业管理员模板")
.contains("owner_role.id, permission.id, 0")
.contains("'IAM客户档案字段' AS check_item")
.contains("9 AS expected_count")
.contains("10 AS expected_count")
.contains("47 AS expected_count")
.contains("MIGRATION_COMPLETED");
assertThat(instructions)
.contains("phase1-platform-master-data-and-permission-management.sql")
.contains("phase1-customer-profile.sql")
.contains("phase1-onboarding.sql")
.contains("IAM 注册审核字段")
.contains("全局 `MERCHANT_OWNER` 角色模板")
.contains("测试环境只执行统一入口");
}
}
@@ -0,0 +1,161 @@
package com.baiyue.iam.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.core.export.ExportContracts.ExportBatchRequest;
import com.baiyue.common.core.export.ExportContracts.ExportColumnType;
import com.baiyue.iam.entity.IamTenant;
import com.baiyue.iam.repository.CustomerListExportProviderRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class CustomerListExportProviderServiceTest {
private CustomerListExportProviderRepository repository;
private CustomerListExportProviderService service;
@BeforeEach
void setUp() {
repository = mock(CustomerListExportProviderRepository.class);
service = new CustomerListExportProviderService(repository,
"test-internal-service-token-at-least-32-bytes");
}
@Test
void firstBatchReturnsTypedSchemaSignedMaximumIdAndBoundedRows() {
when(repository.maximumId("柏悦", "ACTIVE")).thenReturn(103L);
when(repository.estimateRows(103L, "柏悦", "ACTIVE")).thenReturn(3L);
when(repository.findBatch(null, 103L, 3, "柏悦", "ACTIVE"))
.thenReturn(List.of(tenant(101L), tenant(102L), tenant(103L)));
var response = service.exportBatch(request(Map.of("keyword", " 柏悦 ", "status", "ACTIVE"),
null, null, 2));
assertEquals(1, response.schemaVersion());
assertThat(response.schemaHash()).matches("sha256:[0-9a-f]{64}");
assertThat(response.snapshotToken()).matches("v1\\.103\\.[A-Za-z0-9_-]+");
assertEquals(20, response.columns().size());
assertEquals("tenantCode", response.columns().get(1).key());
assertEquals(ExportColumnType.DECIMAL,
response.columns().stream().filter(column -> "registeredCapital".equals(column.key()))
.findFirst().orElseThrow().type());
assertEquals(2, response.rows().size());
assertEquals("101", response.rows().get(0).get("id"));
assertEquals(new BigDecimal("1000000.00"), response.rows().get(0).get("registeredCapital"));
assertEquals("启用", response.rows().get(0).get("status"));
assertEquals("102", response.nextCursor());
assertEquals(true, response.hasMore());
assertEquals(3L, response.totalRows());
}
@Test
void laterBatchReusesVerifiedSnapshotAndOmitsSchemaAndEstimate() {
when(repository.maximumId("柏悦", "ACTIVE")).thenReturn(103L);
when(repository.estimateRows(103L, "柏悦", "ACTIVE")).thenReturn(3L);
when(repository.findBatch(null, 103L, 3, "柏悦", "ACTIVE"))
.thenReturn(List.of(tenant(101L), tenant(102L), tenant(103L)));
var first = service.exportBatch(request(Map.of("keyword", "柏悦", "status", "ACTIVE"),
null, null, 2));
when(repository.findBatch(102L, 103L, 3, "柏悦", "ACTIVE"))
.thenReturn(List.of(tenant(103L)));
var later = service.exportBatch(request(Map.of("keyword", "柏悦", "status", "ACTIVE"),
"102", first.snapshotToken(), 2));
assertThat(later.columns()).isEmpty();
assertEquals(first.schemaHash(), later.schemaHash());
assertEquals(first.snapshotToken(), later.snapshotToken());
assertEquals(1, later.rows().size());
assertEquals("103", later.nextCursor());
assertEquals(false, later.hasMore());
assertEquals(null, later.totalRows());
verify(repository).findBatch(102L, 103L, 3, "柏悦", "ACTIVE");
}
@Test
void snapshotIsBoundToTaskIdentityAndFilters() {
when(repository.maximumId("柏悦", "ACTIVE")).thenReturn(103L);
when(repository.estimateRows(103L, "柏悦", "ACTIVE")).thenReturn(0L);
when(repository.findBatch(null, 103L, 2, "柏悦", "ACTIVE")).thenReturn(List.of());
String token = service.exportBatch(request(Map.of("keyword", "柏悦", "status", "ACTIVE"),
null, null, 1)).snapshotToken();
BizException changedFilter = assertThrows(BizException.class, () -> service.exportBatch(
request(Map.of("keyword", "其他", "status", "ACTIVE"), "0", token, 1)));
String tampered = token.substring(0, token.length() - 1)
+ (token.endsWith("A") ? "B" : "A");
BizException tamperedToken = assertThrows(BizException.class, () -> service.exportBatch(
request(Map.of("keyword", "柏悦", "status", "ACTIVE"), "0", tampered, 1)));
assertEquals(ResultCode.VALIDATION_FAILED, changedFilter.getResultCode());
assertEquals(ResultCode.VALIDATION_FAILED, tamperedToken.getResultCode());
}
@Test
void rejectsMissingScopeInvalidLimitsAndUntrustedParametersBeforeQuerying() {
BizException tenant = assertThrows(BizException.class, () -> service.exportBatch(new ExportBatchRequest(
"190000000000000001", "9", "PLATFORM", "190000000000000009",
1, Map.of(), null, null, 500)));
BizException app = assertThrows(BizException.class, () -> service.exportBatch(new ExportBatchRequest(
"190000000000000001", "0", "MERCHANT", "190000000000000009",
1, Map.of(), null, null, 500)));
BizException actor = assertThrows(BizException.class, () -> service.exportBatch(new ExportBatchRequest(
"190000000000000001", "0", "PLATFORM", null,
1, Map.of(), null, null, 500)));
BizException limit = assertThrows(BizException.class, () -> service.exportBatch(
request(Map.of(), null, null, 501)));
BizException untrusted = assertThrows(BizException.class, () -> service.exportBatch(
request(Map.of("tenantId", "9"), null, null, 500)));
BizException cursor = assertThrows(BizException.class, () -> service.exportBatch(
request(Map.of(), "1", null, 500)));
assertThat(List.of(tenant, app, actor, limit, untrusted, cursor))
.allMatch(error -> error.getResultCode() == ResultCode.VALIDATION_FAILED);
verify(repository, never()).maximumId(org.mockito.ArgumentMatchers.any(),
org.mockito.ArgumentMatchers.any());
}
private ExportBatchRequest request(Map<String, Object> parameters, String cursor,
String snapshotToken, int limit) {
return new ExportBatchRequest("190000000000000001", "0", "PLATFORM",
"190000000000000009", 1, parameters, cursor, snapshotToken, limit);
}
private IamTenant tenant(long id) {
IamTenant tenant = new IamTenant();
tenant.setId(id);
tenant.setTenantCode("TEN" + id);
tenant.setTenantName("柏悦物流" + id);
tenant.setLegalName("柏悦物流有限公司");
tenant.setUnifiedSocialCreditCode("91440000123456789X");
tenant.setEnterpriseType("有限责任公司");
tenant.setEstablishedDate(LocalDate.of(2020, 1, 2));
tenant.setRegisteredCapital(new BigDecimal("1000000.00"));
tenant.setRegisteredCapitalCurrency("CNY");
tenant.setRegisteredAddress("深圳市");
tenant.setContactName("张三");
tenant.setContactPhone("13800138000");
tenant.setContactEmail("customer@example.com");
tenant.setContactWechat("wechat");
tenant.setSalesOwnerName("销售甲");
tenant.setServicePackage("基础版");
tenant.setPaymentTermDays(30);
tenant.setStatus("ACTIVE");
tenant.setCreatedAt(LocalDateTime.of(2026, 8, 1, 10, 0));
tenant.setApprovedAt(LocalDateTime.of(2026, 8, 2, 10, 0));
return tenant;
}
}
@@ -0,0 +1,178 @@
package com.baiyue.iam.service;
import com.baiyue.iam.dto.OnboardingDtos.MerchantRegistrationRequest;
import com.baiyue.iam.entity.IamMembership;
import com.baiyue.iam.entity.IamMembershipRole;
import com.baiyue.iam.entity.IamRole;
import com.baiyue.iam.entity.IamTenant;
import com.baiyue.iam.entity.IamUser;
import com.baiyue.iam.mapper.IamMembershipMapper;
import com.baiyue.iam.mapper.IamMembershipRoleMapper;
import com.baiyue.iam.mapper.IamRoleMapper;
import com.baiyue.iam.mapper.IamTenantAddressMapper;
import com.baiyue.iam.mapper.IamTenantMapper;
import com.baiyue.iam.mapper.IamTenantQualificationMapper;
import com.baiyue.iam.mapper.IamTenantReviewMapper;
import com.baiyue.iam.mapper.IamUserMapper;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class MerchantOnboardingServiceTest {
@Test
void clientRegistrationCreatesOwnerAndAssignsGlobalMerchantOwnerRole() {
IamTenantMapper tenants = mock(IamTenantMapper.class);
IamUserMapper users = mock(IamUserMapper.class);
IamMembershipMapper memberships = mock(IamMembershipMapper.class);
IamRoleMapper roles = mock(IamRoleMapper.class);
IamMembershipRoleMapper membershipRoles = mock(IamMembershipRoleMapper.class);
IamTenantAddressMapper addresses = mock(IamTenantAddressMapper.class);
IamTenantQualificationMapper qualifications = mock(IamTenantQualificationMapper.class);
IamTenantReviewMapper reviews = mock(IamTenantReviewMapper.class);
MerchantOnboardingService service = new MerchantOnboardingService();
ReflectionTestUtils.setField(service, "tenantMapper", tenants);
ReflectionTestUtils.setField(service, "userMapper", users);
ReflectionTestUtils.setField(service, "membershipMapper", memberships);
ReflectionTestUtils.setField(service, "roleMapper", roles);
ReflectionTestUtils.setField(service, "membershipRoleMapper", membershipRoles);
ReflectionTestUtils.setField(service, "addressMapper", addresses);
ReflectionTestUtils.setField(service, "qualificationMapper", qualifications);
ReflectionTestUtils.setField(service, "reviewMapper", reviews);
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);
var result = service.register(new MerchantRegistrationRequest(
9001L, "注册人", "测试企业", "张三", "13800138000",
"owner@example.com", "CN", "深圳市宝安区"));
ArgumentCaptor<IamTenant> tenant = ArgumentCaptor.forClass(IamTenant.class);
ArgumentCaptor<IamUser> user = ArgumentCaptor.forClass(IamUser.class);
ArgumentCaptor<IamMembership> membership = ArgumentCaptor.forClass(IamMembership.class);
ArgumentCaptor<IamMembershipRole> roleGrant = ArgumentCaptor.forClass(IamMembershipRole.class);
verify(tenants).insert(tenant.capture());
verify(users).insert(user.capture());
verify(memberships).insert(membership.capture());
verify(membershipRoles).insert(roleGrant.capture());
assertEquals("PROFILE_REQUIRED", result.tenantStatus());
assertEquals(result.tenantId(), tenant.getValue().getId());
assertEquals("测试企业", tenant.getValue().getLegalName());
assertEquals("张三", tenant.getValue().getLegalRepresentative());
assertEquals("注册人", tenant.getValue().getContactName());
assertEquals(9001L, user.getValue().getAuthUserId());
assertEquals("ACTIVE", user.getValue().getStatus());
assertEquals(result.tenantId(), membership.getValue().getTenantId());
assertEquals(user.getValue().getId(), membership.getValue().getUserId());
assertEquals("OWNER", membership.getValue().getMemberType());
assertEquals("ACTIVE", membership.getValue().getStatus());
assertEquals(result.membershipId(), membership.getValue().getId());
assertEquals(result.membershipId(), roleGrant.getValue().getMembershipId());
assertEquals(4000100L, roleGrant.getValue().getRoleId());
assertEquals(result.tenantId(), roleGrant.getValue().getTenantId());
assertNotNull(result.tenantId());
}
@Test
void compensationRemovesOnlyAnUnstartedRegistration() {
IamTenantMapper tenants = mock(IamTenantMapper.class);
IamUserMapper users = mock(IamUserMapper.class);
IamMembershipMapper memberships = mock(IamMembershipMapper.class);
IamRoleMapper roles = mock(IamRoleMapper.class);
IamMembershipRoleMapper membershipRoles = mock(IamMembershipRoleMapper.class);
IamTenantAddressMapper addresses = mock(IamTenantAddressMapper.class);
IamTenantQualificationMapper qualifications = mock(IamTenantQualificationMapper.class);
IamTenantReviewMapper reviews = mock(IamTenantReviewMapper.class);
MerchantOnboardingService service = new MerchantOnboardingService();
ReflectionTestUtils.setField(service, "tenantMapper", tenants);
ReflectionTestUtils.setField(service, "userMapper", users);
ReflectionTestUtils.setField(service, "membershipMapper", memberships);
ReflectionTestUtils.setField(service, "roleMapper", roles);
ReflectionTestUtils.setField(service, "membershipRoleMapper", membershipRoles);
ReflectionTestUtils.setField(service, "addressMapper", addresses);
ReflectionTestUtils.setField(service, "qualificationMapper", qualifications);
ReflectionTestUtils.setField(service, "reviewMapper", reviews);
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.setStatus("PROFILE_REQUIRED");
when(users.selectOne(any())).thenReturn(user);
when(memberships.selectList(any())).thenReturn(java.util.List.of(membership));
when(tenants.selectOne(any())).thenReturn(tenant);
when(qualifications.selectCount(any())).thenReturn(0L);
when(reviews.selectCount(any())).thenReturn(0L);
service.compensateRegistration(9001L);
verify(membershipRoles).delete(any());
verify(memberships).deleteById(30001L);
verify(addresses).delete(any());
verify(users).deleteById((java.io.Serializable) Long.valueOf(20001L));
verify(tenants).deleteById((java.io.Serializable) Long.valueOf(10001L));
}
@Test
void compensationRefusesAfterQualificationUpload() {
IamTenantMapper tenants = mock(IamTenantMapper.class);
IamUserMapper users = mock(IamUserMapper.class);
IamMembershipMapper memberships = mock(IamMembershipMapper.class);
IamRoleMapper roles = mock(IamRoleMapper.class);
IamMembershipRoleMapper membershipRoles = mock(IamMembershipRoleMapper.class);
IamTenantAddressMapper addresses = mock(IamTenantAddressMapper.class);
IamTenantQualificationMapper qualifications = mock(IamTenantQualificationMapper.class);
IamTenantReviewMapper reviews = mock(IamTenantReviewMapper.class);
MerchantOnboardingService service = new MerchantOnboardingService();
ReflectionTestUtils.setField(service, "tenantMapper", tenants);
ReflectionTestUtils.setField(service, "userMapper", users);
ReflectionTestUtils.setField(service, "membershipMapper", memberships);
ReflectionTestUtils.setField(service, "roleMapper", roles);
ReflectionTestUtils.setField(service, "membershipRoleMapper", membershipRoles);
ReflectionTestUtils.setField(service, "addressMapper", addresses);
ReflectionTestUtils.setField(service, "qualificationMapper", qualifications);
ReflectionTestUtils.setField(service, "reviewMapper", reviews);
IamUser user = new IamUser();
user.setId(20001L);
IamMembership membership = new IamMembership();
membership.setId(30001L);
membership.setTenantId(10001L);
membership.setUserId(20001L);
membership.setMemberType("OWNER");
IamTenant tenant = new IamTenant();
tenant.setId(10001L);
tenant.setStatus("PROFILE_REQUIRED");
when(users.selectOne(any())).thenReturn(user);
when(memberships.selectList(any())).thenReturn(java.util.List.of(membership));
when(tenants.selectOne(any())).thenReturn(tenant);
when(qualifications.selectCount(any())).thenReturn(1L);
when(reviews.selectCount(any())).thenReturn(0L);
assertThrows(com.baiyue.common.core.exception.BizException.class,
() -> service.compensateRegistration(9001L));
verify(membershipRoles, never()).delete(any());
verify(users, never()).deleteById((java.io.Serializable) any());
verify(tenants, never()).deleteById((java.io.Serializable) any());
}
}
@@ -12,11 +12,13 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
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.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.baiyue.common.core.exception.BizException;
class MerchantQualificationUploadTest {
@@ -43,4 +45,31 @@ class MerchantQualificationUploadTest {
verify(profileRepository).insertQualification(any());
verify(tenantRepository, never()).insertReview(any());
}
@Test
void businessLicenseRequiresAndPersistsAUniqueUnifiedSocialCreditCode() {
TenantRepository tenantRepository = mock(TenantRepository.class);
ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class);
ProfileResourceService service = new ProfileResourceService();
ReflectionTestUtils.setField(service, "tenantRepository", tenantRepository);
ReflectionTestUtils.setField(service, "repository", profileRepository);
IamTenant tenant = new IamTenant();
tenant.setId(10001L);
tenant.setTenantType("MERCHANT");
tenant.setStatus("PROFILE_REQUIRED");
when(tenantRepository.findTenantForUpdate(10001L)).thenReturn(tenant);
when(profileRepository.findQualificationsForUpdate(10001L)).thenReturn(List.of());
assertThrows(BizException.class, () -> service.submitQualification(new QualificationSubmissionRequest(
10001L, "BUSINESS_LICENSE", null,
"/api/merchant/v1/files/qualifications/license.png", null, false)));
var response = service.submitQualification(new QualificationSubmissionRequest(
10001L, "BUSINESS_LICENSE", "91310101abcde12345",
"/api/merchant/v1/files/qualifications/license.png", null, false));
assertEquals("PROFILE_REQUIRED", response.tenantStatus());
assertEquals("91310101ABCDE12345", tenant.getUnifiedSocialCreditCode());
verify(tenantRepository).saveTenant(tenant);
}
}
@@ -0,0 +1,242 @@
package com.baiyue.iam.service;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.iam.client.AuthAccountClient;
import com.baiyue.iam.entity.IamMembership;
import com.baiyue.iam.entity.IamRole;
import com.baiyue.iam.entity.IamTenant;
import com.baiyue.iam.entity.IamUser;
import com.baiyue.iam.mapper.IamRoleMapper;
import com.baiyue.iam.repository.MemberRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.security.SecureRandom;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
class TenantAdministratorProvisioningServiceTest {
private final MemberRepository members = mock(MemberRepository.class);
private final IamRoleMapper roles = mock(IamRoleMapper.class);
private final AuthAccountClient auth = mock(AuthAccountClient.class);
private final TenantAdministratorProvisioningService service =
new TenantAdministratorProvisioningService(members, roles, auth, "internal-token", new SecureRandom());
private IamTenant tenant;
@BeforeEach
void setUp() {
tenant = new IamTenant();
tenant.setId(190000000000000001L);
tenant.setCreatedBy(7000L);
tenant.setContactName("张三");
tenant.setContactPhone("13800138000");
tenant.setContactEmail("owner@example.com");
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);
when(auth.create(anyString(), any())).thenAnswer(invocation -> {
AuthAccountClient.CreateAccountRequest request = invocation.getArgument(1);
return ApiResponse.success(new AuthAccountClient.AccountSummary(
request.accountId(), request.username(), request.displayName(), "ACTIVE"));
});
when(auth.compensate(anyString(), anyLong())).thenReturn(ApiResponse.success(null));
}
@Test
void missingOwnerCreatesAccountProfileMembershipAndOwnerRole() {
var result = service.initialize(context());
assertTrue(result.created());
assertTrue(result.username().matches("^merchant_190000000000000001_[a-z]{8}$"));
assertNotNull(result.temporaryPassword());
assertEquals(16, result.temporaryPassword().length());
assertTrue(result.temporaryPassword().matches("^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z0-9]+$"));
ArgumentCaptor<AuthAccountClient.CreateAccountRequest> account =
ArgumentCaptor.forClass(AuthAccountClient.CreateAccountRequest.class);
verify(auth).create(eq("internal-token"), account.capture());
assertEquals(result.username(), account.getValue().username());
assertEquals(result.temporaryPassword(), account.getValue().password());
assertEquals("MERCHANT", account.getValue().appCode());
assertEquals(tenant.getId(), account.getValue().tenantId());
verify(members).insertUser(any(IamUser.class));
verify(members).insert(any(IamMembership.class));
verify(members).replaceRoles(eq(tenant.getId()), anyLong(), eq(List.of(4000100L)));
}
@Test
void existingOwnerSkipsProvisioningAndDoesNotReturnCredentials() {
IamMembership owner = activeOwner();
IamUser user = activeUser();
when(members.findOwnerByTenant(tenant.getId())).thenReturn(owner);
when(members.findUser(owner.getUserId())).thenReturn(user);
when(auth.summary("internal-token", user.getAuthUserId())).thenReturn(ApiResponse.success(
new AuthAccountClient.AccountSummary(user.getAuthUserId(), "existing.owner", "张三", "ACTIVE",
null, tenant.getId(), owner.getId(), "MERCHANT")));
when(members.findRoleIds(tenant.getId(), owner.getId())).thenReturn(List.of(4000100L));
var result = service.initialize(context());
assertFalse(result.created());
assertEquals(null, result.username());
assertEquals(null, result.temporaryPassword());
verify(auth, never()).create(anyString(), any());
verify(members, never()).replaceRoles(anyLong(), anyLong(), any());
}
@Test
void existingRegisteredOwnerKeepsAccountAndRepairsMissingAdministratorRole() {
IamMembership owner = activeOwner();
IamUser user = activeUser();
when(members.findOwnerByTenant(tenant.getId())).thenReturn(owner);
when(members.findUser(owner.getUserId())).thenReturn(user);
when(auth.summary("internal-token", user.getAuthUserId())).thenReturn(ApiResponse.success(
new AuthAccountClient.AccountSummary(user.getAuthUserId(), "registered.owner", "张三", "ACTIVE",
null, tenant.getId(), owner.getId(), "MERCHANT")));
when(members.findRoleIds(tenant.getId(), owner.getId())).thenReturn(List.of(4000200L));
var result = service.initialize(context());
assertFalse(result.created());
verify(auth, never()).create(anyString(), any());
verify(members).replaceRoles(tenant.getId(), owner.getId(), List.of(4000200L, 4000100L));
}
@Test
void clientRegistrationWithoutOriginalOwnerIsRejectedInsteadOfCreatingAnotherAdministrator() {
tenant.setCreatedBy(null);
tenant.setUnifiedSocialCreditCode("91330100EXAMPLE001");
assertThrows(com.baiyue.common.core.exception.BizException.class,
() -> service.initialize(context()));
verify(auth, never()).create(anyString(), any());
verify(members, never()).insertUser(any());
verify(members, never()).insert(any());
}
@Test
void ownerWithoutAuthAccountRepairsAccountUsingExistingIdentity() {
IamMembership owner = activeOwner();
IamUser user = activeUser();
when(members.findOwnerByTenant(tenant.getId())).thenReturn(owner);
when(members.findUser(owner.getUserId())).thenReturn(user);
when(auth.summary("internal-token", user.getAuthUserId())).thenReturn(
ApiResponse.failure(com.baiyue.common.core.api.ResultCode.NOT_FOUND,
"账号不存在", null, null));
var result = service.initialize(context());
assertTrue(result.created());
ArgumentCaptor<AuthAccountClient.CreateAccountRequest> account =
ArgumentCaptor.forClass(AuthAccountClient.CreateAccountRequest.class);
verify(auth).create(eq("internal-token"), account.capture());
assertEquals(user.getAuthUserId(), account.getValue().accountId());
assertEquals(owner.getId(), account.getValue().membershipId());
assertEquals(tenant.getId(), account.getValue().tenantId());
verify(members, never()).insertUser(any());
verify(members, never()).insert(any());
verify(members, never()).replaceRoles(anyLong(), anyLong(), any());
}
@Test
void disabledOwnerPreventsApprovalInsteadOfSilentlySkippingProvisioning() {
IamMembership owner = activeOwner();
owner.setStatus("DISABLED");
when(members.findOwnerByTenant(tenant.getId())).thenReturn(owner);
assertThrows(com.baiyue.common.core.exception.BizException.class,
() -> service.initialize(context()));
verify(auth, never()).summary(anyString(), anyLong());
verify(auth, never()).create(anyString(), any());
}
@Test
void mismatchedAuthScopePreventsApproval() {
IamMembership owner = activeOwner();
IamUser user = activeUser();
when(members.findOwnerByTenant(tenant.getId())).thenReturn(owner);
when(members.findUser(owner.getUserId())).thenReturn(user);
when(auth.summary("internal-token", user.getAuthUserId())).thenReturn(ApiResponse.success(
new AuthAccountClient.AccountSummary(user.getAuthUserId(), "existing.owner", "张三", "ACTIVE",
null, tenant.getId() + 1, owner.getId(), "MERCHANT")));
assertThrows(com.baiyue.common.core.exception.BizException.class,
() -> service.initialize(context()));
verify(auth, never()).create(anyString(), any());
}
@Test
void localWriteFailureCompensatesCreatedAuthAccount() {
doThrow(new RuntimeException("IAM write failed")).when(members).insertUser(any());
assertThrows(RuntimeException.class, () -> service.initialize(context()));
verify(auth).compensate(eq("internal-token"), anyLong());
verify(members, never()).insert(any(IamMembership.class));
}
@Test
void usernameConflictGeneratesAnotherUsernameBeforeCreatingOwner() {
org.mockito.Mockito.doReturn(ApiResponse.failure(com.baiyue.common.core.api.ResultCode.CONFLICT,
"登录账号已存在", null, null))
.doAnswer(invocation -> {
AuthAccountClient.CreateAccountRequest request = invocation.getArgument(1);
return ApiResponse.success(new AuthAccountClient.AccountSummary(
request.accountId(), request.username(), request.displayName(), "ACTIVE"));
}).when(auth).create(anyString(), any());
var result = service.initialize(context());
assertTrue(result.created());
ArgumentCaptor<AuthAccountClient.CreateAccountRequest> accounts =
ArgumentCaptor.forClass(AuthAccountClient.CreateAccountRequest.class);
verify(auth, times(2)).create(eq("internal-token"), accounts.capture());
assertFalse(accounts.getAllValues().get(0).username().equals(accounts.getAllValues().get(1).username()));
assertEquals(accounts.getAllValues().get(1).username(), result.username());
}
private TenantApprovalInitializer.ApprovalContext context() {
return new TenantApprovalInitializer.ApprovalContext(tenant, 71L, 9001L, "资料完整");
}
private IamMembership activeOwner() {
IamMembership owner = new IamMembership();
owner.setId(7001L);
owner.setTenantId(tenant.getId());
owner.setUserId(8001L);
owner.setMemberType("OWNER");
owner.setStatus("ACTIVE");
return owner;
}
private IamUser activeUser() {
IamUser user = new IamUser();
user.setId(8001L);
user.setAuthUserId(9001L);
user.setStatus("ACTIVE");
return user;
}
}
@@ -0,0 +1,95 @@
package com.baiyue.iam.service;
import com.baiyue.iam.entity.IamTenant;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.springframework.core.annotation.Order;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class TenantApprovalInitializationCoordinatorTest {
@Test
void initializerFailureCompensatesCompletedStepsInReverseOrder() {
TenantApprovalInitializer first = initializer("rules");
TenantApprovalInitializer second = initializer("fees");
TenantApprovalInitializer failing = initializer("roles");
TenantApprovalInitializer.InitializationResult firstResult = new TestResult("rules");
TenantApprovalInitializer.InitializationResult secondResult = new TestResult("fees");
var context = context();
when(first.initialize(context)).thenReturn(firstResult);
when(second.initialize(context)).thenReturn(secondResult);
when(failing.initialize(context)).thenThrow(new RuntimeException("role initialization failed"));
var coordinator = new TenantApprovalInitializationCoordinator(List.of(first, second, failing));
assertThrows(RuntimeException.class, () -> coordinator.initialize(context));
InOrder compensationOrder = inOrder(second, first);
compensationOrder.verify(second).compensate(context, secondResult);
compensationOrder.verify(first).compensate(context, firstResult);
}
@Test
void completedBatchExposesTypedResultsForApprovalResponseAssembly() {
TenantApprovalInitializer initializer = initializer("administrator");
TestResult result = new TestResult("credential-result");
var context = context();
when(initializer.initialize(context)).thenReturn(result);
var coordinator = new TenantApprovalInitializationCoordinator(List.of(initializer));
var batch = coordinator.initialize(context);
assertEquals(result, batch.result(TestResult.class).orElseThrow());
}
@Test
void springOrderControlsInitializerExecutionSequence() {
List<String> calls = new ArrayList<>();
var coordinator = new TenantApprovalInitializationCoordinator(
List.of(new LateInitializer(calls), new EarlyInitializer(calls)));
coordinator.initialize(context());
assertEquals(List.of("early", "late"), calls);
}
private TenantApprovalInitializer initializer(String name) {
TenantApprovalInitializer initializer = mock(TenantApprovalInitializer.class);
when(initializer.name()).thenReturn(name);
return initializer;
}
private TenantApprovalInitializer.ApprovalContext context() {
IamTenant tenant = new IamTenant();
tenant.setId(10001L);
return new TenantApprovalInitializer.ApprovalContext(tenant, 71L, 9001L, "资料完整");
}
private record TestResult(String value) implements TenantApprovalInitializer.InitializationResult { }
@Order(10)
private static final class EarlyInitializer implements TenantApprovalInitializer {
private final List<String> calls;
private EarlyInitializer(List<String> calls) { this.calls = calls; }
public String name() { return "early"; }
public InitializationResult initialize(ApprovalContext context) {
calls.add("early"); return new TestResult("early");
}
}
@Order(200)
private static final class LateInitializer implements TenantApprovalInitializer {
private final List<String> calls;
private LateInitializer(List<String> calls) { this.calls = calls; }
public String name() { return "late"; }
public InitializationResult initialize(ApprovalContext context) {
calls.add("late"); return new TestResult("late");
}
}
}
@@ -6,6 +6,7 @@ import com.baiyue.iam.dto.TenantDtos.ChangeTenantStatusRequest;
import com.baiyue.iam.dto.TenantDtos.UpdateTenantRequest;
import com.baiyue.iam.dto.TenantDtos.RegisterRequest;
import com.baiyue.iam.entity.IamTenantQualification;
import com.baiyue.iam.entity.IamTenantReview;
import com.baiyue.iam.repository.TenantRepository;
import com.baiyue.iam.repository.ProfileResourceRepository;
import org.junit.jupiter.api.Test;
@@ -21,7 +22,10 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.doThrow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.core.api.ResultCode;
@@ -75,12 +79,13 @@ class TenantServiceTest {
when(repository.findTenant(10001L)).thenReturn(tenant);
var updated = service.updateMerchant(10001L,
new UpdateTenantRequest("New name", "New legal", "Alice", "13900139000", "alice@example.com",
new UpdateTenantRequest("New name", "New legal", "Bob", "Alice", "13900139000", "alice@example.com",
"专业版", "alice_wechat", "Sales Zhang", 45, "LIMITED_COMPANY", LocalDate.of(2020, 1, 2),
new BigDecimal("5000000.00"), "CNY", "深圳市南山区示例路 1 号", "一期重点客户"));
var disabled = service.changeMerchantStatus(10001L, new ChangeTenantStatusRequest("DISABLED"));
assertEquals("New name", updated.tenantName());
assertEquals("Bob", updated.legalRepresentative());
assertEquals("alice_wechat", updated.contactWechat());
assertEquals("专业版", updated.servicePackage());
assertEquals("Sales Zhang", updated.salesOwnerName());
@@ -115,12 +120,16 @@ class TenantServiceTest {
ReflectionTestUtils.setField(service, "repository", repository);
var created = service.register(new RegisterRequest(
"Example Merchant", "Example Merchant Ltd", "91330100EXAMPLE001", "Alice", "13800138000",
"Example Merchant", "Example Merchant Ltd", "Bob", "91330100EXAMPLE001", "Alice", "13800138000",
"alice@example.com", "基础版", "alice_wechat", "Sales Zhang", 30, "LIMITED_COMPANY",
LocalDate.of(2020, 1, 2), new BigDecimal("5000000.00"), "CNY", "深圳市南山区", "草稿客户"));
LocalDate.of(2020, 1, 2), new BigDecimal("5000000.00"), "CNY", "深圳市南山区", "草稿客户"),
9001L);
assertEquals("PROFILE_REQUIRED", created.status());
assertEquals("Bob", created.legalRepresentative());
assertEquals("基础版", created.servicePackage());
verify(repository).insertTenant(argThat(tenant -> Long.valueOf(9001L).equals(tenant.getCreatedBy())
&& Long.valueOf(9001L).equals(tenant.getUpdatedBy())));
verify(repository, never()).insertReview(any());
}
@@ -162,14 +171,132 @@ class TenantServiceTest {
}
@Test
void 企业激活不等于具备下单资格仍需已审核资质() {
void explicitSubmissionRejectsMissingUnifiedSocialCreditCodeBeforeCreatingReview() {
TenantRepository repository = mock(TenantRepository.class);
ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class);
TenantService service = new TenantService();
ReflectionTestUtils.setField(service, "repository", repository);
ReflectionTestUtils.setField(service, "profileRepository", profileRepository);
IamTenant tenant = merchant(10001L, "PROFILE_REQUIRED");
tenant.setUnifiedSocialCreditCode(null);
when(repository.findTenantForUpdate(10001L)).thenReturn(tenant);
BizException exception = assertThrows(BizException.class, () -> service.submitMerchantReview(10001L));
assertEquals(ResultCode.BAD_REQUEST, exception.getResultCode());
verify(repository, never()).insertReview(any());
verify(profileRepository, never()).findQualifications(10001L);
}
@Test
void approvalProvisionsMissingAdministratorAndReturnsCredentialsOnce() {
TenantRepository repository = mock(TenantRepository.class);
ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class);
TenantAdministratorProvisioningService provisioning = mock(TenantAdministratorProvisioningService.class);
when(provisioning.name()).thenReturn("tenant-administrator");
TenantApprovalInitializationCoordinator initialization =
new TenantApprovalInitializationCoordinator(List.of(provisioning));
TenantService service = new TenantService();
ReflectionTestUtils.setField(service, "repository", repository);
ReflectionTestUtils.setField(service, "profileRepository", profileRepository);
ReflectionTestUtils.setField(service, "approvalInitialization", initialization);
IamTenantReview review = review(71L, 10001L);
IamTenant tenant = merchant(10001L, "PENDING");
when(repository.findReviewForUpdate(71L)).thenReturn(review);
when(repository.findTenant(10001L)).thenReturn(tenant);
when(profileRepository.findQualifications(10001L)).thenReturn(List.of(
qualification("BUSINESS_LICENSE"), qualification("LEGAL_PERSON_ID"),
qualification("FREIGHT_FORWARDER_CERTIFICATE"), qualification("BANK_ACCOUNT_LICENSE")));
when(provisioning.initialize(any())).thenReturn(
TenantAdministratorProvisioningService.ProvisioningResult.created(
81L, "merchant_10001", "Password1234"));
var result = service.review(71L, 9001L, true, "资料完整");
assertEquals(10001L, result.tenantId());
assertEquals(true, result.administratorCreated());
assertEquals("merchant_10001", result.administratorUsername());
assertEquals("Password1234", result.temporaryPassword());
assertEquals("APPROVED", review.getStatus());
assertEquals("ACTIVE", tenant.getStatus());
verify(repository).saveReview(review);
verify(repository).saveTenant(tenant);
}
@Test
void approvalKeepsClientRegisteredAccountAsAdministratorWithoutNewCredentials() {
TenantRepository repository = mock(TenantRepository.class);
ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class);
TenantAdministratorProvisioningService provisioning = mock(TenantAdministratorProvisioningService.class);
when(provisioning.name()).thenReturn("tenant-administrator");
TenantApprovalInitializationCoordinator initialization =
new TenantApprovalInitializationCoordinator(List.of(provisioning));
TenantService service = new TenantService();
ReflectionTestUtils.setField(service, "repository", repository);
ReflectionTestUtils.setField(service, "profileRepository", profileRepository);
ReflectionTestUtils.setField(service, "approvalInitialization", initialization);
IamTenantReview review = review(71L, 10001L);
IamTenant tenant = merchant(10001L, "PENDING");
when(repository.findReviewForUpdate(71L)).thenReturn(review);
when(repository.findTenant(10001L)).thenReturn(tenant);
when(profileRepository.findQualifications(10001L)).thenReturn(List.of(
qualification("BUSINESS_LICENSE"), qualification("LEGAL_PERSON_ID"),
qualification("FREIGHT_FORWARDER_CERTIFICATE"), qualification("BANK_ACCOUNT_LICENSE")));
when(provisioning.initialize(any())).thenReturn(
TenantAdministratorProvisioningService.ProvisioningResult.existing());
var result = service.review(71L, 9001L, true, "资料完整");
assertEquals(false, result.administratorCreated());
assertEquals(null, result.administratorUsername());
assertEquals(null, result.temporaryPassword());
assertEquals("APPROVED", review.getStatus());
assertEquals("ACTIVE", tenant.getStatus());
}
@Test
void failureAfterProvisioningCompensatesAdministratorAndKeepsApprovalFailed() {
TenantRepository repository = mock(TenantRepository.class);
ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class);
TenantAdministratorProvisioningService provisioning = mock(TenantAdministratorProvisioningService.class);
when(provisioning.name()).thenReturn("tenant-administrator");
TenantApprovalInitializationCoordinator initialization =
new TenantApprovalInitializationCoordinator(List.of(provisioning));
TenantService service = new TenantService();
ReflectionTestUtils.setField(service, "repository", repository);
ReflectionTestUtils.setField(service, "profileRepository", profileRepository);
ReflectionTestUtils.setField(service, "approvalInitialization", initialization);
IamTenantReview review = review(71L, 10001L);
IamTenant tenant = merchant(10001L, "PENDING");
var administrator = TenantAdministratorProvisioningService.ProvisioningResult.created(
81L, "merchant_10001", "Password1234");
when(repository.findReviewForUpdate(71L)).thenReturn(review);
when(repository.findTenant(10001L)).thenReturn(tenant);
when(profileRepository.findQualifications(10001L)).thenReturn(List.of(
qualification("BUSINESS_LICENSE"), qualification("LEGAL_PERSON_ID"),
qualification("FREIGHT_FORWARDER_CERTIFICATE"), qualification("BANK_ACCOUNT_LICENSE")));
when(provisioning.initialize(any())).thenReturn(administrator);
doThrow(new RuntimeException("review write failed")).when(repository).saveReview(review);
assertThrows(RuntimeException.class, () -> service.review(71L, 9001L, true, "资料完整"));
verify(provisioning).compensate(any(), eq(administrator));
verify(repository, never()).saveTenant(tenant);
}
@Test
void 企业激活不等于具备下单资格仍需四类资质全部审核通过() {
TenantRepository repository = mock(TenantRepository.class);
ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class);
TenantService service = new TenantService();
ReflectionTestUtils.setField(service, "repository", repository);
ReflectionTestUtils.setField(service, "profileRepository", profileRepository);
when(repository.findTenant(10001L)).thenReturn(merchant(10001L, "ACTIVE"));
when(profileRepository.countApprovedQualifications(10001L)).thenReturn(0L, 1L);
when(profileRepository.findQualifications(10001L)).thenReturn(
List.of(approvedQualification("BUSINESS_LICENSE")),
List.of(approvedQualification("BUSINESS_LICENSE"), approvedQualification("LEGAL_PERSON_ID"),
approvedQualification("FREIGHT_FORWARDER_CERTIFICATE"),
approvedQualification("BANK_ACCOUNT_LICENSE")));
var withoutQualification = service.orderEligibility(10001L);
var withQualification = service.orderEligibility(10001L);
@@ -198,4 +325,16 @@ class TenantServiceTest {
item.setQualificationType(type); item.setStatus("PENDING");
return item;
}
private IamTenantQualification approvedQualification(String type) {
IamTenantQualification item = qualification(type);
item.setStatus("APPROVED");
return item;
}
private IamTenantReview review(long id, long tenantId) {
IamTenantReview review = new IamTenantReview();
review.setId(id); review.setTenantId(tenantId); review.setStatus("PENDING");
return review;
}
}