优化
This commit is contained in:
+57
@@ -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);
|
||||
}
|
||||
|
||||
+50
@@ -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));
|
||||
}
|
||||
}
|
||||
+31
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+242
@@ -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;
|
||||
}
|
||||
}
|
||||
+95
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user