test(sql): align contracts with clean initialization

This commit is contained in:
2026-09-17 15:10:27 +08:00
parent 1fe99eb6d0
commit 9dc344bdef
68 changed files with 851 additions and 1448 deletions
@@ -7,23 +7,18 @@ import static org.assertj.core.api.Assertions.assertThat;
class NotificationSqlContractTest {
@Test void definesTablesIsolationIdempotencyAndPermissions() throws Exception {String sql;try(var in=getClass().getResourceAsStream("/db/notification-center.sql")){assertThat(in).isNotNull();sql=new String(in.readAllBytes(),StandardCharsets.UTF_8);}assertThat(sql).contains("iam_notification_template","iam_notification_task","iam_notification_recipient","iam_notification_inbox","iam_notification_event_inbox","uk_notification_task_no","uk_notification_task_idempotency","uk_notification_source_event","uk_notification_recipient","idx_notification_inbox_list","platform:notification:template:status","platform:notification:task:retry","platform:notification:read","platform:notification:read-all","merchant:notification:view","merchant:notification:read","merchant:notification:read-all").contains("'MENU',5106000,'platform:notification:template:list'","'MENU',5106000,'platform:notification:task:list'");}
@Test void unifiedMigrationContainsNotificationBaselineAndSevenIndexAssertion() throws Exception {
@Test void cleanInitializationContainsNotificationSchemaAndPermissionSeeds() throws Exception {
Path direct=Path.of("deploy","sql","头程数据库统一增量-MySQL8.sql");
Path migration=Files.exists(direct)?direct:Path.of("..","deploy","sql","头程数据库统一增量-MySQL8.sql").normalize();
String sql=Files.readString(migration,StandardCharsets.UTF_8);
assertThat(sql).contains("iam_notification_template","iam_notification_task","iam_notification_recipient",
assertThat(sql).contains("CREATE TABLE iam_notification_template","CREATE TABLE iam_notification_task","CREATE TABLE iam_notification_recipient",
"iam_notification_inbox","iam_notification_event_inbox","uk_notification_template_code",
"uk_notification_task_no","uk_notification_task_idempotency","uk_notification_source_event",
"uk_notification_recipient","uk_notification_inbox_recipient","uk_notification_event",
"assert_notification_center_schema","permission_code='merchant:notification:view'",
"permission_code LIKE 'platform:notification%'");
assertThat(sql).contains("COUNT(DISTINCT index_name) FROM information_schema.statistics WHERE table_schema='baiyue_iam'");
"'merchant:notification:view'", "'platform:notification:template:list'",
"'platform:notification:task:list'");
assertThat(sql)
.contains(")) <> 7 THEN")
.contains("permission_code='merchant:notification:view')) <> 13 THEN")
.contains("permission_code LIKE 'platform:notification%') <> 12 THEN")
.contains("'merchant:notification:view','merchant:notification:read','merchant:notification:read-all')) <> 3 THEN");
assertThat(sql.indexOf("(5106014,0,'MERCHANT','BUTTON'"))
.isLessThan(sql.indexOf("CALL baiyue_iam.assert_notification_center_schema();"));
.contains("(5106014,0,'MERCHANT','BUTTON'")
.doesNotContain("assert_notification_center_schema", "information_schema.statistics");
}
}
@@ -60,15 +60,9 @@ class AdminPrototypeMenuPermissionSqlContractTest {
}
assertThat(unified)
.contains("platform:finance:receivable-statistics:export")
.contains("permission_code LIKE 'platform:finance:%' AND status='ACTIVE' AND deleted=0) <> 34")
.contains("(p.permission_code='platform:finance' OR p.permission_code LIKE 'platform:finance:%')) <> 35")
.contains("Finance 导出和下载中心授权不完整")
.contains("客户全局 Finance 权限模板不完整")
.contains("客户全局管理员 Finance 模板授权不完整")
.satisfies(content -> assertThat(content.indexOf("(5105050,0,'PLATFORM','BUTTON'"))
.isLessThan(content.indexOf("CREATE PROCEDURE assert_finance_permissions()")))
.satisfies(content -> assertThat(content.indexOf("CALL migrate_wallet_permissions();"))
.isLessThan(content.indexOf("CALL assert_finance_permissions();")));
.contains("(4000001,5105050,0,CURRENT_TIMESTAMP(3))")
.contains("(4000100,5305010,0,CURRENT_TIMESTAMP(3))")
.doesNotContain("CREATE PROCEDURE assert_finance_permissions", "CALL migrate_wallet_permissions");
}
@Test
@@ -80,8 +74,10 @@ class AdminPrototypeMenuPermissionSqlContractTest {
.contains("'platform:overseas-storage-fee:create','platform:overseas-storage-fee:delete'");
String unified = Files.readString(resolveUnifiedSql(), StandardCharsets.UTF_8);
assertThat(unified).contains("<> 4")
.contains("platform:overseas-storage-fee:list', 'platform:overseas-storage-fee:create'");
assertThat(unified)
.contains("'platform:overseas-storage-fee:list','海外仓阶梯费用','/customers/overseas-storage-fees','GET',20,'ACTIVE'")
.contains("'platform:overseas-storage-fee:create','新增海外仓阶梯费用',NULL,NULL,10,'INACTIVE'")
.contains("'platform:overseas-storage-fee:delete','删除海外仓阶梯费用',NULL,NULL,30,'INACTIVE'");
}
@Test
@@ -90,27 +90,16 @@ class ContainerWorkspacePermissionSqlContractTest {
}
@Test
void unifiedIncrementContainsTheCompleteModuleSql() throws Exception {
void cleanInitializationContainsTheCompletePermissionCatalog() throws Exception {
Path unifiedPath = Path.of("deploy", "sql", "头程数据库统一增量-MySQL8.sql");
if (!Files.exists(unifiedPath)) {
unifiedPath = Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql");
}
String unified = Files.readString(unifiedPath, StandardCharsets.UTF_8);
String startMarker = "-- 货柜双工作台权限开始(与 iam/db/container-workspace-permissions.sql 保持一致)";
String endMarker = "-- 货柜双工作台权限结束,恢复头程数据库上下文";
int start = unified.indexOf(startMarker);
int bodyStart = unified.indexOf('\n', start) + 1;
int end = unified.indexOf(endMarker, bodyStart);
assertThat(start).isGreaterThanOrEqualTo(0);
assertThat(bodyStart).isGreaterThan(start);
assertThat(end).isGreaterThan(bodyStart);
assertThat(normalizeLineEndings(unified.substring(bodyStart, end)).trim())
.isEqualTo(normalizeLineEndings(sql()).trim());
}
private static String normalizeLineEndings(String value) {
return value.replace("\r\n", "\n");
assertThat(codes(unified)).containsAll(REQUIRED);
assertThat(unified)
.contains("(5102090,0,'PLATFORM','MENU'", "(5102205,0,'PLATFORM','BUTTON'")
.doesNotContain("migrate_container_workspace_permissions", "ON DUPLICATE KEY UPDATE");
}
private static String sql() throws Exception {
@@ -20,15 +20,24 @@ class CustomerCodeSchemaContractTest {
}
String unified = Files.readString(unifiedPath, StandardCharsets.UTF_8);
assertSequenceDefinition(baseline);
assertSequenceDefinition(unified);
assertMigrationSequenceDefinition(baseline);
assertInitializationSequenceDefinition(unified);
}
private static void assertSequenceDefinition(String sql) {
private static void assertMigrationSequenceDefinition(String sql) {
assertThat(sql)
.contains("tenant_code VARCHAR(8) NOT NULL")
.contains("CREATE TABLE IF NOT EXISTS iam_number_sequence")
.contains("PRIMARY KEY (sequence_key)")
.contains("INSERT IGNORE INTO iam_number_sequence(sequence_key, next_value) VALUES ('CUSTOMER_CODE', 1)");
}
private static void assertInitializationSequenceDefinition(String sql) {
assertThat(sql)
.contains("tenant_code varchar(8) NOT NULL")
.contains("CREATE TABLE iam_number_sequence")
.contains("PRIMARY KEY (sequence_key)")
.contains("INSERT INTO iam_number_sequence (sequence_key, next_value, updated_at) VALUES ('CUSTOMER_CODE',1")
.doesNotContain("CREATE TABLE IF NOT EXISTS iam_number_sequence", "INSERT IGNORE INTO iam_number_sequence");
}
}
@@ -28,7 +28,9 @@ class CustomerDimensionInitializationSqlContractTest {
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");
assertThat(unified)
.contains("CREATE TABLE iam_customer_dimension_initialization_task")
.doesNotContain("information_schema.columns", "CREATE TABLE IF NOT EXISTS");
}
@Test
@@ -49,7 +49,7 @@ class CustomerProfileMigrationContractTest {
}
@Test
void shouldHandleCase03() throws IOException {
void cleanInitializationContainsTheFinalCustomerProfileContract() throws IOException {
Path repositoryRoot = Path.of(System.getProperty("maven.multiModuleProjectDirectory", ".."));
Path migration = repositoryRoot.resolve("deploy/sql/头程数据库统一增量-MySQL8.sql");
Path readme = repositoryRoot.resolve("deploy/sql/README.md");
@@ -60,43 +60,36 @@ class CustomerProfileMigrationContractTest {
String instructions = Files.readString(readme, StandardCharsets.UTF_8);
assertThat(sql)
.contains("customer_nickname VARCHAR(32)")
.contains("ALTER TABLE iam_tenant ADD COLUMN customer_nickname VARCHAR(32)")
.contains("ALTER TABLE iam_tenant MODIFY COLUMN status")
.contains("customer_nickname varchar(32)")
.contains("DEFAULT 'PROFILE_REQUIRED'")
.contains("CALL phase1_iam_add_column('iam_tenant', 'approved_at'")
.contains("CALL phase1_iam_add_column('iam_tenant', 'approved_by'")
.contains("CALL phase1_iam_add_column('iam_tenant', 'reject_reason'")
.contains("CALL phase1_iam_add_column('iam_tenant_qualification', 'review_comment'")
.contains("approved_at datetime(3)")
.contains("approved_by bigint")
.contains("reject_reason varchar(500)")
.contains("review_comment varchar(500)")
.contains("platform:customer:view")
.contains("platform:customer:export")
.contains("platform:download:list")
.contains("'MERCHANT_OWNER'")
.contains("owner_role.id, permission.id, 0")
.contains("统一增量全部执行完成");
.contains("SELECT 'PASS' AS initialization_status")
.doesNotContain("ALTER TABLE iam_tenant", "phase1_iam_add_column");
assertThat(instructions)
.contains("phase1-platform-master-data-and-permission-management.sql")
.contains("phase1-customer-profile.sql")
.contains("phase1-onboarding.sql")
.contains("`PROFILE_REQUIRED` 默认状态、审核结果字段、资质审核备注及成员审计字段")
.contains("全局 `MERCHANT_OWNER` 角色模板")
.contains("测试环境结构发布只执行全量固定入口");
.contains("当前开发阶段只维护纯初始化")
.contains("模块 `src/main/resources/db/` 用作结构来源和开发追溯")
.contains("不再向历史迁移文件追加发布步骤");
}
@Test
void unifiedMigrationGrantsCustomerOperationsToPlatformAdmin() throws IOException {
void cleanInitializationGrantsCustomerOperationsToPlatformAdmin() throws IOException {
Path repositoryRoot = Path.of(System.getProperty("maven.multiModuleProjectDirectory", ".."));
String sql = Files.readString(
repositoryRoot.resolve("deploy/sql/头程数据库统一增量-MySQL8.sql"),
StandardCharsets.UTF_8);
int grantStart = sql.indexOf("-- 新增平台操作权限默认只授予系统管理员。");
int grantEnd = sql.indexOf("-- 已有客户运营角色继续具备", grantStart);
assertThat(grantStart).isGreaterThanOrEqualTo(0);
assertThat(grantEnd).isGreaterThan(grantStart);
assertThat(sql.substring(grantStart, grantEnd))
assertThat(sql)
.contains("'platform:customer:reset-password'")
.contains("'platform:customer:delete'")
.contains("'platform:customer:impersonate'");
.contains("'platform:customer:impersonate'")
.contains("(4000001,5100016,0,CURRENT_TIMESTAMP(3))")
.contains("(4000001,5100017,0,CURRENT_TIMESTAMP(3))")
.contains("(4000001,5100018,0,CURRENT_TIMESTAMP(3))");
}
}
@@ -46,28 +46,16 @@ class FinanceG1G2PermissionSqlContractTest {
@Test
void unifiedSqlMirrorsCatalogAndGrantsTheFinanceCatalogToPlatformAdmin() throws Exception {
String sql = Files.readString(resolveUnifiedSql(), StandardCharsets.UTF_8)
.replace("\r\n", "\n");
String financeSection = sql.substring(sql.indexOf("-- Finance F1 人工财务权限"),
sql.indexOf("-- Admin 原型菜单与财务五页权限补充"));
String sql = Files.readString(resolveUnifiedSql(), StandardCharsets.UTF_8);
assertThat(financeSection)
.contains("(5105024, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:fee-source:review'")
.contains("(5105025, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:fee-source:edit'")
.contains("NULL, 'POST', 150, 'ACTIVE', 1, 0")
.contains("NULL, 'PUT', 160, 'ACTIVE', 1, 0")
.contains("permission_code IN (\n 'platform:finance',")
.contains("Finance G1-G4");
assertThat(financeSection)
.contains("SELECT 4000001, id, 0 FROM baiyue_iam.iam_permission")
.contains("status='INACTIVE',deleted=1");
assertThat(sql)
.contains("(5105024,0,'PLATFORM','BUTTON',5105041,'platform:finance:fee-source:review'")
.contains("(5105025,0,'PLATFORM','BUTTON',5105041,'platform:finance:fee-source:edit'")
.contains("(4000001,5105024,0,CURRENT_TIMESTAMP(3))")
.contains("(4000001,5105025,0,CURRENT_TIMESTAMP(3))")
.contains("platform:finance:settlement-ar:list")
.contains("platform:finance:settlement-ap:list")
.contains("permission_code LIKE 'platform:finance:%' AND status='ACTIVE' AND deleted=0) <> 34")
.contains("rp.role_id=4000001")
.contains("LIKE 'platform:finance:%')) <> 35")
.contains("平台管理员 Finance 授权不完整");
.doesNotContain("migrate_finance_g1_g4_permissions", "status='INACTIVE',deleted=1");
}
private static Path resolveUnifiedSql() {
@@ -39,21 +39,13 @@ class FinanceG3G4PermissionSqlContractTest {
@Test
void unifiedSqlContainsCatalogAndGrantsItToPlatformAdmin() throws Exception {
String sql = Files.readString(resolveUnifiedSql(), StandardCharsets.UTF_8);
String financeSection = sql.substring(sql.indexOf("-- Finance F1 人工财务权限"),
sql.indexOf("-- Admin 原型菜单与财务五页权限补充"));
for (String code : CODES) assertThat(financeSection).contains(code);
assertThat(financeSection)
.contains("migrate_finance_g1_g4_permissions")
.contains("finance G1-G4 permission id/code conflict");
assertThat(financeSection)
.contains("SELECT 4000001, id, 0 FROM baiyue_iam.iam_permission")
.contains("platform:finance:reconciliation:create");
for (String code : CODES) assertThat(sql).contains(code);
assertThat(sql)
.contains("(4000001,5105026,0,CURRENT_TIMESTAMP(3))")
.contains("(4000001,5105035,0,CURRENT_TIMESTAMP(3))")
.contains("platform:finance:settlement-ar:list")
.contains("platform:finance:settlement-ap:list")
.contains("permission_code LIKE 'platform:finance:%' AND status='ACTIVE' AND deleted=0) <> 34")
.contains("rp.role_id=4000001")
.contains("LIKE 'platform:finance:%')) <> 35");
.doesNotContain("migrate_finance_g1_g4_permissions", "finance G1-G4 permission id/code conflict");
}
private static Path resolveUnifiedSql() {
@@ -63,10 +63,11 @@ class InboundHoldPermissionSqlContractTest {
}
@Test
void unifiedIncrementContainsTheSameMigrationBody() throws Exception {
String marker = "DROP PROCEDURE IF EXISTS migrate_inbound_hold_permission;";
assertThat(migrationBody(unifiedSql(), marker)).isEqualTo(migrationBody(sql(), marker));
void cleanInitializationContainsThePermissionAndDefaultGrant() throws Exception {
assertThat(unifiedSql())
.contains("(5102032,0,'PLATFORM','BUTTON',5102030,'platform:firstleg:inbound:hold'")
.contains("(4000001,5102032,0,CURRENT_TIMESTAMP(3))")
.doesNotContain("migrate_inbound_hold_permission", "ON DUPLICATE KEY UPDATE");
}
private static String sql() throws Exception {
@@ -80,11 +81,4 @@ class InboundHoldPermissionSqlContractTest {
return Files.readString(path.normalize(), StandardCharsets.UTF_8);
}
private static String migrationBody(String sql, String marker) {
int start = sql.indexOf(marker);
int end = sql.indexOf(marker, start + marker.length());
assertThat(start).isGreaterThanOrEqualTo(0);
assertThat(end).isGreaterThan(start);
return sql.substring(start, end + marker.length()).replaceAll("\\s+", " ").trim();
}
}
@@ -18,9 +18,17 @@ class InitialIdentitySeedSqlContractTest {
.getContentAsString(StandardCharsets.UTF_8);
String unified = Files.readString(unifiedSql(), StandardCharsets.UTF_8);
assertPlatformIdentitySeed(identitySeed);
assertPlatformIdentitySeed(unified);
assertCleanPlatformIdentitySeed(unified);
assertGlobalMerchantTemplate(templateSeed);
assertGlobalMerchantTemplate(unified);
assertCleanGlobalMerchantTemplate(unified);
}
private static void assertCleanPlatformIdentitySeed(String sql) {
assertThat(sql)
.contains("(0,'PLATFORM','虫虫出海平台'")
.contains("(2000001,1000001,'INTERNAL','平台系统管理员'")
.contains("(3000001,0,2000001,'EMPLOYEE'")
.doesNotContain("merchant_admin", "IT_TEST", "一期演示企业", "ON DUPLICATE KEY UPDATE");
}
private static void assertPlatformIdentitySeed(String sql) {
@@ -47,6 +55,14 @@ class InitialIdentitySeedSqlContractTest {
.doesNotContain("WHERE tenant_id = 10001 AND app_code = 'MERCHANT'");
}
private static void assertCleanGlobalMerchantTemplate(String sql) {
assertThat(sql)
.contains("(4000100,0,'MERCHANT','MERCHANT_OWNER'")
.contains("(5300000,0,'MERCHANT','MENU'")
.contains("(5301024,0,'MERCHANT','BUTTON'")
.doesNotContain("WHERE tenant_id = 10001 AND app_code = 'MERCHANT'");
}
private static Path unifiedSql() {
Path path = Path.of("deploy", "sql", "头程数据库统一增量-MySQL8.sql");
return Files.exists(path) ? path : Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql");
@@ -13,7 +13,16 @@ class LocationPermissionSqlContractTest {
@Test
void acceptsKnownPollutionAndRestoresCanonicalPermissionIds() throws Exception {
assertRepairContract(moduleSql());
assertRepairContract(unifiedSql());
assertFinalInitializationContract(unifiedSql());
}
private static void assertFinalInitializationContract(String sql) {
assertThat(sql)
.contains("(5103040,0,'PLATFORM','MENU',5103000,'platform:download:list'")
.contains("(5103041,0,'PLATFORM','BUTTON',5103040,'platform:download:all'")
.contains("(5103060,0,'PLATFORM','MENU',5103000,'platform:location:list'")
.contains("(5103061,0,'PLATFORM','BUTTON',5103060,'platform:location:create'")
.doesNotContain("tmp_location_role_grant", "库位权限迁移失败");
}
private static void assertRepairContract(String sql) {
@@ -64,10 +64,10 @@ class OverseasPhase4PermissionSqlContractTest {
Set<String> moduleCatalog = codes(new ClassPathResource("db/lastleg-permissions.sql")
.getContentAsString(StandardCharsets.UTF_8));
assertThat(codes(sql)).containsAll(moduleCatalog);
assertThat(sql).contains("baiyue_iam.iam_permission", "baiyue_iam.iam_role_permission", "'lastleg:pda:operate'");
assertPdaPickerGrant(sql);
assertCustomAdminGrant(sql);
assertCustomAdminNavigationGrant(sql);
assertThat(sql)
.contains("CREATE TABLE iam_permission", "CREATE TABLE iam_role_permission", "'lastleg:pda:operate'")
.contains("(4000005,5107015,0,CURRENT_TIMESTAMP(3))")
.doesNotContain("assert_overseas_pda_picker_permission", "DELETE role_permission FROM");
}
@Test
@@ -78,25 +78,33 @@ class OverseasPhase4PermissionSqlContractTest {
if (!Files.exists(path)) path = Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql");
String unifiedSql = Files.readString(path, StandardCharsets.UTF_8);
for (String sql : List.of(moduleSql, unifiedSql)) {
String matrix = matrixBlock(sql);
assertThat(matrix).contains("WAREHOUSE_OPERATOR", "WAREHOUSE_TALLY");
assertThat(codes(matrix)).containsExactlyInAnyOrder(
"platform:overseas-warehouse:last-mile-order:list",
"platform:overseas-warehouse:inbound-order:list",
"platform:overseas-warehouse:relabel-order:list",
"platform:overseas-warehouse:inventory:list",
"platform:overseas-warehouse:warehouse:list",
"platform:overseas-warehouse:location:list",
"platform:overseas-warehouse:pallet:list",
"platform:overseas-business:purchase:list",
"platform:overseas-business:truck:list",
"platform:overseas-business:looseboard:list");
assertThat(matrix).doesNotContain("OVERSEAS_WAREHOUSE_PDA_OPERATOR");
assertThat(matrix).doesNotContain(":create'", ":edit'", ":status'", ":export'", ":adjust'");
assertThat(sql).contains("assert_phase4_warehouse_role_minimum", "<> 20",
"role.role_code IN ('WAREHOUSE_OPERATOR','OVERSEAS_WAREHOUSE_PDA_OPERATOR','WAREHOUSE_TALLY','CN_FIRSTLEG_PDA_OPERATOR')");
String matrix = matrixBlock(moduleSql);
assertThat(matrix).contains("WAREHOUSE_OPERATOR", "WAREHOUSE_TALLY");
assertThat(codes(matrix)).containsExactlyInAnyOrder(
"platform:overseas-warehouse:last-mile-order:list",
"platform:overseas-warehouse:inbound-order:list",
"platform:overseas-warehouse:relabel-order:list",
"platform:overseas-warehouse:inventory:list",
"platform:overseas-warehouse:warehouse:list",
"platform:overseas-warehouse:location:list",
"platform:overseas-warehouse:pallet:list",
"platform:overseas-business:purchase:list",
"platform:overseas-business:truck:list",
"platform:overseas-business:looseboard:list");
assertThat(matrix).doesNotContain("OVERSEAS_WAREHOUSE_PDA_OPERATOR");
assertThat(matrix).doesNotContain(":create'", ":edit'", ":status'", ":export'", ":adjust'");
assertThat(moduleSql).contains("assert_phase4_warehouse_role_minimum", "<> 20",
"role.role_code IN ('WAREHOUSE_OPERATOR','OVERSEAS_WAREHOUSE_PDA_OPERATOR','WAREHOUSE_TALLY','CN_FIRSTLEG_PDA_OPERATOR')");
for (int permissionId : new int[] {5107000, 5107010, 5107020, 5107030, 5107040,
5107050, 5107060, 5107100, 5107110, 5107120}) {
assertThat(unifiedSql)
.contains("(4000004," + permissionId + ",0,CURRENT_TIMESTAMP(3))")
.contains("(4000006," + permissionId + ",0,CURRENT_TIMESTAMP(3))");
}
assertThat(unifiedSql)
.doesNotContain("(4000005,5107000,0,CURRENT_TIMESTAMP(3))")
.doesNotContain("(4000007,5107000,0,CURRENT_TIMESTAMP(3))");
}
@Test
@@ -143,35 +143,28 @@ class Phase5DeliveryPermissionSqlContractTest {
}
@Test
void domesticUnifiedEntryInlinesTheCompleteModuleSqlAfterPhase4Permissions() throws Exception {
String moduleSql = sql().trim();
void domesticCleanInitializationContainsTheCompletePermissionCatalog() throws Exception {
String unifiedSql = repositoryFile("deploy/sql/头程数据库统一增量-MySQL8.sql");
int overseasCatalog = unifiedSql.indexOf("-- 海外权限统一目录");
int outboundDelivery = unifiedSql.indexOf("-- 海外排车、出库与派送权限(来源:iam/db/phase5-delivery-permissions.sql)");
int bodyStart = unifiedSql.indexOf("USE baiyue_iam;", outboundDelivery);
int nextSection = unifiedSql.indexOf("-- IAM 客户提交即成功", bodyStart);
assertThat(overseasCatalog).isGreaterThanOrEqualTo(0);
assertThat(outboundDelivery).isGreaterThan(overseasCatalog);
assertThat(bodyStart).isGreaterThan(outboundDelivery);
assertThat(nextSection).isGreaterThan(bodyStart);
assertThat(unifiedSql.substring(bodyStart, nextSection).trim()).isEqualTo(moduleSql);
assertThat(codes(unifiedSql.substring(outboundDelivery, nextSection)))
.containsExactlyInAnyOrderElementsOf(REQUIRED);
assertThat(codes(unifiedSql)).containsAll(REQUIRED);
assertThat(unifiedSql)
.contains("(4000004,5107064,0,CURRENT_TIMESTAMP(3))")
.contains("(4000004,5107094,0,CURRENT_TIMESTAMP(3))")
.contains("(4000100,5306000,0,CURRENT_TIMESTAMP(3))")
.contains("(4000100,5306001,0,CURRENT_TIMESTAMP(3))")
.doesNotContain("guard_phase5_delivery_permission_ids", "tmp_phase5_delivery_role_permission");
}
@Test
void deploymentGuideUsesOnlyTheDomesticUnifiedEntry() throws Exception {
void deploymentGuideDefinesCleanRegionalInitializationOnly() throws Exception {
String readme = repositoryFile("deploy/sql/README.md");
assertThat(readme).contains(
"海外排车、出库与派送权限",
"测试和部署只执行固定入口,不再单独执行模块脚本",
"排车列表/编辑/取消/转派送",
"异常订单列表/创建/处理和客户回访列表/编辑十六项权限",
"merchant:warehouse:delivery:list",
"merchant:warehouse:delivery:create",
"PDA 岗位不得获得上述后台权限");
"当前开发阶段只维护纯初始化",
"两个 SQL 文件沿用原文件名",
"国内 IAM 是全部平台与客户权限的唯一权威",
"模块 `src/main/resources/db/` 用作结构来源和开发追溯",
"不再向历史迁移文件追加发布步骤");
}
private static String sql() throws Exception {
@@ -22,10 +22,7 @@ class QualificationSchemaContractTest {
assertThat(baseline).contains("original_file_name VARCHAR(255) NOT NULL COMMENT '上传时原始文件名'");
assertThat(unified)
.contains("original_file_name VARCHAR(255) NOT NULL COMMENT '上传时原始文件名'")
.contains("CALL phase1_iam_add_column('iam_tenant_qualification', 'original_file_name',")
.contains("table_name='iam_tenant_qualification'")
.contains("column_name='original_file_name'")
.contains("统一迁移失败:IAM客户资质缺少原始文件名列");
.contains("original_file_name varchar(255) NOT NULL COMMENT '上传时原始文件名'")
.doesNotContain("phase1_iam_add_column", "information_schema.columns");
}
}
@@ -26,18 +26,17 @@ class SecurityEventSqlContractTest {
}
@Test
void unifiedMigrationIncludesSecurityEventSchemaAndAssertion() throws Exception {
void cleanInitializationIncludesSecurityEventSchema() throws Exception {
Path direct = Path.of("deploy", "sql", "头程数据库统一增量-MySQL8.sql");
Path migration = Files.exists(direct)
? direct : Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql").normalize();
String sql = Files.readString(migration, StandardCharsets.UTF_8);
assertThat(sql)
.contains("CREATE TABLE IF NOT EXISTS iam_security_subject_version")
.contains("CREATE TABLE IF NOT EXISTS iam_security_tenant_version")
.contains("CREATE TABLE IF NOT EXISTS iam_security_event_outbox")
.contains("CREATE TABLE iam_security_subject_version")
.contains("CREATE TABLE iam_security_tenant_version")
.contains("CREATE TABLE iam_security_event_outbox")
.contains("idx_security_outbox_publish", "idx_security_outbox_aggregate")
.contains("CALL assert_iam_security_event_schema();");
assertThat(sql.indexOf("CALL assert_iam_security_event_schema();"))
.isLessThan(sql.lastIndexOf("SELECT 'PASS' AS migration_status"));
.contains("SELECT 'PASS' AS initialization_status")
.doesNotContain("assert_iam_security_event_schema", "CREATE TABLE IF NOT EXISTS iam_security_event");
}
}
@@ -18,13 +18,19 @@ class WarehouseRoleSqlContractTest {
@Test
void unifiedDeploymentContainsTheSameWarehouseRoleContract() throws Exception {
String sql = unifiedSql();
assertWarehouseRoleContract(sql);
assertThat(roleMigrationBlock(sql))
.doesNotContain("\n ADD COLUMN IF NOT EXISTS")
.doesNotContain("DROP CHECK IF EXISTS");
assertThat(sql.lastIndexOf("SELECT 'PASS' AS migration_status"))
.isGreaterThan(sql.indexOf("CALL assert_phase3_warehouse_roles_ready()"));
assertThat(sql.trim()).endsWith("AS message;");
assertThat(sql)
.contains("(4000004,0,'PLATFORM','WAREHOUSE_OPERATOR','国内仓后台操作员','SYSTEM'")
.contains("(4000005,0,'PLATFORM','OVERSEAS_WAREHOUSE_PDA_OPERATOR','海外仓 PDA 作业员','SYSTEM'")
.contains("(4000006,0,'PLATFORM','WAREHOUSE_TALLY','国内仓理货复核员','SYSTEM'")
.contains("(4000007,0,'PLATFORM','CN_FIRSTLEG_PDA_OPERATOR','国内头程 PDA 作业员','SYSTEM'")
.contains("(6000004,4000004,0,'TENANT',NULL")
.contains("(6000005,4000005,0,'TENANT',NULL")
.contains("(6000006,4000006,0,'TENANT',NULL")
.contains("(6000007,4000007,0,'TENANT',NULL")
.contains("(4000005,5107015,0,CURRENT_TIMESTAMP(3))")
.contains("(4000007,5102190,0,CURRENT_TIMESTAMP(3))")
.contains("SELECT 'PASS' AS initialization_status")
.doesNotContain("assert_phase3_warehouse_roles_ready", "ON DUPLICATE KEY UPDATE");
}
private static void assertWarehouseRoleContract(String sql) {
@@ -100,15 +106,6 @@ class WarehouseRoleSqlContractTest {
return sql.substring(start, end > start ? end : sql.length());
}
private static String roleMigrationBlock(String sql) {
String marker = "-- 来源:iam/src/main/resources/db/phase3-warehouse-roles.sql";
String endMarker = "-- BEGIN FINANCE FEE SOURCE MODULE MIGRATIONS";
int start = sql.indexOf(marker);
int end = sql.indexOf(endMarker, start + marker.length());
if (start < 0 || end < 0) throw new IllegalStateException("统一入口缺少仓库角色连续区块");
return sql.substring(start, end);
}
private static String moduleSql() throws Exception {
return new ClassPathResource("db/phase3-warehouse-roles.sql")
.getContentAsString(StandardCharsets.UTF_8);