This commit is contained in:
2026-08-25 16:40:50 +08:00
parent 87c5e18ca6
commit eb1a8895ac
14 changed files with 170 additions and 42 deletions
@@ -84,6 +84,8 @@ public class CustomerDimensionInitializationRelay {
String value = failure.getMessage();
if (value == null || value.isBlank()) value = failure.getClass().getSimpleName();
value = value.replaceAll("[\\r\\n\\t]+", " ");
value = value.replaceAll("(?i)Bearer\\s+[^\\s,;]+", "Bearer ***")
.replaceAll("(?i)(token|password)=([^\\s&,;]+)", "$1=***");
return value.length() > 1000 ? value.substring(0, 1000) : value;
}
+3 -1
View File
@@ -25,6 +25,9 @@ spring:
baiyue:
security-event:
event-bridge-url: ${SECURITY_EVENT_BRIDGE_URL:http://event-bridge-overseas:9312}
customer-dimension-initialization:
fixed-delay-ms: ${CUSTOMER_DIMENSION_INITIALIZATION_FIXED_DELAY_MS:5000}
max-attempts: ${CUSTOMER_DIMENSION_INITIALIZATION_MAX_ATTEMPTS:12}
notification:
scheduler-enabled: ${NOTIFICATION_SCHEDULER_ENABLED:false}
dispatch-timeout-seconds: ${NOTIFICATION_DISPATCH_TIMEOUT_SECONDS:300}
@@ -47,4 +50,3 @@ logging:
max-history: "${LOGGING_ROLLING_MAX_HISTORY:7}"
total-size-cap: "${LOGGING_ROLLING_TOTAL_SIZE_CAP:5GB}"
clean-history-on-start: "${LOGGING_ROLLING_CLEAN_HISTORY_ON_START:false}"
@@ -0,0 +1,64 @@
package com.baiyue.iam.repository;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class CustomerDimensionInitializationSqlContractTest {
@Test
void latestIamSqlContainsStatusAndDurableTask() throws Exception {
String sql = new ClassPathResource("db/iam.sql").getContentAsString(StandardCharsets.UTF_8);
assertContract(sql);
}
@Test
void idempotentStandaloneAndUnifiedSqlContainTheSameTaskContract() throws Exception {
String standalone = new ClassPathResource("db/customer-dimension-initialization-task.sql")
.getContentAsString(StandardCharsets.UTF_8);
Path direct = Path.of("deploy", "sql", "头程数据库统一增量-MySQL8.sql");
Path unifiedPath = Files.exists(direct) ? direct
: Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql");
String unified = Files.readString(unifiedPath, StandardCharsets.UTF_8);
assertContract(standalone);
assertContract(unified);
assertThat(standalone).contains("information_schema.columns", "CREATE TABLE IF NOT EXISTS");
assertThat(unified).contains("information_schema.columns", "CREATE TABLE IF NOT EXISTS");
}
@Test
void schedulerRetryBudgetHasSafeOverridableDefaultsInAllDomesticProfiles() throws Exception {
String application = new ClassPathResource("application.yml").getContentAsString(StandardCharsets.UTF_8);
assertSchedulerConfig(application);
for (String profile : new String[] {"local", "cn"}) {
Path direct = Path.of("config", "nacos", profile, "iam-service.yml");
Path config = Files.exists(direct) ? direct
: Path.of("..", "config", "nacos", profile, "iam-service.yml");
assertSchedulerConfig(Files.readString(config, StandardCharsets.UTF_8));
}
}
private static void assertContract(String sql) {
assertThat(sql)
.contains("dimension_initialization_status")
.contains("iam_customer_dimension_initialization_task")
.contains("initialization_key")
.contains("attempt_count")
.contains("next_retry_at")
.contains("lease_until")
.contains("last_error_code")
.contains("task_version");
}
private static void assertSchedulerConfig(String yaml) {
assertThat(yaml)
.contains("customer-dimension-initialization:")
.contains("${CUSTOMER_DIMENSION_INITIALIZATION_FIXED_DELAY_MS:5000}")
.contains("${CUSTOMER_DIMENSION_INITIALIZATION_MAX_ATTEMPTS:12}");
}
}
@@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
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.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -47,7 +48,7 @@ class CustomerDimensionInitializationRelayTest {
relay.deliver(10001L);
verify(repository).markReady(task, workerId(), 91L);
verify(repository, never()).recordFailure(any(), anyString(), anyLong(), any(), anyString(),
verify(repository, never()).recordFailure(any(), anyString(), anyInt(), any(), anyString(),
anyString(), any(Boolean.class));
}
@@ -60,7 +61,7 @@ class CustomerDimensionInitializationRelayTest {
relay.deliver(10001L);
ArgumentCaptor<Boolean> exhausted = ArgumentCaptor.forClass(Boolean.class);
verify(repository).recordFailure(any(), anyString(), anyLong(), any(), anyString(),
verify(repository).recordFailure(any(), anyString(), anyInt(), any(), anyString(),
anyString(), exhausted.capture());
assertFalse(exhausted.getValue());
}
@@ -75,7 +76,7 @@ class CustomerDimensionInitializationRelayTest {
relay.deliver(10001L);
ArgumentCaptor<Boolean> exhausted = ArgumentCaptor.forClass(Boolean.class);
verify(repository).recordFailure(any(), anyString(), anyLong(), any(), anyString(),
verify(repository).recordFailure(any(), anyString(), anyInt(), any(), anyString(),
anyString(), exhausted.capture());
assertTrue(exhausted.getValue());
verify(repository, never()).markReady(any(), anyString(), any());
@@ -74,6 +74,8 @@ class MerchantOnboardingServiceTest {
"INITIAL_AUTHORIZATION");
assertEquals("PROFILE_REQUIRED", result.tenantStatus());
assertEquals("PENDING", result.dimensionInitializationStatus());
assertEquals("PENDING", tenant.getValue().getDimensionInitializationStatus());
assertEquals(result.tenantId(), tenant.getValue().getId());
assertEquals("测试企业", tenant.getValue().getLegalName());
assertEquals("张三", tenant.getValue().getLegalRepresentative());
@@ -122,7 +124,7 @@ class MerchantOnboardingServiceTest {
}
@Test
void clientRegistrationFailsWhenFourDefaultBoxDimensionsCannotBeInitialized() {
void clientRegistrationFailsWhenDurableInitializationIntentCannotBePersisted() {
IamRole ownerRole = new IamRole();
ownerRole.setId(4000100L);
ownerRole.setTenantId(0L);
@@ -199,4 +201,3 @@ class MerchantOnboardingServiceTest {
verify(tenants, never()).deleteById((java.io.Serializable) any());
}
}
@@ -313,6 +313,7 @@ class TenantServiceTest {
9001L);
assertEquals("PROFILE_REQUIRED", created.tenant().status());
assertEquals("PENDING", created.tenant().dimensionInitializationStatus());
assertEquals("Bob", created.tenant().legalRepresentative());
assertEquals("基础版", created.tenant().servicePackage());
assertEquals("13800138000", created.administratorUsername());
@@ -326,7 +327,7 @@ class TenantServiceTest {
}
@Test
void platformRegistrationFailsWhenFourDefaultBoxDimensionsCannotBeInitialized() {
void platformRegistrationFailsWhenDurableInitializationIntentCannotBePersisted() {
doThrow(new BizException(ResultCode.SERVICE_UNAVAILABLE, "初始化失败"))
.when(dimensionRuleInitialization).initialize(any(Long.class), any(Long.class));
@@ -491,12 +492,26 @@ class TenantServiceTest {
assertEquals(true, withQualification.eligible());
}
@Test
void activeCustomerCannotOrderUntilDefaultDimensionsAreReady() {
IamTenant tenant = merchant(10001L, "ACTIVE");
tenant.setDimensionInitializationStatus("FAILED");
when(repository.findTenant(10001L)).thenReturn(tenant);
var result = service.orderEligibility(10001L);
assertEquals(false, result.eligible());
assertEquals("Customer default dimensions are not ready", result.reason());
verify(profileRepository, never()).findQualifications(10001L);
}
private IamTenant merchant(long id, String status) {
IamTenant tenant = new IamTenant();
tenant.setId(id);
tenant.setTenantCode("TEN" + id);
tenant.setTenantType("MERCHANT");
tenant.setStatus(status);
tenant.setDimensionInitializationStatus("READY");
tenant.setTenantName("Example Merchant");
tenant.setLegalName("Example Merchant Ltd");
tenant.setUnifiedSocialCreditCode("91330100EXAMPLE001");
@@ -529,4 +544,3 @@ class TenantServiceTest {
return review;
}
}