diff --git a/finance/src/test/java/com/baiyue/finance/FinanceContractTest.java b/finance/src/test/java/com/baiyue/finance/FinanceContractTest.java index a7d673357..d3cbb730c 100644 --- a/finance/src/test/java/com/baiyue/finance/FinanceContractTest.java +++ b/finance/src/test/java/com/baiyue/finance/FinanceContractTest.java @@ -43,12 +43,11 @@ class FinanceContractTest { assertFalse(operations.contains("bl.source_id=f.source_fee_id")); String unified = Files.readString(Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql")); assertTrue(unified.contains("CREATE DATABASE IF NOT EXISTS baiyue_finance")); - assertTrue(unified.contains("assert_finance_permissions")); assertTrue(unified.contains("merchant:finance:statement:view")); - int walletEventSchema = unified.indexOf("CREATE TABLE IF NOT EXISTS fin_wallet_event_outbox"); - int finalPass = unified.lastIndexOf("SELECT 'PASS' AS migration_status"); + int walletEventSchema = unified.indexOf("CREATE TABLE fin_wallet_event_outbox"); + int finalPass = unified.lastIndexOf("SELECT 'PASS' AS initialization_status"); assertTrue(walletEventSchema >= 0); assertTrue(finalPass > walletEventSchema); - assertTrue(unified.contains("CREATE PROCEDURE assert_wallet_event_schema()")); + assertFalse(unified.contains("CREATE PROCEDURE assert_wallet_event_schema()")); } } diff --git a/finance/src/test/java/com/baiyue/finance/FinanceSettlementSchemaContractTest.java b/finance/src/test/java/com/baiyue/finance/FinanceSettlementSchemaContractTest.java index c8b178151..13f0ae1e8 100644 --- a/finance/src/test/java/com/baiyue/finance/FinanceSettlementSchemaContractTest.java +++ b/finance/src/test/java/com/baiyue/finance/FinanceSettlementSchemaContractTest.java @@ -22,12 +22,13 @@ class FinanceSettlementSchemaContractTest { .contains("idx_fin_settlement_customer_direction_status (tenant_id, direction, status)") .contains("total_amount DECIMAL(20,6)", "paid_amount DECIMAL(20,6)", "outstanding_amount DECIMAL(20,6)") .contains("updated_at DATETIME(3)"); - assertThat(unified) - .contains("CREATE TABLE IF NOT EXISTS fin_settlement (") - .contains("CREATE TABLE IF NOT EXISTS fin_settlement_line (") - .contains("CREATE TABLE IF NOT EXISTS fin_settlement_payment (") - .contains("idx_fin_settlement_customer_direction_status (tenant_id, direction, status)") - .contains("'fin_settlement', 'fin_settlement_line', 'fin_settlement_payment')) <> 16"); + String normalized = unified.toLowerCase().replaceAll("\\s+", ""); + assertThat(normalized) + .contains("createtablefin_settlement(") + .contains("createtablefin_settlement_line(") + .contains("createtablefin_settlement_payment(") + .contains("idx_fin_settlement_customer_direction_status(tenant_id,direction,status)"); + assertThat(unified).doesNotContain("ALTER TABLE fin_settlement", "assert_finance"); } private static Path resolveUnifiedSql() { diff --git a/finance/src/test/java/com/baiyue/finance/FinanceSqlCommentContractTest.java b/finance/src/test/java/com/baiyue/finance/FinanceSqlCommentContractTest.java index 964d12bdd..30795d6ec 100644 --- a/finance/src/test/java/com/baiyue/finance/FinanceSqlCommentContractTest.java +++ b/finance/src/test/java/com/baiyue/finance/FinanceSqlCommentContractTest.java @@ -62,16 +62,14 @@ class FinanceSqlCommentContractTest { } @Test - void unifiedMigrationAddsBillLineAdjustmentReasonForNewAndExistingSchemas() throws Exception { + void unifiedInitializationDefinesBillLineAdjustmentReasonDirectly() throws Exception { Path direct = Path.of("deploy", "sql", "头程数据库统一增量-MySQL8.sql"); Path unified = Files.exists(direct) ? direct : Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"); String sql = Files.readString(unified, StandardCharsets.UTF_8); assertThat(sql) - .contains("adjustment_reason VARCHAR(500) NULL COMMENT '最近一次账单调整行原因;完整历史保存在修订与审计记录'") - .contains("table_schema='baiyue_finance' AND table_name='fin_bill_line' AND column_name='adjustment_reason'") - .contains("ALTER TABLE baiyue_finance.fin_bill_line ADD COLUMN adjustment_reason") - .contains("queue_column_comment('baiyue_finance', 'fin_bill_line', 'adjustment_reason'"); + .contains("adjustment_reason varchar(500) DEFAULT NULL COMMENT '最近一次账单调整行原因;完整历史保存在修订与审计记录'") + .doesNotContain("ALTER TABLE baiyue_finance.fin_bill_line", "queue_column_comment"); } @Test @@ -97,12 +95,17 @@ class FinanceSqlCommentContractTest { assertThat(baseline) .contains("updated_by_name VARCHAR(128) NULL COMMENT '最后修改操作人名称快照'") .contains("created_by_name VARCHAR(128) NULL COMMENT '登记操作人名称快照'"); - assertThat(unified) - .contains("CALL finance_add_operator_snapshot_column('fin_bill', 'created_by_name', 'created_by')") - .contains("CALL finance_add_operator_snapshot_column('fin_bill', 'updated_by_name', 'updated_by')") - .contains("CALL finance_add_operator_snapshot_column('fin_receipt', 'created_by_name', 'created_by')") - .contains("CALL finance_add_operator_snapshot_column('fin_cost', 'created_by_name', 'created_by')") - .contains("CALL finance_add_operator_snapshot_column('fin_settlement_payment', 'created_by_name', 'created_by')") - .contains("CALL finance_add_operator_snapshot_column('fin_bill_revision', 'created_by_name', 'created_by')"); + assertThat(tableBlock(unified, "fin_bill")).contains("created_by_name", "updated_by_name"); + assertThat(tableBlock(unified, "fin_receipt")).contains("created_by_name"); + assertThat(tableBlock(unified, "fin_cost")).contains("created_by_name"); + assertThat(tableBlock(unified, "fin_settlement_payment")).contains("created_by_name"); + assertThat(tableBlock(unified, "fin_bill_revision")).contains("created_by_name"); + assertThat(unified).doesNotContain("finance_add_operator_snapshot_column"); + } + + private static String tableBlock(String sql, String table) { + int start = sql.indexOf("CREATE TABLE " + table + " ("); + int end = sql.indexOf(';', start); + return start < 0 || end < 0 ? "" : sql.substring(start, end); } } diff --git a/finance/src/test/java/com/baiyue/finance/FinanceSubjectSeedContractTest.java b/finance/src/test/java/com/baiyue/finance/FinanceSubjectSeedContractTest.java index a20c7772e..194cc0759 100644 --- a/finance/src/test/java/com/baiyue/finance/FinanceSubjectSeedContractTest.java +++ b/finance/src/test/java/com/baiyue/finance/FinanceSubjectSeedContractTest.java @@ -14,12 +14,14 @@ class FinanceSubjectSeedContractTest { String module = Files.readString(Path.of("src/main/resources/db/finance.sql"), StandardCharsets.UTF_8); String unified = Files.readString(Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql"), StandardCharsets.UTF_8); - for (String sql : new String[] {module, unified}) { - assertThat(sql) - .contains("INSERT INTO fin_subject") - .contains("('PLATFORM_DEFAULT', '平台', 'ACTIVE', 0, 0, 0)") - .contains("ON DUPLICATE KEY UPDATE") - .contains("status = 'ACTIVE'"); - } + assertThat(module) + .contains("INSERT INTO fin_subject") + .contains("('PLATFORM_DEFAULT', '平台', 'ACTIVE', 0, 0, 0)") + .contains("ON DUPLICATE KEY UPDATE") + .contains("status = 'ACTIVE'"); + assertThat(unified) + .contains("INSERT INTO fin_subject (id, subject_code, subject_name, status") + .contains("VALUES (1,'PLATFORM_DEFAULT','平台','ACTIVE',0,0,0") + .doesNotContain("ON DUPLICATE KEY UPDATE"); } } diff --git a/finance/src/test/java/com/baiyue/finance/FinanceWalletContractTest.java b/finance/src/test/java/com/baiyue/finance/FinanceWalletContractTest.java index 2b2ecd69c..d41aa62a4 100644 --- a/finance/src/test/java/com/baiyue/finance/FinanceWalletContractTest.java +++ b/finance/src/test/java/com/baiyue/finance/FinanceWalletContractTest.java @@ -10,18 +10,16 @@ import static org.assertj.core.api.Assertions.assertThat; class FinanceWalletContractTest { @Test - void unifiedMigrationDropsTheLegacyBalanceConstraintBeforeRewritingBalances() throws Exception { + void unifiedInitializationDefinesTheFinalWalletBalanceConstraintWithoutRewritingData() throws Exception { Path migration = Path.of("deploy/sql/头程数据库统一增量-MySQL8.sql"); if (!Files.exists(migration)) migration = Path.of("..").resolve(migration); String sql = Files.readString(migration, StandardCharsets.UTF_8); - int procedure = sql.indexOf("CREATE PROCEDURE migrate_fin_wallet_credit()"); - int dropConstraint = sql.indexOf("ALTER TABLE fin_wallet_account DROP CHECK chk_fin_wallet_account_balance", procedure); - int rewriteBalance = sql.indexOf("SET actual_balance=actual_balance-credit_used", procedure); - - assertThat(procedure).isGreaterThanOrEqualTo(0); - assertThat(sql.substring(procedure, rewriteBalance)) - .contains("CHECK_CLAUSE LIKE '%actual_balance%'", "v_legacy_wallet_constraint OR v_credit_expiry_missing"); - assertThat(dropConstraint).isBetween(procedure, rewriteBalance - 1); + String account = tableBlock(sql, "fin_wallet_account"); + assertThat(account).contains( + "credit_expires_on date DEFAULT NULL", + "CONSTRAINT chk_fin_wallet_account_balance CHECK", + "frozen_balance >= credit_frozen"); + assertThat(sql).doesNotContain("migrate_fin_wallet_credit", "SET actual_balance=actual_balance-credit_used"); } @Test @@ -68,4 +66,10 @@ class FinanceWalletContractTest { .contains("/holds/{businessType}/{businessNo}/release") .contains("Idempotency-Key"); } + + private static String tableBlock(String sql, String table) { + int start = sql.indexOf("CREATE TABLE " + table + " ("); + int end = sql.indexOf(';', start); + return start < 0 || end < 0 ? "" : sql.substring(start, end); + } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/controller/PdaConvergenceOpenApiContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/controller/PdaConvergenceOpenApiContractTest.java index 089336d9d..2c853fa6c 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/controller/PdaConvergenceOpenApiContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/controller/PdaConvergenceOpenApiContractTest.java @@ -65,7 +65,7 @@ class PdaConvergenceOpenApiContractTest { .contains("loadingPlanId:", "containerBookingId:") .doesNotContain("containerNo:"); assertThat(batchItemRequest) - .contains("PICK_PALLET", "palletNo:", "every pickItem", "No carton quantities") + .contains("PICK_PALLET", "palletNo:", "every SKU line", "No carton quantities") .doesNotContain("releaseMode:", "reservationIds:", "locationIds:"); String pickItem = yaml.substring(yaml.indexOf(" PickItem:"), yaml.indexOf(" PendingTask:")); assertThat(pickItem).contains("unitType:", "palletId:", "palletNo:", "indivisible:", "SEALED_PALLET"); diff --git a/firstleg/src/test/java/com/baiyue/firstleg/controller/TruckAppointmentControllerContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/controller/TruckAppointmentControllerContractTest.java index 56fb39b2b..36927b842 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/controller/TruckAppointmentControllerContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/controller/TruckAppointmentControllerContractTest.java @@ -25,9 +25,8 @@ class TruckAppointmentControllerContractTest { Path direct=Path.of("deploy","sql","头程数据库统一增量-MySQL8.sql"); Path unifiedPath=Files.exists(direct)?direct:Path.of("..","deploy","sql","头程数据库统一增量-MySQL8.sql"); String unifiedSql=Files.readString(unifiedPath,StandardCharsets.UTF_8); - for(String sql:new String[]{moduleSql,unifiedSql}) { - assertThat(sql).contains("'bill_of_lading_no'","'actual_arrived_warehouse_at'","'actual_loading_at'","'actual_departed_at'"); - } + assertThat(moduleSql).contains("'bill_of_lading_no'","'actual_arrived_warehouse_at'","'actual_loading_at'","'actual_departed_at'"); + assertThat(unifiedSql).contains("bill_of_lading_no varchar(128)","actual_arrived_warehouse_at datetime(3)","actual_loading_at datetime(3)","actual_departed_at datetime(3)"); } @Test void openApiDocumentsLifecycleIdempotencyAndActualTimes() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("/api/platform/v1/truck-appointments/{id}/{action}:","/api/platform/v1/truck-appointments/{id}/reschedule:","name: Idempotency-Key","required: true","actual event timestamps");} @Test void openApiDocumentsActionableDuplicateCreateConflict() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("A0409","existing appointment number","continue editing","database booking-level unique key");} diff --git a/firstleg/src/test/java/com/baiyue/firstleg/loading/coload/ColoadGroupSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/loading/coload/ColoadGroupSqlContractTest.java index 4404c8d1b..3ed5a8802 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/loading/coload/ColoadGroupSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/loading/coload/ColoadGroupSqlContractTest.java @@ -17,17 +17,22 @@ class ColoadGroupSqlContractTest { String baseline = Files.readString(Path.of("src/main/resources/db/phase3-coload-group-schema.sql")); String unified = Files.readString(Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql")); for (String source : new String[]{baseline, unified}) { + String normalized = source + .replace("CREATE TABLE IF NOT EXISTS ", "CREATE TABLE ") + .replace("CREATE TABLE ", "CREATE TABLE IF NOT EXISTS "); assertThat(source) - .contains("CREATE TABLE IF NOT EXISTS fl_coload_group (") - .contains("CREATE TABLE IF NOT EXISTS fl_coload_group_member (") - .contains("CREATE TABLE IF NOT EXISTS fl_coload_group_history (") - .contains("CREATE TABLE IF NOT EXISTS fl_coload_command (") - .contains("ACTIVE','LOCKED','CONSUMED','CANCELLED") + .contains("ACTIVE", "LOCKED", "CONSUMED", "CANCELLED") + .contains("chk_coload_group_status") .contains("uk_coload_active_inbound") .contains("full_package_count") .contains("uk_coload_command") .contains("locked_plan_id"); - assertThat(tableDefinition(source, "fl_coload_group")) + assertThat(normalized) + .contains("CREATE TABLE IF NOT EXISTS fl_coload_group (") + .contains("CREATE TABLE IF NOT EXISTS fl_coload_group_member (") + .contains("CREATE TABLE IF NOT EXISTS fl_coload_group_history (") + .contains("CREATE TABLE IF NOT EXISTS fl_coload_command ("); + assertThat(tableDefinition(normalized, "fl_coload_group")) .doesNotContain("chk_coload_group_destination") .doesNotContain("destination_type") .doesNotContain("destination_warehouse_id") diff --git a/firstleg/src/test/java/com/baiyue/firstleg/loading/repository/LoadingEvidenceSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/loading/repository/LoadingEvidenceSqlContractTest.java index 9dc06259a..c424ee458 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/loading/repository/LoadingEvidenceSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/loading/repository/LoadingEvidenceSqlContractTest.java @@ -30,11 +30,12 @@ class LoadingEvidenceSqlContractTest { Path direct = Path.of("deploy", "sql", "头程数据库统一增量-MySQL8.sql"); Path unified = Files.exists(direct) ? direct : Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"); String sql = Files.readString(unified, StandardCharsets.UTF_8); - assertThat(sql).contains("CREATE TABLE IF NOT EXISTS fl_loading_cabinet_evidence") + assertThat(sql).contains("CREATE TABLE fl_loading_cabinet_evidence") .contains("uk_cabinet_evidence_active") .contains("uk_cabinet_evidence_operation") - .contains("normalize_loading_cabinet_evidence_collation") - .doesNotContain("CREATE TABLE IF NOT EXISTS fl_loading_evidence") + .contains("FOREIGN KEY (container_booking_id) REFERENCES fl_container_booking (id)") + .doesNotContain("CREATE TABLE fl_loading_evidence") + .doesNotContain("normalize_loading_cabinet_evidence_collation") .doesNotContain("phase3_loading_evidence_add_column"); } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/loading/repository/PdaConvergenceSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/loading/repository/PdaConvergenceSqlContractTest.java index 257649cee..54651c253 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/loading/repository/PdaConvergenceSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/loading/repository/PdaConvergenceSqlContractTest.java @@ -38,8 +38,13 @@ class PdaConvergenceSqlContractTest { String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"), StandardCharsets.UTF_8); - assertThat(createBlock(unified, "fl_loading_pick_item_assignment")) - .isEqualTo(createBlock(sql, "fl_loading_pick_item_assignment")); + assertThat(unified) + .contains("CREATE TABLE fl_loading_pick_item_assignment") + .contains("assignment_type varchar(16) NOT NULL") + .contains("PRIMARY KEY (pick_item_id)") + .contains("KEY idx_loading_pick_item_assignee (assignee_id,updated_at)") + .contains("CONSTRAINT fk_loading_pick_item_assignment_item") + .contains("platform:firstleg:pda:assign"); } @Test diff --git a/firstleg/src/test/java/com/baiyue/firstleg/portschedule/repository/CarrierScheduleQuerySqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/portschedule/repository/CarrierScheduleQuerySqlContractTest.java index 8d9952d3a..c5b2d01d9 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/portschedule/repository/CarrierScheduleQuerySqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/portschedule/repository/CarrierScheduleQuerySqlContractTest.java @@ -108,49 +108,28 @@ class CarrierScheduleQuerySqlContractTest { @Test void unifiedDeploymentEntryContainsTheSameContract() throws Exception { String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"), - StandardCharsets.UTF_8); + StandardCharsets.UTF_8).toLowerCase(); assertThat(unified) - .contains("fl_carrier_schedule_query") + .contains("create table fl_carrier_schedule_query") .contains("uk_carrier_schedule_query_scope") - .contains("id IN (5102184,5102186,5102188)") - .contains("assert_carrier_schedule_query_schema") - .contains("CREATE PROCEDURE migrate_carrier_schedule_retry_columns()") - .contains("ADD COLUMN trigger_source VARCHAR(32) NOT NULL DEFAULT 'MANUAL'") - .contains("ADD COLUMN latest_standard_node VARCHAR(32) NULL") - .contains("ADD COLUMN latest_node_observed_at DATETIME(3) NULL") - .contains("ADD COLUMN stopped_at DATETIME(3) NULL") - .contains("ADD COLUMN stop_reason VARCHAR(500) NULL") - .contains("ADD COLUMN consecutive_failures INT UNSIGNED NOT NULL DEFAULT 0") - .contains("ADD COLUMN window_started_at DATETIME(3) NULL") - .contains("ADD COLUMN attempt_count INT UNSIGNED NOT NULL DEFAULT 0") - .contains("ADD COLUMN next_allowed_at DATETIME(3) NULL") - .contains("ADD KEY idx_carrier_schedule_query_due (status,next_allowed_at,id)") - .contains("DROP CHECK chk_carrier_schedule_query_access") - .contains("DROP CHECK chk_carrier_schedule_query_carrier") - .contains("DROP CHECK chk_carrier_schedule_query_status") - .contains("DROP INDEX uk_carrier_schedule_query_scope") - .contains("ADD UNIQUE KEY uk_carrier_schedule_query_scope") + .contains("trigger_source varchar(32) not null default 'manual'") + .contains("latest_standard_node varchar(32) default null") + .contains("latest_node_observed_at datetime(3) default null") + .contains("stopped_at datetime(3) default null") + .contains("stop_reason varchar(500) default null") + .contains("consecutive_failures int unsigned not null default '0'") + .contains("window_started_at datetime(3) not null") + .contains("attempt_count int unsigned not null default '0'") + .contains("next_allowed_at datetime(3) default null") + .contains("key idx_carrier_schedule_query_due (status,next_allowed_at,id)") + .contains("unique key uk_carrier_schedule_query_scope") .contains("(customer_id,ocean_booking_id,container_scope_id)") - .contains("'PUBLIC_FORM','PUBLIC_WEB_PROXY','PUBLIC_HTML','HCAPTCHA','OFFICIAL_API'") - .contains("'MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER'") - .contains("'OFFICIAL_API','MANUAL'") - .contains("status IN ('READY','MANUAL_REQUIRED','STOPPED')") - .contains("ARRIVED_DESTINATION_PORT") - .contains("EMPTY_RETURNED") - .contains("SET @carrier_schedule_now = CONVERT_TZ(UTC_TIMESTAMP(3),'+00:00','+08:00')") - .contains("SET @carrier_schedule_next = CASE") - .contains("CREATE PROCEDURE ensure_carrier_schedule_retry_check()") - .contains("CREATE PROCEDURE migrate_carrier_schedule_history_action_check()") - .contains("DROP CHECK chk_carrier_schedule_history_action") - .contains("'MANUAL_NODE','NODE_OBSERVED'") - .contains("'EXCEPTION_OBSERVED','PROVIDER_QUERY','STOP'") - .contains("ADD CONSTRAINT chk_carrier_schedule_query_attempt CHECK (attempt_count BETWEEN 0 AND 3)") - .contains("统一迁移失败:船司查询固定槽位字段、索引或尝试次数约束不完整") - .doesNotContain("DROP CHECK chk_carrier_schedule_query_attempt") - .doesNotContain("\n+--") - .contains("'PUBLIC_FORM','PUBLIC_WEB_PROXY','PUBLIC_HTML','HCAPTCHA','OFFICIAL_API'") - .contains("船司查询任务角色授权未完整创建") - .doesNotContain("'platform:firstleg:port-schedule-query:attempt','执行船司查询'"); + .contains("chk_carrier_schedule_query_attempt check ((attempt_count between 0 and 3))") + .contains("arrived_destination_port") + .contains("empty_returned") + .contains("platform:firstleg:port-schedule-query:manual") + .doesNotContain("migrate_carrier_schedule_retry_columns") + .doesNotContain("drop check chk_carrier_schedule_query_attempt"); } @Test @@ -158,13 +137,13 @@ class CarrierScheduleQuerySqlContractTest { String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"), StandardCharsets.UTF_8); assertThat(unified) - .contains("CALL firstleg_finance_add_column('fl_inbound_outbox', 'event_key'") - .contains("CALL firstleg_finance_add_column('fl_inbound_outbox', 'event_version'") - .contains("CALL firstleg_finance_add_column('fl_inbound_outbox', 'aggregate_sequence'") - .contains("CALL firstleg_finance_add_column('fl_inbound_outbox', 'payload_hash'") + .contains("event_key varchar(160) DEFAULT NULL") + .contains("event_version int unsigned NOT NULL DEFAULT '1'") + .contains("aggregate_sequence int unsigned DEFAULT NULL") + .contains("payload_hash varchar(71) DEFAULT NULL") .contains("uk_inbound_outbox_event_key") - .contains("MODIFY COLUMN payload_hash VARCHAR(71)") - .contains("MODIFY COLUMN payload_json LONGTEXT NOT NULL"); + .contains("payload_json longtext NOT NULL") + .doesNotContain("firstleg_finance_add_column"); } private static String moduleSql() throws Exception { diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/ContainerBookingMapperContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/ContainerBookingMapperContractTest.java index 18f379ad1..da3c5b79a 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/ContainerBookingMapperContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/ContainerBookingMapperContractTest.java @@ -108,16 +108,16 @@ class ContainerBookingMapperContractTest { @Test @DisplayName("初始化与统一增量锁定真实柜号权威、唯一和ISO结构约束") void databaseContractsFreezeAuthoritativeContainerNumber() throws Exception { - for (String source : new String[] { - Files.readString(Path.of("src/main/resources/db/phase3-smart-loading-schema.sql")), - Files.readString(Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql")) - }) { - assertThat(source) - .contains("国内唯一权威真实集装箱号") - .contains("提柜后可原子补录") - .contains("UNIQUE KEY uk_container_booking_container_no (container_no)") - .contains("CONSTRAINT chk_container_booking_iso6346") - .contains("container_no REGEXP '^[A-Z]{3}[UJZ][0-9]{7}$'"); + String module = Files.readString(Path.of("src/main/resources/db/phase3-smart-loading-schema.sql")); + String unified = Files.readString(Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql")); + assertThat(module.toLowerCase()) + .contains("国内唯一权威真实集装箱号") + .contains("提柜后可原子补录"); + for (String source : new String[] {module, unified}) { + assertThat(source.toLowerCase()) + .contains("unique key uk_container_booking_container_no (container_no)") + .contains("constraint chk_container_booking_iso6346") + .contains("^[a-z]{3}[ujz][0-9]{7}$"); } } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/CustomerDimensionConfigSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/CustomerDimensionConfigSqlContractTest.java index aec810024..484c64e39 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/CustomerDimensionConfigSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/CustomerDimensionConfigSqlContractTest.java @@ -28,21 +28,23 @@ class CustomerDimensionConfigSqlContractTest { ); for (String sql : sqlEntrypoints) { - assertThat(sql) + String normalized = sql.toLowerCase().replace(", ", ","); + assertThat(normalized) .contains("fl_customer_dimension_config") - .contains("box_type VARCHAR(24) NOT NULL") - .contains("inner_length_cm DECIMAL(9,3) NOT NULL") - .contains("inner_width_cm DECIMAL(9,3) NOT NULL") - .contains("inner_height_cm DECIMAL(9,3) NOT NULL") - .contains("edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000") - .contains("default_calculation_mode VARCHAR(16) NOT NULL DEFAULT 'ACTUAL'") - .contains("initialization_key VARCHAR(128) NOT NULL") - .contains("created_by BIGINT UNSIGNED NOT NULL") - .contains("updated_by BIGINT UNSIGNED NOT NULL") - .contains("version INT NOT NULL DEFAULT 0") - .contains("UNIQUE KEY uk_customer_dimension_config_tenant_box (tenant_id, box_type)") - .contains("CHECK (box_type IN ('SMALL', 'MEDIUM', 'LARGE', 'EXTRA_LARGE'))") - .doesNotContain("UNIQUE KEY uk_customer_dimension_config_tenant (tenant_id)"); + .contains("box_type varchar(24) not null") + .contains("inner_length_cm decimal(9,3) not null") + .contains("inner_width_cm decimal(9,3) not null") + .contains("inner_height_cm decimal(9,3) not null") + .contains("edge_addition_cm decimal(6,3) not null default") + .contains("default_calculation_mode varchar(16)") + .contains("not null default 'actual'") + .contains("initialization_key varchar(128) not null") + .contains("created_by bigint unsigned not null") + .contains("updated_by bigint unsigned not null") + .contains("version int not null default") + .contains("unique key uk_customer_dimension_config_tenant_box (tenant_id,box_type)") + .contains("'small'", "'medium'", "'large'", "'extra_large'") + .doesNotContain("unique key uk_customer_dimension_config_tenant (tenant_id)"); } } @@ -104,21 +106,19 @@ class CustomerDimensionConfigSqlContractTest { } @Test - void unifiedMigrationKeepsPermissionGuardsAndAssertsFinalFourRowSchema() throws Exception { + void unifiedInitializationKeepsPermissionsAndFinalFourRowSchema() throws Exception { String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"), StandardCharsets.UTF_8); assertThat(sql) - .contains("CREATE PROCEDURE unified_guard_dimension_config_permissions()") - .contains("(5102111, 0, 'PLATFORM', 'BUTTON', 5100010") - .contains("r.role_code = 'PLATFORM_ADMIN'") - .contains("r.role_code = 'ORDER_OPERATOR'") - .contains("index_name = 'uk_customer_dimension_config_tenant_box'") - .contains("客户四箱规尺寸配置字段不完整") - .contains("客户默认计费规则初始化幂等字段未创建") - .contains("index_name = 'idx_charge_rule_initialization'") - .contains("客户默认计费规则初始化索引未完整创建") - .contains("客户尺寸计算配置权限码、固定ID或父节点不正确"); + .contains("CREATE TABLE fl_customer_dimension_config") + .contains("UNIQUE KEY uk_customer_dimension_config_tenant_box") + .contains("5102111,0,'PLATFORM','BUTTON',5100010") + .contains("firstleg:dimension-config:view") + .contains("firstleg:dimension-config:edit") + .contains("VALUES (4000001,5102111,0,CURRENT_TIMESTAMP(3))") + .contains("VALUES (4000003,5102111,0,CURRENT_TIMESTAMP(3))") + .doesNotContain("unified_guard_dimension_config_permissions"); } @Test @@ -129,43 +129,21 @@ class CustomerDimensionConfigSqlContractTest { StandardCharsets.UTF_8); assertThat(schema).contains("calculation_rule_version VARCHAR(64) NOT NULL"); - assertThat(unified) - .contains("calculation_rule_version VARCHAR(64) NOT NULL") - .contains("MODIFY COLUMN calculation_rule_version VARCHAR(64) NOT NULL"); + assertThat(unified).contains("calculation_rule_version varchar(64) NOT NULL") + .doesNotContain("MODIFY COLUMN calculation_rule_version"); } @Test - void unifiedMigrationUpgradesOnlyRecognizedLegacyTableAndBackfillsIdempotently() throws Exception { + void unifiedInitializationCreatesOnlyTheFinalDimensionTableWithoutBackfill() throws Exception { String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"), StandardCharsets.UTF_8); - int upgrade = sql.indexOf("CREATE PROCEDURE unified_upgrade_legacy_customer_dimension_config()"); - int create = sql.indexOf("CREATE TABLE IF NOT EXISTS baiyue_firstleg.fl_customer_dimension_config"); - int backfill = sql.indexOf("INSERT INTO baiyue_firstleg.fl_customer_dimension_config (", create); - assertThat(upgrade).isGreaterThanOrEqualTo(0); - assertThat(create).isGreaterThan(upgrade); - assertThat(backfill).isGreaterThan(create); assertThat(sql) - .contains("AND column_name = 'box_type') = 0") - .contains("AND column_name IN ('id', 'tenant_id', 'edge_addition_cm', 'remark', 'initialization_key'") - .contains("AND table_name = 'fl_customer_dimension_config') <> 11") - .contains("AND index_name = 'uk_customer_dimension_config_tenant'") - .contains("DROP TABLE baiyue_firstleg.fl_customer_dimension_config") - .contains("客户尺寸配置表不是可识别的旧单行结构") - .contains("SELECT id AS tenant_id, created_by") - .contains("FROM baiyue_iam.iam_tenant") - .contains("WHERE tenant_type = 'MERCHANT' AND deleted = 0 AND id > 0") - .contains("CONCAT('unified-four-box-default-v1:', tenant.tenant_id)") - .contains("ON DUPLICATE KEY UPDATE id = id") - .contains("现有企业客户未完整持有固定四箱规尺寸配置"); - - String repeatSafeClause = sql.substring(backfill, - sql.indexOf("-- 来源:firstleg/src/main/resources/db/phase3-inbound-parent-child-schema.sql", backfill)); - assertThat(repeatSafeClause) - .doesNotContain("edge_addition_cm = VALUES") - .doesNotContain("inner_length_cm = VALUES") - .doesNotContain("remark = VALUES") - .doesNotContain("version = VALUES"); + .contains("CREATE TABLE fl_customer_dimension_config") + .contains("COMMENT='客户固定四箱规尺寸配置'") + .doesNotContain("unified_upgrade_legacy_customer_dimension_config") + .doesNotContain("DROP TABLE baiyue_firstleg.fl_customer_dimension_config") + .doesNotContain("ON DUPLICATE KEY UPDATE"); } private static String resource(String path) throws Exception { diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/DropshipPalletizationSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/DropshipPalletizationSqlContractTest.java index 6b75c8ca3..d1d694558 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/DropshipPalletizationSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/DropshipPalletizationSqlContractTest.java @@ -49,34 +49,31 @@ class DropshipPalletizationSqlContractTest { String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql")); for (String sql : new String[]{inbound, unified}) { - assertThat(sql) - .contains("counted_cartons INT UNSIGNED NOT NULL DEFAULT 0") - .contains("discrepancy_cartons INT UNSIGNED NOT NULL DEFAULT 0") - .contains("pallet_status VARCHAR(16) NOT NULL COMMENT '托盘状态:OPEN组托中、SEALED已封托'") - .contains("expected_sku_quantity INT UNSIGNED NOT NULL") - .contains("actual_sku_quantity INT UNSIGNED NOT NULL") - .contains("count_operation_id VARCHAR(128) NOT NULL") - .contains("bind_operation_id VARCHAR(128) NULL"); + assertThat(sql.toLowerCase()) + .contains("counted_cartons int unsigned not null default") + .contains("discrepancy_cartons int unsigned not null default") + .contains("pallet_status varchar(16) not null comment '托盘状态:open组托中、sealed已封托'") + .contains("expected_sku_quantity int unsigned not null") + .contains("actual_sku_quantity int unsigned not null") + .contains("count_operation_id varchar(128) not null") + .contains("bind_operation_id varchar(128)"); } for (String sql : new String[]{loading, unified}) { - assertThat(sql) - .contains("unit_type VARCHAR(24) NOT NULL DEFAULT 'PACKAGE'") - .contains("pallet_id BIGINT UNSIGNED NULL") - .contains("indivisible TINYINT(1) NOT NULL DEFAULT 0") - .contains("active_assignment_key VARCHAR(96) GENERATED ALWAYS AS") - .contains("UNIQUE KEY uk_loading_item_plan_assignment (loading_plan_id, active_assignment_key)") - .doesNotContain("CREATE TABLE IF NOT EXISTS fl_loading_work_pallet (") - .doesNotContain("CREATE TABLE IF NOT EXISTS fl_loading_work_pallet_carton ("); + String normalized = sql.toLowerCase().replace(", ", ","); + assertThat(normalized) + .contains("unit_type varchar(24) not null default 'package'") + .contains("pallet_id bigint unsigned") + .contains("indivisible tinyint(1) not null default") + .contains("active_assignment_key varchar(96) generated always as") + .contains("unique key uk_loading_item_plan_assignment (loading_plan_id,active_assignment_key)") + .doesNotContain("create table fl_loading_work_pallet (") + .doesNotContain("create table fl_loading_work_pallet_carton ("); } assertThat(unified) - .contains("CREATE PROCEDURE converge_loading_assignment_schema()") - .contains("CREATE PROCEDURE converge_dropship_palletization_schema()") - .contains("ALTER TABLE fl_dropship_pallet DROP CHECK chk_dropship_pallet_status") - .contains("ALTER TABLE fl_dropship_pallet DROP CHECK chk_dropship_pallet_status;\n END IF;\n UPDATE fl_dropship_pallet") - .contains("CHECK (pallet_status IN ('OPEN','SEALED'))") - .contains("CHANGE COLUMN operation_id count_operation_id") - .contains("DROP INDEX uk_loading_item_plan_detail") - .contains("ADD UNIQUE KEY uk_loading_item_plan_assignment (loading_plan_id,active_assignment_key)"); + .contains("CONSTRAINT chk_dropship_pallet_status CHECK") + .contains("UNIQUE KEY uk_loading_item_plan_assignment") + .doesNotContain("converge_loading_assignment_schema") + .doesNotContain("converge_dropship_palletization_schema"); } @Test diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/FeeItemMigrationSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/FeeItemMigrationSqlContractTest.java index 144a900f1..6f5e0491f 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/FeeItemMigrationSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/FeeItemMigrationSqlContractTest.java @@ -11,9 +11,13 @@ import static org.assertj.core.api.Assertions.assertThat; class FeeItemMigrationSqlContractTest { @Test void temporarySeedTableIsNotReopenedWithinOneMysqlStatement() throws Exception { - assertMysqlCompatible(resolve("deploy/sql/头程数据库统一增量-MySQL8.sql")); assertMysqlCompatible(resolve( "firstleg/src/main/resources/db/phase2-published-freight-detail-migration.sql")); + String unified = Files.readString(resolve("deploy/sql/头程数据库统一增量-MySQL8.sql"), StandardCharsets.UTF_8); + assertThat(unified) + .contains("193000000001000111,0,'头程运费','INTERNATIONAL','FBA_HEAD_FREIGHT'") + .contains("193000000001000112,0,'尾程派送费','INTERNATIONAL','PRIVATE_LAST_MILE_DELIVERY'") + .doesNotContain("tmp_fee_item_seed"); } private static void assertMysqlCompatible(Path path) throws Exception { @@ -35,9 +39,12 @@ class FeeItemMigrationSqlContractTest { @Test void inboundFeeSourceIndexUsesTheFinalVersionedDefinition() throws Exception { - assertFinalSourceIndex(resolve("deploy/sql/头程数据库统一增量-MySQL8.sql")); assertFinalSourceIndex(resolve( "firstleg/src/main/resources/db/phase2-published-freight-detail-migration.sql")); + String unified = Files.readString(resolve("deploy/sql/头程数据库统一增量-MySQL8.sql"), StandardCharsets.UTF_8); + assertThat(unified) + .contains("UNIQUE KEY uk_inbound_fee_source (tenant_id,source_type,source_fee_id,source_fee_version)") + .doesNotContain("DROP INDEX uk_inbound_fee_source"); } private static void assertFinalSourceIndex(Path path) throws Exception { diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/FulfillmentTypeSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/FulfillmentTypeSqlContractTest.java index ca27c12fb..728b68092 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/FulfillmentTypeSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/FulfillmentTypeSqlContractTest.java @@ -17,11 +17,11 @@ class FulfillmentTypeSqlContractTest { assertFulfillmentMigration(order, "baiyue_order.ord_order"); assertFulfillmentMigration(inbound, "fl_inbound_order"); - assertFulfillmentMigration(unified, "baiyue_order.ord_order"); - assertFulfillmentMigration(unified, "fl_inbound_order"); - assertThat(unified) - .contains("unified_assert_column('baiyue_order', 'ord_order', 'fulfillment_type')") - .contains("unified_assert_column('baiyue_firstleg', 'fl_inbound_order', 'fulfillment_type')"); + assertThat(unified.toLowerCase()) + .contains("create table ord_order") + .contains("create table fl_inbound_order") + .contains("fulfillment_type varchar(32) not null default 'standard'") + .doesNotContain("unified_assert_column"); } private static void assertFulfillmentMigration(String sql, String table) { diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundCartonLabelSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundCartonLabelSqlContractTest.java index 9fe3da95f..64f4f117a 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundCartonLabelSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundCartonLabelSqlContractTest.java @@ -29,20 +29,17 @@ class InboundCartonLabelSqlContractTest { assertThat(versioningMigration).contains("phase2_carton_label_add_column('label_version'") .contains("phase2_carton_label_add_column('batch_status'") .contains("idx_carton_label_active (tenant_id, inbound_id, batch_status, label_version)"); - assertThat(unified).contains("CREATE TABLE IF NOT EXISTS fl_inbound_carton_label") - .contains("units_per_carton INT UNSIGNED NULL") - .contains("unified_carton_label_add_column('label_version'") - .contains("unified_carton_label_add_column('batch_status'") - .contains("unified_carton_label_add_column('cargo_fingerprint'") - .contains("unified_carton_label_add_column('voided_at'") - .contains("ALTER TABLE fl_inbound_carton_label DROP INDEX uk_carton_label_index") - .contains("ADD UNIQUE KEY uk_carton_label_index (tenant_id,inbound_id,label_version,carton_index)") - .contains("ADD KEY idx_carton_label_active (tenant_id,inbound_id,batch_status,label_version)") + assertThat(unified).contains("CREATE TABLE fl_inbound_carton_label") + .contains("units_per_carton int unsigned DEFAULT NULL") + .contains("label_version int unsigned NOT NULL DEFAULT '1'") + .contains("batch_status varchar(16) NOT NULL DEFAULT 'ACTIVE'") + .contains("cargo_fingerprint char(64) DEFAULT NULL") + .contains("voided_at datetime(3) DEFAULT NULL") + .contains("UNIQUE KEY uk_carton_label_index (tenant_id,inbound_id,label_version,carton_index)") + .contains("KEY idx_carton_label_active (tenant_id,inbound_id,batch_status,label_version)") .contains("uk_carton_label_box_code") .contains("uk_carton_label_index") - .contains("稳定箱号映射表未创建") - .contains("稳定箱码映射兼容字段未完整创建") - .contains("箱唛有效批次索引未完整创建"); + .doesNotContain("unified_carton_label_add_column"); } private String resource(String path) throws Exception { diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundStatusUnificationSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundStatusUnificationSqlContractTest.java index eea8d70ea..6f15681ed 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundStatusUnificationSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundStatusUnificationSqlContractTest.java @@ -23,7 +23,7 @@ class InboundStatusUnificationSqlContractTest { .doesNotContain("UPDATE fl_inbound_order") .doesNotContain("UPDATE fl_inbound_status_history"); assertThat(unified) - .contains("入库完成后的唯一状态为 WAIT_SPLIT") + .contains("WAIT_SPLIT待分柜") .doesNotContain("历史 WAIT_SPLIT") .doesNotContain("SET process_status = 'WAIT_SPLIT'") .doesNotContain("SET from_status = 'WAIT_SPLIT'") diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundWorkflowSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundWorkflowSqlContractTest.java index 36909bfcf..356b99148 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundWorkflowSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundWorkflowSqlContractTest.java @@ -77,9 +77,12 @@ class InboundWorkflowSqlContractTest { .contains("uk_measure_box_code") .contains("information_schema.STATISTICS"); assertThat(unified) - .contains("CALL phase2_measure_add_column('fl_inbound_measurement_item', 'box_code'") + .contains("CREATE TABLE fl_inbound_measurement_item") + .contains("box_code varchar(128) DEFAULT NULL") + .contains("machine_no varchar(64) DEFAULT NULL") + .contains("device_measured_at datetime(3) DEFAULT NULL") .contains("uk_measure_box_code") - .contains("单箱当前测量字段未完整创建"); + .doesNotContain("phase2_measure_add_column"); } @Test @@ -97,10 +100,13 @@ class InboundWorkflowSqlContractTest { .doesNotContain("final_weight_kg") .doesNotContain("uk_pallet_allocation_pallet"); assertThat(unified) - .contains("CALL phase2_measure_add_column('fl_inbound_weight_result', 'pallet_id'") - .contains("ALTER TABLE fl_inbound_weight_result DROP COLUMN final_weight_kg") - .contains("ALTER TABLE fl_inbound_pallet_allocation DROP INDEX uk_pallet_allocation_pallet") - .contains("称重托盘快照或毛重净重字段未正确收敛") - .contains("同票托盘唯一约束仍阻止托盘主数据复用"); + .contains("CREATE TABLE fl_inbound_weight_result") + .contains("pallet_id bigint unsigned NOT NULL") + .contains("pallet_tare_weight_kg decimal(18,3) NOT NULL") + .contains("gross_weight_kg decimal(18,3) DEFAULT NULL") + .contains("net_weight_kg decimal(18,3) DEFAULT NULL") + .contains("KEY idx_weight_inbound_pallet") + .doesNotContain("final_weight_kg") + .doesNotContain("uk_pallet_allocation_pallet"); } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/MojiaMeasurementReceiptSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/MojiaMeasurementReceiptSqlContractTest.java index 6cf89ad8f..aebaa3c53 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/MojiaMeasurementReceiptSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/MojiaMeasurementReceiptSqlContractTest.java @@ -19,18 +19,20 @@ class MojiaMeasurementReceiptSqlContractTest { StandardCharsets.UTF_8); for (String sql : new String[]{schema, unified}) { - assertThat(sql) - .contains("CREATE TABLE IF NOT EXISTS fl_mojia_measurement_receipt") - .contains("box_code VARCHAR(128) NOT NULL") - .contains("scanned_shipment_id VARCHAR(64) NULL") - .contains("image_file_id BIGINT UNSIGNED NOT NULL") - .contains("status VARCHAR(24) NOT NULL DEFAULT 'UNMATCHED'") + String normalized = sql.toLowerCase() + .replace("create table if not exists ", "create table ") + .replace("create table ", "create table if not exists "); + assertThat(normalized) + .contains("create table if not exists fl_mojia_measurement_receipt") + .contains("box_code varchar(128) not null") + .contains("scanned_shipment_id varchar(64)") + .contains("image_file_id bigint unsigned not null") + .contains("status varchar(24) not null default 'unmatched'") .contains("idx_mojia_receipt_status_time") .doesNotContain("picture_base64"); } assertThat(unified) - .contains("墨家未匹配测量收件箱字段未完整创建") - .contains("箱级测量实扫Shipment ID字段未创建") - .contains("测量计算规则版本字段长度不是64"); + .contains("COMMENT='墨家模拟或未匹配入库单的原始测量收件箱'") + .contains("calculation_rule_version varchar(64) NOT NULL"); } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/PdaSplitSettlementSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/PdaSplitSettlementSqlContractTest.java index f792fd39e..7d81e6af5 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/PdaSplitSettlementSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/PdaSplitSettlementSqlContractTest.java @@ -31,10 +31,11 @@ class PdaSplitSettlementSqlContractTest { .contains("phase3_split_add_column('fl_preload_plan', 'confirmed_by_name'") .contains("phase3_split_add_column('fl_preload_plan', 'confirmed_at'"); assertThat(unifiedSql) - .contains("CREATE PROCEDURE migrate_preload_confirmation_columns()") - .contains("ADD COLUMN confirmed_by BIGINT UNSIGNED NULL") - .contains("ADD COLUMN confirmed_by_name VARCHAR(64) NULL") - .contains("ADD COLUMN confirmed_at DATETIME(3) NULL"); + .contains("CREATE TABLE fl_preload_plan") + .contains("confirmed_by bigint unsigned DEFAULT NULL") + .contains("confirmed_by_name varchar(64) DEFAULT NULL") + .contains("confirmed_at datetime(3) DEFAULT NULL") + .doesNotContain("migrate_preload_confirmation_columns"); } @Test @@ -188,9 +189,8 @@ class PdaSplitSettlementSqlContractTest { .contains("UNIQUE KEY uk_pallet_allocation_weight (tenant_id, inbound_id, weight_result_id)") .doesNotContain("UNIQUE KEY uk_pallet_allocation_slot (tenant_id, block_id, slot_no)"); assertThat(unifiedSql) - .contains("'tenant_id,inbound_id,block_id,slot_no'") - .contains("ADD UNIQUE KEY uk_pallet_allocation_slot (tenant_id,inbound_id,block_id,slot_no)") - .contains("'tenant_id,inbound_id,weight_result_id'") - .contains("ADD UNIQUE KEY uk_pallet_allocation_weight (tenant_id,inbound_id,weight_result_id)"); + .contains("UNIQUE KEY uk_pallet_allocation_slot (tenant_id,inbound_id,block_id,slot_no)") + .contains("UNIQUE KEY uk_pallet_allocation_weight (tenant_id,inbound_id,weight_result_id)") + .doesNotContain("ADD UNIQUE KEY uk_pallet_allocation_slot"); } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3BookingTruckPermissionSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3BookingTruckPermissionSqlContractTest.java index 2e1c0f81a..4d1aa802f 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3BookingTruckPermissionSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3BookingTruckPermissionSqlContractTest.java @@ -58,36 +58,26 @@ class Phase3BookingTruckPermissionSqlContractTest { } @Test - void unifiedSqlLocksCanonicalIdsScopeParentsAndRoleGrants() throws Exception { + void unifiedSqlCreatesCanonicalIdsScopeParentsAndRoleGrants() throws Exception { String sql = unifiedSql(); - assertThat(sql) - .contains("UNIQUE KEY uk_permission_scope_code (tenant_id, app_code, permission_code)") - .contains("CREATE PROCEDURE assert_ocean_booking_permission_ids_available()") - .contains("CREATE PROCEDURE assert_truck_appointment_permission_ids_available()") - .contains("CREATE PROCEDURE assert_phase3_service_center_schema()") - .contains("CALL assert_phase3_service_center_schema();") - .contains("DROP PROCEDURE IF EXISTS assert_phase3_service_center_schema;") - .contains("三期订舱权限迁移失败:5102140-5102149 ID 或权限码被其他语义占用") - .contains("三期拖车权限迁移失败:5102150-5102154 ID 或权限码被其他语义占用") + String normalized = sql.replace(", ", ","); + assertThat(normalized) + .contains("UNIQUE KEY uk_permission_scope_code (tenant_id,app_code,permission_code)") .contains("(5102140,0,'PLATFORM','MENU',5102000,'platform:firstleg:ocean-booking:list'") .contains("(5102149,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:allocate'") .contains("(5102150,0,'PLATFORM','MENU',5102000,'platform:firstleg:truck-appointment:list'") .contains("(5102154,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:operate'") - .contains("permission_code LIKE 'platform:firstleg:ocean-booking:%') <> 6") - .contains("p.permission_code LIKE 'platform:firstleg:ocean-booking:%') <> 12") - .contains("role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR')") - .doesNotContain("assert_phase3_booking_truck_permission_ids_available") + .contains("VALUES (4000001,5102140,0,CURRENT_TIMESTAMP(3))") + .contains("VALUES (4000003,5102140,0,CURRENT_TIMESTAMP(3))") + .contains("VALUES (4000001,5102150,0,CURRENT_TIMESTAMP(3))") + .contains("VALUES (4000003,5102150,0,CURRENT_TIMESTAMP(3))") + .doesNotContain("assert_ocean_booking_permission_ids_available") + .doesNotContain("assert_truck_appointment_permission_ids_available") .doesNotContain("/trucking"); - assertThat(count(sql, "(5102140,0,'PLATFORM','MENU',5102000,'platform:firstleg:ocean-booking:list'")) + assertThat(count(normalized, "(5102140,0,'PLATFORM','MENU',5102000,'platform:firstleg:ocean-booking:list'")) .isEqualTo(1); - assertThat(count(sql, "(5102150,0,'PLATFORM','MENU',5102000,'platform:firstleg:truck-appointment:list'")) - .isEqualTo(1); - assertThat(count(sql, "CREATE PROCEDURE assert_ocean_booking_permission_ids_available()")) - .isEqualTo(1); - assertThat(count(sql, "CREATE PROCEDURE assert_truck_appointment_permission_ids_available()")) - .isEqualTo(1); - assertThat(count(sql, "CREATE PROCEDURE assert_phase3_service_center_schema()")) + assertThat(count(normalized, "(5102150,0,'PLATFORM','MENU',5102000,'platform:firstleg:truck-appointment:list'")) .isEqualTo(1); } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3ChannelContainerMigrationContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3ChannelContainerMigrationContractTest.java index eb74c0acc..91b0d40af 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3ChannelContainerMigrationContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3ChannelContainerMigrationContractTest.java @@ -17,10 +17,20 @@ class Phase3ChannelContainerMigrationContractTest { } @Test - void unifiedMigrationRestoresGuardsBeforeSurfacingFailures() throws Exception { + void unifiedInitializationCreatesFinalGuardsAndColumnsWithoutMigrationWindow() throws Exception { String sql = unifiedMigration(); - assertSafeWindow(sql, true); - assertNoDuplicateInboundColumns(sql); + assertThat(sql) + .contains("CREATE TABLE fl_inbound_order") + .contains("warehouse_id bigint unsigned NOT NULL") + .contains("warehouse_name varchar(128) NOT NULL") + .contains("channel_code varchar(64) NOT NULL") + .contains("channel_name varchar(128) NOT NULL") + .contains("CREATE TRIGGER trg_container_booking_before_update") + .contains("装柜作业开始后仅允许修改集装箱号与放舱运输数据") + .contains("CREATE TRIGGER trg_loading_pick_task_before_update") + .contains("已完成PDA拣货任务不可修改") + .doesNotContain("migrate_phase3_channel_containers") + .doesNotContain("assert_phase3_channel_container_migration"); } private static void assertNoDuplicateInboundColumns(String sql) { diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3FinalUnifiedSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3FinalUnifiedSqlContractTest.java index 2d812a167..c2ccd324b 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3FinalUnifiedSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/Phase3FinalUnifiedSqlContractTest.java @@ -5,370 +5,89 @@ import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static org.assertj.core.api.Assertions.assertThat; class Phase3FinalUnifiedSqlContractTest { private static final Path UNIFIED = Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"); - private static final Pattern CREATE_TABLE = Pattern.compile( - "(?i)CREATE\\s+TABLE\\s+IF\\s+NOT\\s+EXISTS\\s+(?:`[^`]+`\\.)?`?([a-zA-Z0-9_]+)`?\\s*\\("); - private static final Pattern COLUMN = Pattern.compile( - "(?i)^\\s*`?([a-zA-Z0-9_]+)`?\\s+(bigint|binary|bit|blob|boolean|char|date|datetime|decimal|double|enum|float|int|integer|json|longblob|longtext|mediumblob|mediumint|mediumtext|numeric|real|set|smallint|text|time|timestamp|tinyblob|tinyint|tinytext|varbinary|varchar|year)\\b"); - private static final Pattern CHINESE_COMMENT = Pattern.compile( - "(?i)\\bCOMMENT\\s*=?\\s*'(?:(?:''|\\\\'|[^'])*[\\u3400-\\u9fff](?:''|\\\\'|[^'])*)'"); @Test - void unifiedEntryOwnsPhase3OutboundCustomsPortAndEvidenceHardening() throws Exception { - String sql = Files.readString(UNIFIED, StandardCharsets.UTF_8); + void unifiedEntryCreatesTheFinalPhase3SchemaDirectly() throws Exception { + String sql = sql(); + assertThat(sql) + .contains("CREATE TABLE fl_inbound_split_batch") + .contains("CREATE TABLE fl_ocean_booking") + .contains("CREATE TABLE fl_truck_appointment") + .contains("CREATE TABLE fl_outbound_dispatch") + .contains("CREATE TABLE fl_customs_container") + .contains("CREATE TABLE fl_port_schedule") + .contains("CREATE TABLE fl_carrier_schedule_query") + .contains("CREATE TABLE fl_terminal_drayage") + .contains("CREATE TABLE fl_loading_pick_assignment") + .contains("CREATE TABLE fl_loading_pick_item_assignment") + .contains("CREATE TABLE fl_loading_pick_review") + .contains("CREATE TABLE fl_loading_pick_operation") + .contains("CREATE TABLE fl_loading_cabinet_evidence") + .contains("CREATE TABLE fl_container_fee_detail") + .doesNotContain("phase3_order_add_column") + .doesNotContain("assert_phase3_final_schema") + .doesNotContain("-- 来源:"); + } + @Test + void unifiedEntryKeepsFinalConstraintsAndImmutableTriggers() throws Exception { + String sql = sql(); assertThat(sql) - .contains("CREATE TABLE IF NOT EXISTS fl_outbound_dispatch") - .contains("CREATE TABLE IF NOT EXISTS fl_customs_container") - .contains("CREATE TABLE IF NOT EXISTS fl_customs_customer_document") - .contains("CREATE TABLE IF NOT EXISTS fl_customs_customer_document_inbound") - .contains("CREATE TABLE IF NOT EXISTS fl_customs_completion_document") - .contains("terminal_trucking_company_id BIGINT UNSIGNED NULL") - .contains("phase3_container_supplier_snapshot_add_column") - .doesNotContain("phase3_customs_supplier_snapshot_add_column","CREATE TABLE IF NOT EXISTS fl_customs_case") - .contains("CREATE TABLE IF NOT EXISTS fl_port_schedule") - .contains("CREATE TABLE IF NOT EXISTS fl_port_schedule_history") - .contains("CREATE TABLE IF NOT EXISTS fl_preload_loading_plan_link") - .contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_assignment") - .contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_review") - .contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_operation") - .contains("CREATE TABLE IF NOT EXISTS fl_inbound_split_detail") - .contains("trg_inbound_split_detail_before_insert") - .contains("trg_inbound_split_detail_before_update") - .contains("trg_inbound_split_detail_before_delete") - .contains("CHECK (action_type IN ('CLAIM', 'ASSIGN', 'ITEM_CLAIM', 'ITEM_ASSIGN', 'REVIEW', 'BATCH', 'PICK_PALLET', 'COMPLETE_LOADING'))") - .contains("CREATE TABLE IF NOT EXISTS fl_loading_cabinet_evidence") .contains("UNIQUE KEY uk_cabinet_evidence_active (container_booking_id,evidence_type,active_flag)") .contains("UNIQUE KEY uk_cabinet_evidence_operation (source_task_id,operation_key)") - .contains("normalize_loading_cabinet_evidence_collation") - .contains("table_collation<>'utf8mb4_0900_ai_ci'") - .contains("column_name IN ('container_booking_id','source_task_id','active_flag','operation_key','request_hash')") - .doesNotContain("CREATE TABLE IF NOT EXISTS fl_loading_evidence", "phase3_loading_evidence_add_column") - .contains("CALL assert_phase3_final_schema()") - .contains("trg_port_schedule_history_before_update") - .contains("trg_port_schedule_history_before_delete") - .contains("trg_loading_pick_operation_before_delete"); + .contains("UNIQUE KEY uk_inbound_one_active_remainder") + .contains("UNIQUE KEY uk_inbound_split_source_once") + .contains("CREATE TRIGGER trg_inbound_split_detail_before_insert") + .contains("CREATE TRIGGER trg_inbound_split_detail_before_update") + .contains("CREATE TRIGGER trg_inbound_split_detail_before_delete") + .contains("CREATE TRIGGER trg_port_schedule_history_before_update") + .contains("CREATE TRIGGER trg_port_schedule_history_before_delete") + .contains("CREATE TRIGGER trg_loading_pick_operation_before_delete") + .doesNotContain("UNIQUE KEY uk_inbound_one_child") + .doesNotContain("UNIQUE KEY uk_inbound_one_secondary"); } @Test - void unifiedOceanBookingBlockIncludesExistingSchemaReconciliation() throws Exception { - String sql = normalise(Files.readString(UNIFIED)); - assertThat(sql) - .contains("DROP CHECK chk_ocean_booking_status") - .contains("ADD CONSTRAINT chk_ocean_booking_status CHECK") - .contains("DROP COLUMN failure_reason") - .contains("(status IN ('DRAFT','RELEASED'))") - .contains("platform:firstleg:ocean-booking:edit") - .contains("platform:firstleg:ocean-booking:delete") - .doesNotContain("reconcile_ocean_booking_legacy") - .doesNotContain("UPDATE fl_ocean_booking SET status='CONFIRMED'"); - } - - @Test - void permissionRangesDoNotOverlapAndBothPlatformRolesAreGranted() throws Exception { - String sql = Files.readString(UNIFIED, StandardCharsets.UTF_8); - - for (int id = 5102160; id <= 5102164; id++) assertThat(sql).contains(Integer.toString(id)); - for (int id : java.util.List.of(5102170,5102172)) assertThat(sql).contains(Integer.toString(id)); - assertThat(sql).contains("DELETE FROM iam_permission WHERE id IN(5102171,5102173,5102174,5102175)") - .doesNotContain("(5102171,0,'PLATFORM','BUTTON'","(5102173,0,'PLATFORM','BUTTON'", - "(5102174,0,'PLATFORM','BUTTON'","(5102175,0,'PLATFORM','BUTTON'"); - for (int id = 5102180; id <= 5102183; id++) assertThat(sql).contains(Integer.toString(id)); - for (int id = 5102190; id <= 5102193; id++) assertThat(sql).contains(Integer.toString(id)); - assertThat(sql) - .contains("platform:firstleg:outbound-dispatch:%") - .contains("platform:firstleg:customs:%") - .contains("platform:firstleg:port-schedule:%") - .contains("id BETWEEN 5102180 AND 5102183 AND permission_code LIKE 'platform:firstleg:port-schedule:%') <> 3") - .contains("OR p.permission_code LIKE 'platform:firstleg:pda:%')) <> 28") - .contains("platform:firstleg:pda:%") - .contains("role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR')") - .contains("角色授权未完整写入"); - } - - @Test - void phase3ModuleSqlBlocksAreContiguousAndSynchronised() throws Exception { - String unified = normalise(Files.readString(resolveUnified())); - assertBlock(unified, "-- 来源:order/src/main/resources/db/phase3-order-parent-child-schema.sql", - "-- 来源:firstleg/src/main/resources/db/phase3-inbound-parent-child-schema.sql", - "order/src/main/resources/db/phase3-order-parent-child-schema.sql"); - assertBlock(unified, "-- 来源:firstleg/src/main/resources/db/phase3-inbound-parent-child-schema.sql", - "-- 来源:firstleg/src/main/resources/db/phase3-ocean-booking-schema.sql", - "firstleg/src/main/resources/db/phase3-inbound-parent-child-schema.sql"); - assertBlock(unified, "-- 来源:firstleg/src/main/resources/db/phase3-carrier-schedule-query-schema.sql", - "-- 来源:iam/src/main/resources/db/phase3-warehouse-roles.sql", - "firstleg/src/main/resources/db/phase3-carrier-schedule-query-schema.sql"); - assertBlock(unified, "-- 来源:iam/src/main/resources/db/phase3-warehouse-roles.sql", - "-- 入库完成后的唯一状态为 WAIT_SPLIT。", - "iam/src/main/resources/db/phase3-warehouse-roles.sql"); - } - - @Test - void customsBaselineIsUniqueSynchronisedAndRunsBeforeFinalSchemaAssertion() throws Exception { - String unified = normalise(Files.readString(resolveUnified())); - String marker = "-- 来源:firstleg/src/main/resources/db/phase3-container-customs-schema.sql"; - String finalAssertion = "CREATE PROCEDURE assert_phase3_final_schema()"; - List tables = List.of( - "fl_customs_container", - "fl_customs_customer_document", - "fl_customs_customer_document_inbound", - "fl_customs_customer_document_version", - "fl_customs_frozen_document_inbound", - "fl_customs_completion_document", - "fl_customs_completion_document_version", - "fl_customs_container_history", - "fl_customs_command" - ); - - assertBlock(unified, marker, "/* Phase 3: link inbound preload plans", - "firstleg/src/main/resources/db/phase3-container-customs-schema.sql"); - assertThat(count(unified, marker)).isEqualTo(1); - assertThat(unified.indexOf(marker)).isLessThan(unified.indexOf(finalAssertion)); - for (String table : tables) { - String create = "CREATE TABLE IF NOT EXISTS " + table + " ("; - assertThat(count(unified, create)).as(table).isEqualTo(1); - assertThat(unified.indexOf(create)).as(table + " before final assertion") - .isLessThan(unified.indexOf(finalAssertion)); + void unifiedEntrySeedsCanonicalPermissionsAndBothPlatformRoles() throws Exception { + String sql = sql(); + for (int id : new int[] {5102140, 5102149, 5102150, 5102154, 5102160, 5102164, + 5102170, 5102172, 5102180, 5102183, 5102190, 5102193}) { + assertThat(sql).contains(Integer.toString(id)); } - } - - @Test - void financeFeeSourceMigrationsAreContiguousAndSynchronised() throws Exception { - String unified = normalise(Files.readString(resolveUnified())); - assertBlock(unified, "-- 来源:firstleg/src/main/resources/db/finance-fee-source-migration.sql", - "-- 来源:finance/src/main/resources/db/fee-source-event-migration.sql", - "firstleg/src/main/resources/db/finance-fee-source-migration.sql"); - assertBlock(unified, "-- 来源:finance/src/main/resources/db/fee-source-event-migration.sql", - "-- END FINANCE FEE SOURCE MODULE MIGRATIONS", - "finance/src/main/resources/db/fee-source-event-migration.sql"); - - assertThat(unified) - .contains("column_name IN ('source_type','source_no','direction','container_id','fee_item_code',") - .contains("column_name IN ('payload_hash','result_resource_id','error_code')") - .contains("column_name IN ('event_key', 'event_version', 'aggregate_sequence', 'payload_hash',") - .contains("CREATE TABLE IF NOT EXISTS fl_container_fee_detail"); - } - - @Test - void commentUpgradeIsIdempotentAndGuardsNineSchemaStructure() throws Exception { - String sql = normalise(Files.readString(resolveUnified())); - assertThat(sql) - .contains("CREATE PROCEDURE queue_table_comment") - .contains("CREATE PROCEDURE queue_column_comment") - .contains("CREATE PROCEDURE apply_one_table_comments") - .contains("CREATE PROCEDURE apply_queued_comments") - .contains("NOT (BINARY v_current_comment=BINARY p_column_comment)") - .contains("SET v_signature_before=SHA2") - .contains("REPLACE(v_generation_expression,CONCAT(CHAR(92),CHAR(39)),CHAR(39))") - .contains("BINARY target.signature_before") - .contains("字段注释升级遇到未知数据类型") - .contains("字段注释升级遇到未知EXTRA定义") - .contains("字段注释升级遇到未知生成列结构") - .contains("MySQL DDL 会隐式提交") - .contains("不使用 GROUP_CONCAT") - .contains("DECLARE table_cursor CURSOR FOR") - .contains("CALL apply_one_table_comments(v_schema_name,v_table_name)") - .contains("CALL apply_queued_comments();") - .contains("PREPARE table_comment_batch_stmt FROM @table_comment_batch_sql") - .contains("九个业务数据库未完整创建") - .contains("存在缺失中文注释的业务表") - .contains("存在缺失中文注释的业务字段") - .doesNotContain("CREATE PROCEDURE upgrade_column_comment"); - assertThat(count(sql, "CALL queue_table_comment(")).isEqualTo(147); - assertThat(count(sql, "CALL queue_column_comment(")).isGreaterThanOrEqualTo(2374); - assertThat(sql) - .contains("CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'supplier_id'") - .contains("CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'supplier_name'") - .contains("CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'supplier_category'") - .contains("CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'container_booking_id'"); - assertThat(sql.indexOf("CALL apply_queued_comments();", - sql.indexOf("-- END GENERATED NINE-SCHEMA COMMENT UPGRADE CALLS"))) - .isGreaterThan(sql.indexOf("-- END GENERATED NINE-SCHEMA COMMENT UPGRADE CALLS")); + .contains("platform:firstleg:ocean-booking:list") + .contains("platform:firstleg:truck-appointment:list") + .contains("platform:firstleg:outbound-dispatch:list") + .contains("platform:firstleg:customs:list") + .contains("platform:firstleg:port-schedule:list") + .contains("platform:firstleg:pda:view") + .contains("VALUES (4000001,5102190,0,CURRENT_TIMESTAMP(3))") + .contains("VALUES (4000003,5102190,0,CURRENT_TIMESTAMP(3))"); } @Test - void unifiedCreateDefinitionsHaveChineseTableAndColumnComments() throws Exception { - String sql = normalise(Files.readString(resolveUnified())); - List issues = new ArrayList<>(); - Matcher tableMatcher = CREATE_TABLE.matcher(sql); - int tableCount = 0; - int columnCount = 0; - while (tableMatcher.find()) { - tableCount++; - String table = tableMatcher.group(1); - int open = tableMatcher.end() - 1; - int close = matchingParen(sql, open); - int semicolon = sql.indexOf(';', close); - assertThat(semicolon).as("missing semicolon for %s", table).isGreaterThan(close); - if (!CHINESE_COMMENT.matcher(sql.substring(close + 1, semicolon)).find()) { - issues.add(table + ": table comment"); - } - for (String definition : splitDefinitions(sql.substring(open + 1, close))) { - Matcher columnMatcher = COLUMN.matcher(definition); - if (!columnMatcher.find()) continue; - columnCount++; - if (!table.startsWith("fl_customs_") && !CHINESE_COMMENT.matcher(definition).find()) { - issues.add(table + "." + columnMatcher.group(1)); - } - } - } - assertThat(tableCount).isGreaterThan(100); - assertThat(columnCount).isGreaterThan(2000); - assertThat(issues).isEmpty(); - } - - @Test - void passIsUniqueAndOnlyTheFinalExecutableStatement() throws Exception { - String sql = normalise(Files.readString(resolveUnified())); - String pass = "SELECT 'PASS' AS migration_status"; + void initializationPassIsUniqueAndFinal() throws Exception { + String sql = sql(); + String pass = "SELECT 'PASS' AS initialization_status, 'CN' AS region, 208 AS initialized_tables;"; assertThat(count(sql, pass)).isEqualTo(1); - int passIndex = sql.lastIndexOf(pass); - assertThat(passIndex).isGreaterThan(sql.lastIndexOf("USE ")) - .isGreaterThan(sql.lastIndexOf("ALTER TABLE ")) - .isGreaterThan(sql.lastIndexOf("CREATE TABLE ")) - .isGreaterThan(sql.lastIndexOf("CALL ")); - } - - @Test - void unifiedEntryUsesMysql8CompatibleConditionalDdl() throws Exception { - String sql = normalise(Files.readString(resolveUnified())); - + assertThat(sql.trim()).endsWith(pass); assertThat(sql) - .doesNotContain("\n ADD COLUMN IF NOT EXISTS") - .doesNotContain("\n DROP CHECK IF EXISTS") - .doesNotContain("\n+--") - .doesNotContain("\n---") - .doesNotContain("\n>>>") - .doesNotContain("\n<<<") - .contains("CREATE PROCEDURE phase3_pda_add_column") - .contains("CREATE PROCEDURE phase3_pda_replace_operation_check"); + .doesNotContain("SELECT 'PASS' AS migration_status") + .doesNotContain("ON DUPLICATE KEY UPDATE"); } - @Test - void unifiedPdaBlockDropsLegacyPerItemReleaseColumns() throws Exception { - String sql = normalise(Files.readString(resolveUnified())); - - assertThat(sql) - .contains("column_name='release_location_ids'") - .contains("ALTER TABLE fl_loading_pick_item DROP COLUMN release_location_ids") - .contains("column_name='release_mode'") - .contains("ALTER TABLE fl_loading_pick_item DROP COLUMN release_mode") - .doesNotContain("'fl_loading_pick_item', 'release_mode'") - .doesNotContain("'fl_loading_pick_item', 'release_location_ids'") - .doesNotContain("CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'release_mode'") - .doesNotContain("CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'release_location_ids'"); - } - - @Test - void legacySplitIndexesAreOnlyDroppedAndNeverRecreated() throws Exception { - String sql = normalise(Files.readString(resolveUnified())); - assertThat(sql) - .contains("DROP INDEX uk_inbound_one_child") - .contains("DROP INDEX uk_inbound_one_secondary") - .doesNotContain("UNIQUE KEY `uk_inbound_one_child`") - .doesNotContain("UNIQUE KEY `uk_inbound_one_secondary`") - .doesNotContain("UNIQUE KEY `uk_inbound_split_batch_source_item`"); - } - - @Test - void unifiedEntryEnforcesSingleRemainderWritesAndKeepsLegacyColumns() throws Exception { - String sql = normalise(Files.readString(resolveUnified())); - - assertThat(sql) - .contains("'fl_inbound_order', 'active_remainder_root_id'") - .contains("uk_inbound_one_active_remainder") - .contains("uk_inbound_split_source_once") - .contains("NEW.loaded_child_inbound_id IS NOT NULL") - .contains("NEW.child_inbound_id = batch.remainder_child_inbound_id") - .contains("source_order.package_count = NEW.loaded_package_count") - .contains("根入仓单原始箱数与已完成分片及当前余量不守恒") - .contains("'fl_inbound_split_batch', 'loaded_child_inbound_id'") - .contains("fk_inbound_split_loaded_child"); - } - - private static void assertBlock(String unified, String marker, String nextMarker, String modulePath) - throws Exception { - int start = unified.indexOf(marker); - int next = unified.indexOf(nextMarker, start + marker.length()); - assertThat(start).as("missing marker %s", marker).isGreaterThanOrEqualTo(0); - assertThat(next).as("missing next marker %s", nextMarker).isGreaterThan(start); - String module = normalise(Files.readString(resolve(modulePath))).trim(); - String block = unified.substring(start + marker.length(), next).trim(); - assertThat(block).as("module block drift: %s", modulePath) - .startsWith(module); - } - - private static Path resolveUnified() { - return resolve("deploy/sql/头程数据库统一增量-MySQL8.sql"); - } - - private static Path resolve(String path) { - Path direct = Path.of(path); - if (Files.exists(direct)) return direct; - return Path.of("..", path).normalize(); - } - - private static String normalise(String value) { - return value.replace("\r\n", "\n").replace("\uFEFF", ""); + private static String sql() throws Exception { + return Files.readString(UNIFIED, StandardCharsets.UTF_8) + .replace("\r\n", "\n") + .replace("\uFEFF", ""); } private static int count(String value, String token) { return value.split(java.util.regex.Pattern.quote(token), -1).length - 1; } - - private static int matchingParen(String sql, int open) { - int depth = 0; - char quote = 0; - for (int index = open; index < sql.length(); index++) { - char current = sql.charAt(index); - if (quote != 0) { - if (current == '\\') index++; - else if (current == quote) { - if (index + 1 < sql.length() && sql.charAt(index + 1) == quote) index++; - else quote = 0; - } - continue; - } - if (current == '\'' || current == '"' || current == '`') quote = current; - else if (current == '(') depth++; - else if (current == ')' && --depth == 0) return index; - } - throw new AssertionError("unclosed CREATE TABLE at " + open); - } - - private static List splitDefinitions(String body) { - List definitions = new ArrayList<>(); - int start = 0; - int depth = 0; - char quote = 0; - for (int index = 0; index < body.length(); index++) { - char current = body.charAt(index); - if (quote != 0) { - if (current == '\\') index++; - else if (current == quote) { - if (index + 1 < body.length() && body.charAt(index + 1) == quote) index++; - else quote = 0; - } - continue; - } - if (current == '\'' || current == '"' || current == '`') quote = current; - else if (current == '(') depth++; - else if (current == ')') depth--; - else if (current == ',' && depth == 0) { - definitions.add(body.substring(start, index)); - start = index + 1; - } - } - definitions.add(body.substring(start)); - return definitions; - } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/ServiceCenterUnifiedSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/ServiceCenterUnifiedSqlContractTest.java index 550f3127e..d222b101c 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/ServiceCenterUnifiedSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/ServiceCenterUnifiedSqlContractTest.java @@ -10,7 +10,7 @@ import static org.assertj.core.api.Assertions.assertThat; class ServiceCenterUnifiedSqlContractTest { @Test - void unifiedMigrationContainsServiceCenterSchemaAndGrants() throws Exception { + void unifiedInitializationContainsServiceCenterSchemaAndGrants() throws Exception { Path working = Path.of(System.getProperty("user.dir")); Path path = working.resolve("deploy/sql/头程数据库统一增量-MySQL8.sql"); if (!Files.exists(path)) { @@ -19,24 +19,24 @@ class ServiceCenterUnifiedSqlContractTest { String sql = Files.readString(path, StandardCharsets.UTF_8); assertThat(sql) - .contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking") - .contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking_party") - .contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking_container_requirement") - .contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking_container_allocation") - .contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking_status_history") - .contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking_operation") - .contains("CREATE TABLE IF NOT EXISTS fl_truck_appointment") - .contains("CREATE TABLE IF NOT EXISTS fl_truck_appointment_history") - .contains("CREATE TABLE IF NOT EXISTS fl_truck_appointment_attachment") - .contains("CREATE TABLE IF NOT EXISTS fl_truck_appointment_operation") + .contains("CREATE TABLE fl_ocean_booking") + .contains("CREATE TABLE fl_ocean_booking_party") + .contains("CREATE TABLE fl_ocean_booking_container_requirement") + .contains("CREATE TABLE fl_ocean_booking_container_allocation") + .contains("CREATE TABLE fl_ocean_booking_status_history") + .contains("CREATE TABLE fl_ocean_booking_operation") + .contains("CREATE TABLE fl_truck_appointment") + .contains("CREATE TABLE fl_truck_appointment_history") + .contains("CREATE TABLE fl_truck_appointment_attachment") + .contains("CREATE TABLE fl_truck_appointment_operation") .contains("5102140") .contains("5102149") .contains("5102150") .contains("5102154") - .contains("PLATFORM_ADMIN") - .contains("ORDER_OPERATOR") - .contains("统一迁移失败:订舱或拖车预约角色授权未完整写入") - .contains("assert_phase3_service_center_schema()") - .contains("CALL assert_phase3_service_center_schema();"); + .contains("VALUES (4000001,5102140,0,CURRENT_TIMESTAMP(3))") + .contains("VALUES (4000003,5102140,0,CURRENT_TIMESTAMP(3))") + .contains("VALUES (4000001,5102150,0,CURRENT_TIMESTAMP(3))") + .contains("VALUES (4000003,5102150,0,CURRENT_TIMESTAMP(3))") + .doesNotContain("assert_phase3_service_center_schema"); } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingSqlContractTest.java index 36f5a9d45..ae0f70a6d 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingSqlContractTest.java @@ -7,406 +7,102 @@ import org.springframework.core.io.ClassPathResource; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; @DisplayName("智能装柜SQL契约") class SmartLoadingSqlContractTest { - private static final Pattern PERMISSION_PATTERN = Pattern.compile("'((?:platform:firstleg:)[^']+)'"); - private static final Set PHASE3_PERMISSIONS = Set.of( - "platform:firstleg:container:list", - "platform:firstleg:container:create", "platform:firstleg:container:edit", - "platform:firstleg:smart-loading:list", - "platform:firstleg:smart-loading:assign", "platform:firstleg:smart-loading:delete", - "platform:firstleg:preload:list", - "platform:firstleg:preload:edit", - "platform:firstleg:preload:complete", "platform:firstleg:preload:confirm", - "platform:firstleg:loading-list:list"); - @Test - @DisplayName("平台聚合与客户明细边界及原型快照字段完整") - void shouldProvidePlatformAggregatesAndAccountScopedSnapshots() throws Exception { - assertThat(migration()) - .contains("CREATE TABLE IF NOT EXISTS fl_document_sequence") + void moduleMigrationStillProtectsExistingDatabaseUpgrades() throws Exception { + String sql = moduleMigration(); + assertThat(sql) .contains("CREATE TABLE IF NOT EXISTS fl_container_booking") .contains("CREATE TABLE IF NOT EXISTS fl_loading_plan") .contains("CREATE TABLE IF NOT EXISTS fl_loading_plan_item") - .contains("CREATE TABLE IF NOT EXISTS fl_container_document") - .contains("CREATE TABLE IF NOT EXISTS fl_loading_plan_history") - .contains("account_id BIGINT UNSIGNED NOT NULL") - .contains("customer_name VARCHAR(128) NOT NULL") - .contains("warehouse_id BIGINT UNSIGNED NOT NULL") - .contains("cabinet_no VARCHAR(64) NOT NULL") - .contains("bill_of_lading_no VARCHAR(128)") - .contains("destination_warehouse_name VARCHAR(128)") - .contains("product_name_zh VARCHAR(255)") - .contains("product_name_en VARCHAR(255)") - .contains("product_image_file_id BIGINT UNSIGNED") - .contains("pallet_count INT UNSIGNED") - .contains("net_weight_kg DECIMAL(18,3)") - .contains("dimensional_weight_kg DECIMAL(18,3)") - .contains("product_quantity INT UNSIGNED") - .contains("units_per_carton INT UNSIGNED") - .contains("fba_tracking_no VARCHAR(128)") - .contains("shipment_no VARCHAR(128)") - .contains("shipping_mark VARCHAR(500)") - .contains("internal_remark VARCHAR(1000)") - .contains("customer_remark VARCHAR(1000)") - .contains("warehouse_remark VARCHAR(1000)"); - } - - @Test - @DisplayName("活动明细唯一锁、乐观锁和操作幂等字段完整") - void shouldProvideConcurrencyAndIdempotencyBoundaries() throws Exception { - assertContainerLifecycleSchema(migration()); - assertContainerLifecycleSchema(unifiedMigration()); - assertThat(migration()) - .contains("active_inbound_detail_id BIGINT UNSIGNED GENERATED ALWAYS AS") - .contains("CASE WHEN deleted = 0 THEN inbound_detail_id ELSE NULL END") - .contains("active_assignment_key VARCHAR(96) GENERATED ALWAYS AS") - .contains("UNIQUE KEY uk_loading_item_plan_assignment (loading_plan_id, active_assignment_key)") - .contains("KEY idx_loading_item_active_detail (account_id, active_inbound_detail_id)") - .contains("UNIQUE KEY uk_container_booking_no (booking_no)") - .contains("UNIQUE KEY uk_container_booking_cabinet (cabinet_no)") - .contains("UNIQUE KEY uk_loading_history_operation (operation_id)") - .contains("version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号'"); - } - - @Test - @DisplayName("存量货柜迁移补齐BaseMapper所需客户快照列") - void shouldMigrateContainerCustomerSnapshotColumns() throws Exception { - for (String sql : Set.of(migration(), unifiedMigration())) { - assertThat(sql) - .contains("ADD COLUMN customer_id BIGINT UNSIGNED NULL") - .contains("ADD COLUMN customer_name VARCHAR(128) NULL") - .contains("JOIN fl_ocean_booking ocean_booking ON ocean_booking.id=container_booking.ocean_booking_id") - .contains("container_booking.customer_id=COALESCE(container_booking.customer_id,ocean_booking.customer_id)") - .contains("container_booking.customer_name=COALESCE(container_booking.customer_name,ocean_booking.customer_name)"); - } - } - - @Test - @DisplayName("同一入库明细的多条单箱测量记录只生成一个候选") - void shouldAggregateCartonMeasurementRowsByInboundDetail() throws Exception { - String mapper = Files.readString(Path.of( - "src/main/java/com/baiyue/firstleg/mapper/SmartLoadingCandidateMapper.java"), StandardCharsets.UTF_8); - assertThat(mapper) - .contains("SUM(package_count) package_count") - .contains("GROUP BY tenant_id,measurement_id,inbound_detail_id") - .contains("split_item.source_inbound_detail_id=i.inbound_detail_id") - .doesNotContain("split_item.source_measurement_item_id=i.id"); - } - - @Test - @DisplayName("全局候选分页具备平台维度和关联明细索引") - void shouldProvideGlobalCandidatePagingIndexes() throws Exception { - assertThat(new ClassPathResource("db/phase2-inbound-schema.sql") - .getContentAsString(StandardCharsets.UTF_8)) - .contains("idx_inbound_smart_loading (warehouse_id, process_status, channel_code, expected_arrival_at, id)") - .contains("idx_measure_inbound_status_id (tenant_id, inbound_id, status, id)") - .contains("idx_measure_item_candidate (tenant_id, measurement_id, inbound_detail_id)"); - assertThat(migration()) - .contains("idx_loading_item_candidate (account_id, inbound_id, inbound_detail_id, deleted)"); - String unified = unifiedMigration(); - assertThat(unified) - .contains("CREATE PROCEDURE baiyue_firstleg.ensure_smart_loading_candidate_indexes()") - .contains("ADD KEY idx_inbound_smart_loading") - .contains("ADD KEY idx_measure_inbound_status_id") - .contains("ADD KEY idx_measure_item_candidate") - .contains("ADD KEY idx_loading_item_candidate") - .contains("ADD KEY idx_inbound_split_candidate"); - } - - @Test - @DisplayName("可编辑备注使用计划先行与明细单表CAS并允许已拣明细仅改备注") - void shouldUpdateEditableRemarksWithoutMultiTableTriggerConflict() throws Exception { - String mapper = Files.readString(Path.of( - "src/main/java/com/baiyue/firstleg/mapper/LoadingPlanItemMapper.java"), StandardCharsets.UTF_8); - assertThat(mapper) - .contains("UPDATE fl_loading_plan_item i") - .contains("p.version=#{touchedPlanVersion}") - .contains("p.plan_status IN ('EDITING','LOADING')") - .contains("i.frozen_at IS NULL") - .doesNotContain("UPDATE fl_loading_plan_item i JOIN fl_loading_plan p") - .doesNotContain("p.version=p.version+1"); - assertEditableRemarksTriggerContract(migration()); - assertEditableRemarksTriggerContract(unifiedMigration()); - } - - private static void assertContainerLifecycleSchema(String sql) { - assertThat(sql) - .contains("source_type VARCHAR(24) NOT NULL DEFAULT 'SIMULATION'") - .contains("record_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE'") - .contains("merged_into_container_booking_id BIGINT UNSIGNED NULL") - .contains("UNIQUE KEY uk_container_booking_container_no (container_no)") - .contains("KEY idx_container_booking_merge (record_status,merged_into_container_booking_id)") - .contains("CHECK (source_type IN ('SIMULATION','OCEAN_BOOKING'))") - .contains("CHECK (record_status IN ('ACTIVE','MERGED'))") - .contains("column_name='source_type'") - .contains("column_name='record_status'") - .contains("column_name='merged_into_container_booking_id'"); - } - - private static void assertEditableRemarksTriggerContract(String sql) { - assertThat(sql) - .contains("IF NOT (NEW.internal_remark <=> OLD.internal_remark)") - .contains("OR NOT (NEW.warehouse_remark <=> OLD.warehouse_remark)") - .contains("OR NOT (NEW.customer_service_remark <=> OLD.customer_service_remark) THEN") - .contains("IF NEW.package_count <> OLD.package_count OR NEW.deleted <> OLD.deleted") - .contains("已有PDA拣货记录的装柜明细修改备注时不得变更其他业务字段") - .contains("ELSEIF NOT EXISTS (") - .contains("IF OLD.frozen_at IS NOT NULL THEN") - .contains("装柜完成后的明细永久冻结"); - } - - @Test - @DisplayName("货柜简单文件只保留目的港清关与DO,PU改为数字码") - void shouldAllowExactlyTwoSimpleContainerDocumentTypes() throws Exception { - assertThat(migration()) - .contains("'DESTINATION_CLEARANCE', 'DELIVERY_ORDER'") - .contains("pickup_code VARCHAR(64) NULL") - .contains("file_hash VARCHAR(64) NULL") - .contains("completed_by BIGINT UNSIGNED NULL") - .contains("completed_at DATETIME(3) NULL"); - assertThat(unifiedMigration()) - .contains("pickup_code VARCHAR(64) NULL") - .contains("pickup_code_updated_by BIGINT UNSIGNED NULL") - .contains("pickup_code_updated_by_name VARCHAR(64) NULL") - .contains("pickup_code_updated_at DATETIME(3) NULL") - .contains("chk_container_booking_pickup_code") - .contains("CHECK (pickup_code IS NULL OR pickup_code REGEXP '^[0-9]{1,64}$')"); - } - - @Test - @DisplayName("平台菜单按钮权限使用统一前缀并授权运营角色") - void shouldProvidePlatformPermissions() throws Exception { - assertThat(migration()) - .contains("platform:firstleg:container:view") - .doesNotContain("'platform:firstleg:smart-loading:push', '推送预装'") - .contains("platform:firstleg:preload:confirm") - .contains("platform:firstleg:loading-list:view") - .contains("role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR')") - .contains("智能装柜迁移失败:平台权限资源未完整创建") + .contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_task") + .contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_item") + .contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_history") + .contains("migrate_legacy_loading_pick_task") + .contains("assert_phase3_smart_loading_schema") .contains("智能装柜迁移失败:平台角色授权未完整写入"); } @Test - @DisplayName("模块脚本和统一脚本均校验11项权限与两个角色22项授权") - void shouldAssertCompletePermissionGrants() throws Exception { - assertPermissionContract(migration()); - assertPermissionContract(unifiedMigration()); - assertThat(unifiedMigration()) - .contains("CALL migrate_legacy_loading_pick_task()") - .contains("CREATE TABLE IF NOT EXISTS fl_inbound_loading_pick_task") - .contains("5102121, 0, 'PLATFORM'") - .contains("5102129, 0, 'PLATFORM'") - .contains("三期入库装柜角色授权未完整写入") - .contains("permission.permission_code IN (") - .contains("role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR')") - .contains("'firstleg:inbound:split', 'firstleg:inbound:preload'") - .contains(")) <> 18 THEN"); - } - - @Test - @DisplayName("计划状态只允许编辑中、装柜中和已完成单向推进") - void shouldEnforceOneWayStatusAndPermanentFreeze() throws Exception { - assertThat(migration()) - .contains("CHECK (plan_status IN ('EDITING', 'LOADING', 'COMPLETED'))") - .contains("OLD.plan_status = 'EDITING' AND NEW.plan_status IN ('EDITING', 'LOADING')") - .contains("NEW.plan_status IN ('LOADING', 'COMPLETED')") - .contains("OLD.plan_status = 'LOADING' AND NEW.plan_status = 'EDITING'") - .doesNotContain("旧版本在推送预装时即写入 LOADING") - .doesNotContain("修复旧版本“推送预装即拣货中”的状态污染") - .contains("SET MESSAGE_TEXT = '装柜状态只允许单向推进'") - .contains("IF OLD.plan_status = 'LOADING' AND NEW.plan_status = 'COMPLETED' THEN") - .contains("SET frozen_at = COALESCE(frozen_at, NEW.confirmed_at, CURRENT_TIMESTAMP(3))") - .contains("已有PDA拣货记录的装柜明细仅允许收敛部分拣货实际箱数") - .contains("装柜完成后的明细永久冻结") - .contains("current_plan_status IN ('LOADING', 'COMPLETED')") - .contains("装柜作业开始后仅允许修改集装箱号与放舱运输数据") - .doesNotContain("NOT (OLD.bill_of_lading_no <=> NEW.bill_of_lading_no)") - .doesNotContain("NOT (OLD.vessel_name <=> NEW.vessel_name)") - .doesNotContain("NOT (OLD.estimated_sailing_at <=> NEW.estimated_sailing_at)") - .doesNotContain("preload_status") - .doesNotContain("PRELOAD_PUSHED") - .doesNotContain("PRELOADING") - .doesNotContain("PRELOAD_COMPLETED") - .doesNotContain("CONFIRMED") - .doesNotContain("ROLLED_BACK") - .doesNotContain("WITHDRAWN"); - assertLoadingCompletionTrigger(migration()); - assertLoadingCompletionTrigger(unifiedMigration()); - assertThat(unifiedMigration().indexOf("CALL phase3_add_container_channel_group()")) - .isLessThan(unifiedMigration().indexOf("CREATE TRIGGER trg_container_booking_before_update")); - } - - private static void assertLoadingCompletionTrigger(String sql) { - int triggerStart = sql.indexOf("CREATE TRIGGER trg_loading_plan_before_update"); - int triggerEnd = sql.indexOf("DROP TRIGGER IF EXISTS trg_loading_plan_after_update", triggerStart); - assertThat(triggerStart).isGreaterThanOrEqualTo(0); - assertThat(triggerEnd).isGreaterThan(triggerStart); - String beforeUpdateTrigger = sql.substring(triggerStart, triggerEnd); - assertThat(beforeUpdateTrigger) - .contains("AND NOT EXISTS (") - .contains("pick_task.loading_plan_id = OLD.id") - .doesNotContain("pick_task.task_status") - .contains("pick_item.item_status = 'PICKED'") - .contains("pick_item.item_status = 'PARTIAL'") - .contains("pick_item.picked_package_count > 0") - .contains("pick_item.item_status = 'SKIPPED'") - .contains("pick_item.picked_package_count = 0") - .doesNotContain("pick_item.picked_package_count <> item.package_count"); - assertThat(org.springframework.util.StringUtils.countOccurrencesOf(sql, - "pick_item.picked_package_count = item.package_count")).isEqualTo(2); - } - - @Test - @DisplayName("PDA三表、唯一索引、向上取整和不可变审计契约完整") - void shouldProvidePdaContract() throws Exception { - assertThat(migration()) - .contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_task") - .contains("CALL migrate_legacy_loading_pick_task()") - .contains("source_total_columns = 13 AND source_current_columns = 13") - .contains("index_name = 'uk_loading_pick_task_booking'") - .contains("RENAME TABLE fl_loading_pick_task TO fl_inbound_loading_pick_task") - .contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_item") - .contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_history") - .contains("UNIQUE KEY uk_loading_pick_task_plan (loading_plan_id)") - .contains("UNIQUE KEY uk_loading_pick_item_plan_item (loading_plan_item_id)") - .contains("UNIQUE KEY uk_loading_pick_history_operation (operation_id)") - .contains("上层拆分按业务规则向上取整") - .contains("CONSTRAINT chk_loading_pick_item_planned CHECK (planned_package_count > 0)") - .contains("CALL repair_phase3_loading_pick_task_foreign_keys()") - .contains("GROUP_CONCAT(CONCAT('DROP FOREIGN KEY `'") - .contains("item_fk_count <> 1 OR item_target_count <> 1") - .contains("history_fk_count <> 1 OR history_target_count <> 1") - .contains("FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id)") - .contains("referenced_column_name = 'id'") - .contains("柜次PDA明细任务外键不唯一或指向错误") - .contains("柜次PDA历史任务外键不唯一或指向错误") - .doesNotContain("DROP FOREIGN KEY fk_loading_pick_item_task") - .doesNotContain("DROP FOREIGN KEY fk_loading_pick_history_task") - .contains("trg_loading_pick_task_before_update") - .contains("trg_loading_pick_item_before_update") - .contains("trg_loading_pick_item_before_delete") - .contains("trg_loading_pick_history_before_update") - .contains("trg_loading_pick_history_before_delete") - .contains("PDA唯一索引缺失") - .contains(")) <> 16 THEN"); - } - - @Test - @DisplayName("统一脚本只定义并调用一次智能装柜断言") - void shouldDefineSmartLoadingAssertionOnce() throws Exception { - String unified = unifiedMigration(); - assertThat(count(unified, "CREATE PROCEDURE assert_phase3_smart_loading_schema()")) - .isEqualTo(1); - assertThat(count(unified, "CALL assert_phase3_smart_loading_schema();")) - .isEqualTo(1); - assertThat(count(unified, "SELECT 'PASS' AS migration_status")) - .isEqualTo(1); - int relocation = unified.indexOf("CALL unified_relocate_inbound_loading_permissions();"); - int assertion = unified.indexOf("CALL assert_phase3_smart_loading_schema();"); - assertThat(relocation).isGreaterThan(0); - assertThat(assertion).isGreaterThan(relocation); - assertThat(unified.substring(relocation, assertion)) - .contains("tmp_phase3_inbound_role_grant") - .contains("DROP TEMPORARY TABLE IF EXISTS tmp_phase3_inbound_role_grant") - .contains("5102121, 0, 'PLATFORM'") - .contains("5102129, 0, 'PLATFORM'") - .contains("role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR')"); - } - - @Test - @DisplayName("详细拣货和入库汇总拣货使用独立完整表型") - void shouldKeepDetailedAndInboundPickTaskShapesSeparate() throws Exception { - String unified = unifiedMigration(); - assertThat(unified) - .contains("CREATE TABLE IF NOT EXISTS fl_loading_pick_task (") - .contains("loading_plan_id BIGINT UNSIGNED NOT NULL") - .contains("container_booking_id BIGINT UNSIGNED NOT NULL") - .contains("cabinet_no VARCHAR(64) NOT NULL") - .contains("container_no VARCHAR(64) NULL") - .contains("task_status VARCHAR(24) NOT NULL DEFAULT 'LOADING'") - .contains("column_name = 'task_status' AND LOWER(column_type) = 'varchar(24)'") + void unifiedInitializationCreatesSeparateLoadingAndInboundTaskShapes() throws Exception { + String sql = unified(); + assertThat(sql) + .contains("CREATE TABLE fl_loading_pick_task") + .contains("loading_plan_id bigint unsigned NOT NULL") + .contains("container_booking_id bigint unsigned NOT NULL") + .contains("task_status varchar(24) NOT NULL DEFAULT 'LOADING'") .contains("UNIQUE KEY uk_loading_pick_task_plan (loading_plan_id)") .contains("UNIQUE KEY uk_loading_pick_task_booking (container_booking_id)") - .contains("KEY idx_loading_pick_container (container_no, task_status, started_at)") - .contains("CREATE TABLE IF NOT EXISTS fl_inbound_loading_pick_task (") - .contains("tenant_id BIGINT UNSIGNED NOT NULL") - .contains("inbound_id BIGINT UNSIGNED NOT NULL") - .contains("expected_count INT UNSIGNED NULL") - .contains("picked_count INT UNSIGNED NULL") - .contains("UNIQUE KEY uk_loading_pick_tenant_inbound (tenant_id, inbound_id)") - .contains("KEY idx_loading_pick_status (tenant_id, status, updated_at)"); + .contains("CREATE TABLE fl_inbound_loading_pick_task") + .contains("tenant_id bigint unsigned NOT NULL") + .contains("inbound_id bigint unsigned NOT NULL") + .contains("UNIQUE KEY uk_loading_pick_tenant_inbound (tenant_id,inbound_id)") + .contains("KEY idx_loading_pick_status (tenant_id,status,updated_at)") + .doesNotContain("migrate_legacy_loading_pick_task"); } @Test - @DisplayName("迁移拒绝缺列错类型和混合拣货表型") - void shouldRejectMalformedLegacyPickTaskShapes() throws Exception { - assertMalformedShapeGuards(migration()); - assertMalformedShapeGuards(unifiedMigration()); + void unifiedInitializationCreatesFinalConcurrencyAndCandidateIndexes() throws Exception { + String sql = unified(); + assertThat(sql) + .contains("active_inbound_detail_id bigint unsigned GENERATED ALWAYS AS") + .contains("active_assignment_key varchar(96) GENERATED ALWAYS AS") + .contains("UNIQUE KEY uk_loading_item_plan_assignment (loading_plan_id,active_assignment_key)") + .contains("KEY idx_loading_item_active_detail (account_id,active_inbound_detail_id)") + .contains("KEY idx_inbound_smart_loading (warehouse_id,process_status,channel_code,expected_arrival_at,id)") + .contains("KEY idx_measure_inbound_status_id (tenant_id,inbound_id,status,id)") + .contains("KEY idx_measure_item_candidate (tenant_id,measurement_id,inbound_detail_id)") + .contains("KEY idx_loading_item_candidate (account_id,inbound_id,inbound_detail_id,deleted)") + .contains("KEY idx_inbound_split_candidate") + .doesNotContain("ensure_smart_loading_candidate_indexes"); } - private static String migration() throws Exception { + @Test + void unifiedInitializationKeepsFinalLifecycleConstraintsAndTriggers() throws Exception { + String sql = unified(); + assertThat(sql) + .contains("source_type varchar(24) NOT NULL DEFAULT 'SIMULATION'") + .contains("record_status varchar(16) NOT NULL DEFAULT 'ACTIVE'") + .contains("merged_into_container_booking_id bigint unsigned DEFAULT NULL") + .contains("UNIQUE KEY uk_container_booking_container_no (container_no)") + .contains("KEY idx_container_booking_merge (record_status,merged_into_container_booking_id)") + .contains("CREATE TRIGGER trg_loading_plan_before_update") + .contains("装柜状态只允许单向推进") + .contains("CREATE TRIGGER trg_loading_item_before_update") + .contains("装柜完成后的明细永久冻结") + .contains("CREATE TRIGGER trg_container_booking_before_update") + .contains("装柜作业开始后仅允许修改集装箱号与放舱运输数据") + .doesNotContain("assert_phase3_smart_loading_schema"); + } + + @Test + void unifiedInitializationSeedsSmartLoadingPermissionsForBothPlatformRoles() throws Exception { + String sql = unified(); + assertThat(sql) + .contains("(5102090,0,'PLATFORM','MENU',5102000,'platform:firstleg:container:list'") + .contains("(5102094,0,'PLATFORM','MENU',5102000,'platform:firstleg:smart-loading:list'") + .contains("(5102100,0,'PLATFORM','MENU',5102000,'platform:firstleg:preload:list'") + .contains("(5102106,0,'PLATFORM','MENU',5102000,'platform:firstleg:loading-list:list'") + .contains("platform:firstleg:container:list") + .contains("platform:firstleg:smart-loading:assign") + .contains("platform:firstleg:preload:confirm") + .contains("platform:firstleg:loading-list:list") + .contains("VALUES (4000001,5102106,0,CURRENT_TIMESTAMP(3))") + .contains("VALUES (4000003,5102106,0,CURRENT_TIMESTAMP(3))"); + } + + private static String moduleMigration() throws Exception { return new ClassPathResource("db/phase3-smart-loading-schema.sql") .getContentAsString(StandardCharsets.UTF_8); } - private static String unifiedMigration() throws Exception { - Path working = Path.of("").toAbsolutePath(); - Path file = working.resolve("deploy/sql/头程数据库统一增量-MySQL8.sql"); - if (!Files.exists(file)) { - file = working.resolve("../deploy/sql/头程数据库统一增量-MySQL8.sql").normalize(); - } - return Files.readString(file, StandardCharsets.UTF_8); - } - - private static void assertPermissionContract(String sql) { - int message = sql.indexOf("智能装柜迁移失败:平台角色授权未完整写入"); - assertThat(message).isGreaterThan(0); - - int grantStart = sql.lastIndexOf("INSERT IGNORE INTO iam_role_permission", message); - int grantEnd = sql.indexOf(';', grantStart); - assertThat(permissions(sql.substring(grantStart, grantEnd))).isEqualTo(PHASE3_PERMISSIONS); - - int assertionStart = sql.lastIndexOf("IF (SELECT COUNT(DISTINCT CONCAT", message); - String assertion = sql.substring(assertionStart, message); - assertThat(permissions(assertion)).isEqualTo(PHASE3_PERMISSIONS); - assertThat(assertion).contains("<> 22 THEN"); - } - - private static Set permissions(String sql) { - Matcher matcher = PERMISSION_PATTERN.matcher(sql); - return matcher.results().map(result -> result.group(1)).collect(Collectors.toSet()); - } - - private static void assertMalformedShapeGuards(String sql) { - assertThat(sql) - .containsAnyOf("source_total_columns = 11", "source_total = 11") - .containsAnyOf("source_smart_columns = 11", "source_smart = 11") - .containsAnyOf("source_total_columns = 12", "source_total = 12") - .containsAnyOf("source_inbound_columns = 12", "source_inbound = 12") - .contains("column_name = 'version'") - .contains("column_name = 'remark'") - .contains("column_name = 'updated_by'") - .contains("column_name = 'updated_by_name'") - .contains("column_name = 'created_at'") - .contains("column_name = 'updated_at'") - .contains("LOWER(column_type) = 'int'") - .contains("LOWER(column_type) = 'int unsigned'") - .contains("LOWER(extra) LIKE '%on update current_timestamp%'") - .contains("uk_loading_pick_tenant_inbound") - .contains("idx_loading_pick_status") - .contains("referenced_table_name = 'fl_inbound_order'") - .contains("字段签名未知或混合"); - } - - private static int count(String value, String token) { - return (value.length() - value.replace(token, "").length()) / token.length(); + private static String unified() throws Exception { + Path direct = Path.of("deploy/sql/头程数据库统一增量-MySQL8.sql"); + Path path = Files.exists(direct) ? direct : Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql"); + return Files.readString(path, StandardCharsets.UTF_8); } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/mapper/TerminalDrayageSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/mapper/TerminalDrayageSqlContractTest.java index 52bc4bd58..971863ec0 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/mapper/TerminalDrayageSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/mapper/TerminalDrayageSqlContractTest.java @@ -22,12 +22,12 @@ class TerminalDrayageSqlContractTest { assertThat(selected).isEqualTo(fields); if (name.equals("pendingMails")) assertThat(sql).contains("earlier.container_booking_id=m.container_booking_id", "earlier.id'SENT'"); } - for (Path path : java.util.List.of(Path.of("src/main/resources/db/phase3-terminal-drayage-schema.sql"), Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql"))) { - assertThat(Files.readString(path)).contains( - "CREATE TABLE IF NOT EXISTS fl_terminal_drayage_mail (", - "CHECK(status IN ('PENDING','SENDING','SENT','FAILED'))", - "('SELECT_FLEET','SEND_PICKUP_EMAIL','SCHEDULE_PICKUP','RECORD_EMPTY_RETURN')"); - } + String baseline = Files.readString(Path.of("src/main/resources/db/phase3-terminal-drayage-schema.sql")); + String unified = Files.readString(Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql")); + assertThat(baseline).contains("CREATE TABLE IF NOT EXISTS fl_terminal_drayage_mail ("); + for (String sql : java.util.List.of(baseline, unified)) assertThat(sql).contains( + "PENDING","SENDING","SENT","FAILED", + "SELECT_FLEET","SEND_PICKUP_EMAIL","SCHEDULE_PICKUP","RECORD_EMPTY_RETURN"); } @Test @@ -35,10 +35,10 @@ class TerminalDrayageSqlContractTest { String baseline = Files.readString(Path.of("src/main/resources/db/phase3-terminal-drayage-schema.sql")); assertThat(baseline).doesNotContain("actual_pickup_at", "chk_terminal_drayage_times"); String release = Files.readString(Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql")); - assertThat(release).contains("ALTER TABLE fl_terminal_drayage DROP COLUMN actual_pickup_at", - "DROP CHECK chk_terminal_drayage_history_action", - "ADD CONSTRAINT chk_terminal_drayage_history_action CHECK(action IN"); - assertThat(release).doesNotContain("chk_terminal_drayage_times"); + assertThat(release) + .contains("CREATE TABLE fl_terminal_drayage") + .contains("CONSTRAINT chk_terminal_drayage_history_action CHECK") + .doesNotContain("actual_pickup_at", "chk_terminal_drayage_times", "ALTER TABLE fl_terminal_drayage"); } @Test @@ -62,12 +62,13 @@ class TerminalDrayageSqlContractTest { } @Test - void unifiedMigrationRepairsColumnsAfterAPartialDdlRun() throws Exception { + void unifiedInitializationCreatesFinalColumnsWithoutRepairDdl() throws Exception { String sql = Files.readString(Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql")); assertThat(sql).contains( - "ALTER TABLE fl_terminal_drayage", - "ALTER TABLE fl_terminal_drayage_history", - "ALTER TABLE fl_terminal_drayage_operation", - "MODIFY COLUMN estimated_empty_return_at DATETIME(3) NULL COMMENT '服务端按卸柜完成时间加72小时计算的预计还柜时间'"); + "CREATE TABLE fl_terminal_drayage", + "CREATE TABLE fl_terminal_drayage_history", + "CREATE TABLE fl_terminal_drayage_operation", + "estimated_empty_return_at datetime(3) DEFAULT NULL COMMENT '服务端按卸柜完成时间加72小时计算的预计还柜时间'"); + assertThat(sql).doesNotContain("ALTER TABLE fl_terminal_drayage"); } } diff --git a/iam/src/test/java/com/baiyue/iam/notification/NotificationSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/notification/NotificationSqlContractTest.java index 5020e27cf..f3cc87809 100644 --- a/iam/src/test/java/com/baiyue/iam/notification/NotificationSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/notification/NotificationSqlContractTest.java @@ -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"); } } diff --git a/iam/src/test/java/com/baiyue/iam/repository/AdminPrototypeMenuPermissionSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/AdminPrototypeMenuPermissionSqlContractTest.java index 2075ae94c..3a1e81bbe 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/AdminPrototypeMenuPermissionSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/AdminPrototypeMenuPermissionSqlContractTest.java @@ -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 diff --git a/iam/src/test/java/com/baiyue/iam/repository/ContainerWorkspacePermissionSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/ContainerWorkspacePermissionSqlContractTest.java index 70a0f887e..fbdfd01d1 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/ContainerWorkspacePermissionSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/ContainerWorkspacePermissionSqlContractTest.java @@ -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 { diff --git a/iam/src/test/java/com/baiyue/iam/repository/CustomerCodeSchemaContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/CustomerCodeSchemaContractTest.java index 27e88c17d..4cad41bf8 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/CustomerCodeSchemaContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/CustomerCodeSchemaContractTest.java @@ -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"); + } } diff --git a/iam/src/test/java/com/baiyue/iam/repository/CustomerDimensionInitializationSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/CustomerDimensionInitializationSqlContractTest.java index 0c22f9ecf..0ddd9a741 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/CustomerDimensionInitializationSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/CustomerDimensionInitializationSqlContractTest.java @@ -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 diff --git a/iam/src/test/java/com/baiyue/iam/repository/CustomerProfileMigrationContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/CustomerProfileMigrationContractTest.java index adf1dd423..b957d375f 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/CustomerProfileMigrationContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/CustomerProfileMigrationContractTest.java @@ -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))"); } } diff --git a/iam/src/test/java/com/baiyue/iam/repository/FinanceG1G2PermissionSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/FinanceG1G2PermissionSqlContractTest.java index 65157ea37..9124ff645 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/FinanceG1G2PermissionSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/FinanceG1G2PermissionSqlContractTest.java @@ -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() { diff --git a/iam/src/test/java/com/baiyue/iam/repository/FinanceG3G4PermissionSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/FinanceG3G4PermissionSqlContractTest.java index f20090187..01106abdc 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/FinanceG3G4PermissionSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/FinanceG3G4PermissionSqlContractTest.java @@ -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() { diff --git a/iam/src/test/java/com/baiyue/iam/repository/InboundHoldPermissionSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/InboundHoldPermissionSqlContractTest.java index 4df605477..6a06a5b69 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/InboundHoldPermissionSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/InboundHoldPermissionSqlContractTest.java @@ -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(); - } } diff --git a/iam/src/test/java/com/baiyue/iam/repository/InitialIdentitySeedSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/InitialIdentitySeedSqlContractTest.java index 64008a223..be5c81ab3 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/InitialIdentitySeedSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/InitialIdentitySeedSqlContractTest.java @@ -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"); diff --git a/iam/src/test/java/com/baiyue/iam/repository/LocationPermissionSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/LocationPermissionSqlContractTest.java index cffa4ece1..4a330b1aa 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/LocationPermissionSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/LocationPermissionSqlContractTest.java @@ -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) { diff --git a/iam/src/test/java/com/baiyue/iam/repository/OverseasPhase4PermissionSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/OverseasPhase4PermissionSqlContractTest.java index b7b398184..e9c243f8d 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/OverseasPhase4PermissionSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/OverseasPhase4PermissionSqlContractTest.java @@ -64,10 +64,10 @@ class OverseasPhase4PermissionSqlContractTest { Set 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 diff --git a/iam/src/test/java/com/baiyue/iam/repository/Phase5DeliveryPermissionSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/Phase5DeliveryPermissionSqlContractTest.java index cf440e040..a1ebb90c5 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/Phase5DeliveryPermissionSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/Phase5DeliveryPermissionSqlContractTest.java @@ -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 { diff --git a/iam/src/test/java/com/baiyue/iam/repository/QualificationSchemaContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/QualificationSchemaContractTest.java index 547e94802..55d6364b4 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/QualificationSchemaContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/QualificationSchemaContractTest.java @@ -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"); } } diff --git a/iam/src/test/java/com/baiyue/iam/repository/SecurityEventSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/SecurityEventSqlContractTest.java index 55171dea6..b4336c35d 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/SecurityEventSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/SecurityEventSqlContractTest.java @@ -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"); } } diff --git a/iam/src/test/java/com/baiyue/iam/repository/WarehouseRoleSqlContractTest.java b/iam/src/test/java/com/baiyue/iam/repository/WarehouseRoleSqlContractTest.java index 1a4aaeb7d..b8acb4b52 100644 --- a/iam/src/test/java/com/baiyue/iam/repository/WarehouseRoleSqlContractTest.java +++ b/iam/src/test/java/com/baiyue/iam/repository/WarehouseRoleSqlContractTest.java @@ -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); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliveryDocumentContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliveryDocumentContractTest.java index a5fe1a2a6..13ee6c467 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliveryDocumentContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliveryDocumentContractTest.java @@ -44,10 +44,12 @@ class DeliveryDocumentContractTest { @Test void baselineAndOverseasEntryContainSameImmutableRevisionTable() throws Exception { String baseline=Files.readString(Path.of("src/main/resources/db/lastleg.sql")); String overseas=Files.readString(Path.of("../deploy/sql/海外区域统一增量-MySQL8.sql")); - String marker="CREATE TABLE IF NOT EXISTS ll_delivery_document_revision ("; - String a=baseline.substring(baseline.indexOf(marker),baseline.indexOf(";",baseline.indexOf(marker))+1); - String b=overseas.substring(overseas.indexOf(marker),overseas.indexOf(";",overseas.indexOf(marker))+1); - assertThat(a.replaceAll("\\s+","")).isEqualTo(b.replaceAll("\\s+","")); - assertThat(a).contains("uk_delivery_document_command","uk_delivery_document_version"); + for (String sql : List.of(baseline, overseas)) { + assertThat(sql).contains("ll_delivery_document_revision", + "uk_delivery_document_command", "uk_delivery_document_version", + "snapshot_json", "request_hash"); + } + assertThat(overseas).contains("CREATE TABLE ll_delivery_document_revision (") + .doesNotContain("ALTER TABLE ll_delivery_document_revision"); } } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliverySchemaContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliverySchemaContractTest.java index 00d4b5cad..6581d8b52 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliverySchemaContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliverySchemaContractTest.java @@ -68,11 +68,11 @@ class DeliverySchemaContractTest { String overseas = Files.readString(Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"), StandardCharsets.UTF_8).toLowerCase(); - assertTrue(overseas.contains("begin inline empty-database baseline: lastleg")); - assertTrue(overseas.contains(createToken)); - assertTrue(overseas.contains("request_hash char(64) null")); - assertTrue(overseas.indexOf(createToken) < overseas.indexOf("assert_phase5_lastleg_identifiers_migratable")); - assertTrue(overseas.contains("alter table ll_delivery_order add column overseas_warehouse_name")); + String overseasCreateToken = "create table ll_delivery_order"; + assertTrue(overseas.contains(overseasCreateToken)); + assertTrue(overseas.contains("overseas_warehouse_name varchar(255)")); + assertTrue(overseas.contains("unique key uk_ll_delivery_outbound_destination")); + assertFalse(overseas.contains("alter table ll_delivery_order")); } @Test diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/Phase5PdaDeploymentSqlContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/Phase5PdaDeploymentSqlContractTest.java index 4907ad79b..7684ef1af 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/Phase5PdaDeploymentSqlContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/Phase5PdaDeploymentSqlContractTest.java @@ -5,34 +5,26 @@ import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.regex.Pattern; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class Phase5PdaDeploymentSqlContractTest { @Test - void overseasUnifiedMigrationConvergesPhase5IdentifiersWithoutBackfill() throws Exception { + void overseasCleanInitializationDefinesFinalPhase5IdentifiersWithoutBackfill() throws Exception { String sql = Files.readString(Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"), StandardCharsets.UTF_8); - int start = sql.indexOf("-- 五期排车、出库与海外PDA正式业务号/幂等约束"); - int end = sql.indexOf("USE baiyue_lastleg;", start); - assertTrue(start >= 0 && end > start); - String phase5 = sql.substring(start, end); + String normalized = sql.replaceAll("\\s+", ""); - assertTrue(phase5.contains("assert_phase5_lastleg_identifiers_migratable")); - assertTrue(phase5.contains("assert_phase5_lastleg_identifiers_ready")); - assertTrue(phase5.contains("warehouse_id IS NULL OR TRIM(warehouse_id)='' OR TRIM(warehouse_id)='0'")); - assertTrue(phase5.contains("SIGNAL SQLSTATE '45000'")); - assertTrue(phase5.contains("MODIFY COLUMN warehouse_id VARCHAR(128) NOT NULL")); - assertTrue(phase5.contains("ADD COLUMN completion_idempotency_key VARCHAR(255) NULL")); - assertTrue(phase5.contains("ADD UNIQUE KEY uk_ll_dispatch_order_no(dispatch_no)")); - assertTrue(phase5.contains("ADD UNIQUE KEY uk_ll_outbound_no(outbound_no)")); - assertTrue(phase5.contains("ADD UNIQUE KEY uk_ll_dispatch_pda_task_no(task_no)")); - assertTrue(phase5.contains("ADD UNIQUE KEY uk_ll_outbound_pda_task_no(task_no)")); - assertTrue(phase5.contains("ADD UNIQUE KEY uk_ll_outbound_pda_complete_key(customer_id,completion_idempotency_key)")); - assertFalse(Pattern.compile("(?im)^\\s*UPDATE\\s+").matcher(phase5).find()); - assertFalse(phase5.contains("JOIN ll_dispatch_order")); + assertTrue(sql.contains("warehouse_id varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL")); + assertTrue(sql.contains("completion_idempotency_key varchar(255)")); + assertTrue(normalized.contains("UNIQUEKEYuk_ll_dispatch_order_no(dispatch_no)")); + assertTrue(normalized.contains("UNIQUEKEYuk_ll_outbound_no(outbound_no)")); + assertTrue(normalized.contains("UNIQUEKEYuk_ll_dispatch_pda_task_no(task_no)")); + assertTrue(normalized.contains("UNIQUEKEYuk_ll_outbound_pda_task_no(task_no)")); + assertTrue(normalized.contains("UNIQUEKEYuk_ll_outbound_pda_complete_key(customer_id,completion_idempotency_key)")); + assertFalse(sql.contains("assert_phase5_lastleg_identifiers_migratable")); + assertFalse(sql.contains("ALTER TABLE ll_dispatch_order")); + assertFalse(sql.contains("ALTER TABLE ll_outbound_order")); assertFalse(sql.contains("uk_ll_dispatch_order_no(customer_id,dispatch_no)")); assertFalse(sql.contains("uk_ll_outbound_no(customer_id,outbound_no)")); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/ReceivingContainerContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/ReceivingContainerContractTest.java index 9d8f1d297..788d0657f 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/ReceivingContainerContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/ReceivingContainerContractTest.java @@ -84,8 +84,7 @@ class ReceivingContainerContractTest { for (String table : Set.of("ll_inbound_plan_line", "ll_inbound_plan_pallet", "ll_inbound_plan_pallet_allocation")) { assertTrue(tableBlock(schema, table).contains("customer_id varchar(128) not null"), table); - assertTrue(unified.contains("table_name='" + table + "' and column_name='customer_id'"), table); - assertTrue(unified.contains("update " + table), table); + assertTrue(tableBlock(unified, table).contains("customer_id varchar(128)"), table); } assertFalse(schema.contains("tenant_id varchar(64)")); assertFalse(unified.contains("column_name='tenant_id'")); @@ -99,9 +98,9 @@ class ReceivingContainerContractTest { assertFalse(tableBlock(schema, "ll_receiving_container_session").contains("customer_id")); assertFalse(tableBlock(schema, "ll_receiving_container_operation").contains("customer_id")); assertFalse(tableBlock(schema, "ll_container_shipping_gate").contains("customer_id")); - assertTrue(unified.contains("alter table ll_receiving_container_session drop column customer_id")); - assertTrue(unified.contains("alter table ll_receiving_container_operation drop column customer_id")); - assertTrue(unified.contains("alter table ll_container_shipping_gate drop column customer_id")); + assertFalse(tableBlock(unified, "ll_receiving_container_session").contains("customer_id")); + assertFalse(tableBlock(unified, "ll_receiving_container_operation").contains("customer_id")); + assertFalse(tableBlock(unified, "ll_container_shipping_gate").contains("customer_id")); assertTrue(schema.contains("create table if not exists ll_receiving_sku_receipt")); assertTrue(schema.contains("unique key uk_ll_receiving_sku_pallet (customer_id,line_id,pallet_id,sku_code)")); assertTrue(schema.contains("create table if not exists ll_receiving_fba_pallet_count")); @@ -117,22 +116,16 @@ class ReceivingContainerContractTest { assertTrue(receivingSession.contains("overseas_warehouse_id bigint not null")); assertTrue(receivingSession.contains("overseas_warehouse_name varchar(255) not null")); assertFalse(receivingSession.contains("photo_file_ids json")); - assertTrue(unified.contains("create table if not exists ll_receiving_sku_receipt")); - assertTrue(unified.contains("create table if not exists ll_receiving_fba_pallet_count")); - assertTrue(unified.contains("column_name='sign_photos'")); + assertTrue(unified.contains("create table ll_receiving_sku_receipt")); + assertTrue(unified.contains("create table ll_receiving_fba_pallet_count")); + assertTrue(tableBlock(unified, "ll_receiving_container_session").contains("sign_photos json")); assertTrue(schema.contains("wait_sign、signed、receiving、completed")); - assertTrue(unified.contains("customer_id is null or l.customer_id=''")); - assertTrue(unified.contains("customer_id is null or v.customer_id=''")); - assertTrue(unified.contains("customer_id is null or a.customer_id=''")); - assertTrue(unified.contains("signal sqlstate '45000'")); - assertTrue(unified.contains("assert_receiving_global_locks_migratable")); - assertTrue(unified.contains("where status in ('completed','completed_with_difference') and (active_operator_key is not null or active_container_key is not null)")); - assertTrue(unified.contains("concat('operator:',operator_key)")); - assertTrue(unified.contains("concat('container:',upper(trim(container_no)))")); - assertTrue(unified.contains("assert_receiving_operation_global_key_migratable")); - assertTrue(unified.contains("group_concat(column_name order by seq_in_index)= 'operator_key,idempotency_key'")); - assertTrue(unified.contains("assert_pending_orders_do_not_use_source_warehouse")); - assertTrue(unified.contains("assert_active_receiving_session_has_warehouse")); + assertTrue(tableBlock(unified, "ll_receiving_container_session") + .contains("unique key uk_ll_receiving_active_operator (active_operator_key)")); + assertTrue(tableBlock(unified, "ll_receiving_container_operation") + .contains("unique key uk_ll_receiving_operation (operator_key,idempotency_key)")); + assertFalse(unified.contains("assert_receiving_global_locks_migratable")); + assertFalse(unified.contains("alter table ll_receiving_container_session")); } @Test @@ -178,6 +171,7 @@ class ReceivingContainerContractTest { private static String tableBlock(String schema, String table) { int start = schema.indexOf("create table if not exists " + table + " ("); + if (start < 0) start = schema.indexOf("create table " + table + " ("); int end = schema.indexOf(") engine=innodb", start); return start < 0 || end < 0 ? "" : schema.substring(start, end); } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java index 90f0a93c5..786be45df 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java @@ -28,11 +28,19 @@ class LocalLooseboardSchemaContractTest { } @Test - void unifiedIncrementalContainsHistoryIdempotencyAndOperationLedger() throws Exception { + void unifiedInitializationContainsHistoryIdempotencyAndOperationLedger() throws Exception { String sql = Files.readString(Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"), StandardCharsets.UTF_8); - assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_local_looseboard_operation")); - assertTrue(sql.contains("uk_ll_local_looseboard_operation_key(customer_id,order_id,idempotency_key)")); - assertTrue(sql.contains("table_name='ll_local_looseboard_history' AND column_name='idempotency_key'")); + String normalized = sql.replaceAll("\\s+", ""); + assertTrue(sql.contains("CREATE TABLE ll_local_looseboard_operation")); + assertTrue(normalized.contains("uk_ll_local_looseboard_operation_key(customer_id,order_id,idempotency_key)")); + assertTrue(tableBlock(sql, "ll_local_looseboard_history").contains("idempotency_key varchar(255)")); + org.junit.jupiter.api.Assertions.assertFalse(sql.contains("ALTER TABLE ll_local_looseboard_history")); + } + + private static String tableBlock(String sql, String table) { + int start = sql.indexOf("CREATE TABLE " + table + " ("); + int end = sql.indexOf(";", start); + return start < 0 || end < 0 ? "" : sql.substring(start, end); } } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/masterdata/quote/client/DomesticPartnerWarehouseClientContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/masterdata/quote/client/DomesticPartnerWarehouseClientContractTest.java index 0961c5313..0d0c6e580 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/masterdata/quote/client/DomesticPartnerWarehouseClientContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/masterdata/quote/client/DomesticPartnerWarehouseClientContractTest.java @@ -45,6 +45,7 @@ class DomesticPartnerWarehouseClientContractTest { assertThat(compose).contains("INTERNAL_SERVICE_TOKEN: ${INTERNAL_SERVICE_TOKEN}") .contains("DOMESTIC_WAREHOUSE_URL: ${DOMESTIC_WAREHOUSE_URL}"); assertThat(deploy).contains("FIRSTLEG_EVENT_BRIDGE_URL DOMESTIC_WAREHOUSE_URL") - .contains("NACOS_SERVER_ADDR OVERSEAS_NACOS_NAMESPACE INTERNAL_SERVICE_TOKEN"); + .contains("NACOS_SERVER_ADDR OVERSEAS_NACOS_USERNAME OVERSEAS_NACOS_PASSWORD " + + "OVERSEAS_NACOS_NAMESPACE INTERNAL_SERVICE_TOKEN"); } } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/OverseasProcurementSchemaContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/OverseasProcurementSchemaContractTest.java index 54fa76c18..ee27b71af 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/OverseasProcurementSchemaContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/OverseasProcurementSchemaContractTest.java @@ -30,23 +30,26 @@ class OverseasProcurementSchemaContractTest { } @Test - void unifiedIncrementalContainsHistoryIdempotencyKeyForNewAndExistingDatabases() throws Exception { + void unifiedInitializationContainsFinalProcurementAndInboxShapes() throws Exception { String sql = Files.readString( Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"), StandardCharsets.UTF_8); - assertTrue(sql.contains("idempotency_key VARCHAR(255) NULL COMMENT '操作幂等键'")); - assertTrue(sql.contains( - "ALTER TABLE ll_overseas_procurement_status_history ADD COLUMN idempotency_key VARCHAR(255) NULL")); - assertTrue(sql.contains( - "ALTER TABLE ll_overseas_procurement_order ADD COLUMN review_idempotency_key VARCHAR(255) NULL")); - assertTrue(sql.contains( - "ALTER TABLE ll_overseas_procurement_order ADD COLUMN review_request_hash CHAR(64) NULL")); - assertTrue(sql.contains( - "ALTER TABLE ll_overseas_procurement_order ADD COLUMN outbound_details JSON NULL")); - assertTrue(sql.contains( - "table_name='ll_overseas_procurement_status_history' AND column_name='idempotency_key'")); - assertTrue(sql.contains("ALTER TABLE ll_wallet_event_inbox")); - assertTrue(sql.contains("MODIFY COLUMN payload_hash VARCHAR(71) NOT NULL")); + String lower = sql.toLowerCase(); + assertTrue(tableBlock(lower, "ll_overseas_procurement_status_history") + .contains("idempotency_key varchar(255)")); + String order = tableBlock(lower, "ll_overseas_procurement_order"); + assertTrue(order.contains("review_idempotency_key varchar(255)")); + assertTrue(order.contains("review_request_hash char(64)")); + assertTrue(order.contains("outbound_details json")); + assertTrue(tableBlock(lower, "ll_wallet_event_inbox").contains("payload_hash varchar(71)")); + org.junit.jupiter.api.Assertions.assertFalse(sql.contains("ALTER TABLE ll_overseas_procurement")); + org.junit.jupiter.api.Assertions.assertFalse(sql.contains("ALTER TABLE ll_wallet_event_inbox")); + } + + private static String tableBlock(String sql, String table) { + int start = sql.indexOf("create table " + table + " ("); + int end = sql.indexOf(';', start); + return start < 0 || end < 0 ? "" : sql.substring(start, end); } } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/returnreceipt/service/ReturnCartonQueryTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/returnreceipt/service/ReturnCartonQueryTest.java index 673d0c705..b18615a65 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/returnreceipt/service/ReturnCartonQueryTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/returnreceipt/service/ReturnCartonQueryTest.java @@ -23,12 +23,13 @@ class ReturnCartonQueryTest { java.nio.file.Path.of("../deploy/sql/海外区域统一增量-MySQL8.sql"))) { String sql = java.nio.file.Files.readString(path); var matcher = java.util.regex.Pattern.compile( - "CREATE TABLE IF NOT EXISTS ll_return_inventory_carton \\(([\\s\\S]*?)\\) ENGINE") + "CREATE TABLE(?: IF NOT EXISTS)? ll_return_inventory_carton \\(([\\s\\S]*?)\\) ENGINE", + java.util.regex.Pattern.CASE_INSENSITIVE) .matcher(sql); int definitions = 0; while (matcher.find()) { definitions++; - assertThat(matcher.group(1)).as(path.toString()).contains("new_carton_mark VARCHAR(255)"); + assertThat(matcher.group(1).toLowerCase()).as(path.toString()).contains("new_carton_mark varchar(255)"); } assertThat(definitions).isPositive(); } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/schema/LastlegOrderDetailSchemaContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/schema/LastlegOrderDetailSchemaContractTest.java index f1276e0ed..e4bfc0c02 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/schema/LastlegOrderDetailSchemaContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/schema/LastlegOrderDetailSchemaContractTest.java @@ -11,7 +11,7 @@ import static org.assertj.core.api.Assertions.assertThat; class LastlegOrderDetailSchemaContractTest { @Test - void orderDetailHasCustomerIsolationInBaselineAndRepeatableUpgrade() throws Exception { + void orderDetailHasCustomerIsolationInBaselineAndCleanInitialization() throws Exception { String baseline = new ClassPathResource("db/lastleg.sql") .getContentAsString(StandardCharsets.UTF_8); String unified = Files.readString(Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"), @@ -24,11 +24,10 @@ class LastlegOrderDetailSchemaContractTest { .contains("customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键'") .contains("KEY idx_ll_detail_customer_order(customer_id,order_id,id)"); assertThat(unified) - .contains("table_name='ll_order_detail' AND column_name='customer_id'") - .contains("UPDATE ll_order_detail detail") - .contains("SET detail.customer_id=orders.customer_id") - .contains("MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键'") - .contains("ADD KEY idx_ll_detail_customer_order(customer_id,order_id,id)"); + .contains("CREATE TABLE ll_order_detail") + .contains("customer_id varchar(128)") + .contains("KEY idx_ll_detail_customer_order (customer_id,order_id,id)") + .doesNotContain("UPDATE ll_order_detail", "ALTER TABLE ll_order_detail"); assertThat(service) .contains(".eq(LastlegOrderDetail::getCustomerId, order.getCustomerId())") .contains(".eq(LastlegOrderStatusHistory::getCustomerId, order.getCustomerId())"); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5ExceptionVisitSchemaContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5ExceptionVisitSchemaContractTest.java index 8c511c167..084c01f6c 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5ExceptionVisitSchemaContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5ExceptionVisitSchemaContractTest.java @@ -16,14 +16,16 @@ class Phase5ExceptionVisitSchemaContractTest { StandardCharsets.UTF_8); for (String sql : new String[] { baseline, overseas }) { + String normalized = sql.replaceAll("\\s+", ""); + assertThat(normalized).contains( + "ll_exception_order(", + "UNIQUEKEYuk_ll_exception_no(customer_id,exception_no)", + "UNIQUEKEYuk_ll_exception_delivery_return(customer_id,delivery_order_id,exception_type)", + "KEYidx_ll_exception_inbound(customer_id,inbound_code)", + "ll_customer_visit(", + "UNIQUEKEYuk_ll_customer_visit_delivery(customer_id,delivery_order_id)", + "KEYidx_ll_customer_visit_list(customer_id,status,delivery_completed_at)"); assertThat(sql).contains( - "CREATE TABLE IF NOT EXISTS ll_exception_order", - "UNIQUE KEY uk_ll_exception_no(customer_id,exception_no)", - "UNIQUE KEY uk_ll_exception_delivery_return(customer_id,delivery_order_id,exception_type)", - "KEY idx_ll_exception_inbound(customer_id,inbound_code)", - "CREATE TABLE IF NOT EXISTS ll_customer_visit", - "UNIQUE KEY uk_ll_customer_visit_delivery(customer_id,delivery_order_id)", - "KEY idx_ll_customer_visit_list(customer_id,status,delivery_completed_at)", "COMMENT='海外仓异常订单'", "COMMENT='派送单维度客户回访记录'"); } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5OverseasUpgradeSqlContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5OverseasUpgradeSqlContractTest.java index 92777af25..e31035a1e 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5OverseasUpgradeSqlContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5OverseasUpgradeSqlContractTest.java @@ -20,88 +20,73 @@ class Phase5OverseasUpgradeSqlContractTest { "ll_exception_return_item", "ll_exception_order_command", "ll_customer_visit"); @Test - void overseasFixedEntryCreatesEveryPhase5TableBeforeAnyPhase5AlterOrAssertion() throws Exception { + void overseasFixedEntryCreatesEveryPhase5TableExactlyOnce() throws Exception { String overseas = repositoryFile("deploy/sql/海外区域统一增量-MySQL8.sql"); - int phase5Gate = overseas.indexOf("-- 五期排车、出库与海外PDA正式业务号/幂等约束"); - assertThat(phase5Gate).isPositive(); for (String table : PHASE5_TABLES) { - String create = "CREATE TABLE IF NOT EXISTS " + table + " ("; - assertThat(overseas.indexOf(create)).as(table).isBetween(0, phase5Gate - 1); + String create = "CREATE TABLE " + table + " ("; + assertThat(overseas.indexOf(create)).as(table).isGreaterThanOrEqualTo(0); assertThat(count(overseas, create)).as(table).isEqualTo(1); assertThat(overseas.indexOf(table)).as(table + " first reference") - .isEqualTo(overseas.indexOf(create) + "CREATE TABLE IF NOT EXISTS ".length()); + .isEqualTo(overseas.indexOf(create) + "CREATE TABLE ".length()); } - assertThat(overseas).contains("<> 23", "五期完整业务表集合未部署"); + assertThat(overseas).doesNotContain("assert_phase5_lastleg_identifiers_migratable", "ALTER TABLE ll_"); } @Test - void overseasFixedEntryConvergesKnownIntermediatePhase5ShapesWithoutBackfill() throws Exception { + void overseasFixedEntryContainsFinalPhase5ShapesWithoutMigrationBackfill() throws Exception { String overseas = repositoryFile("deploy/sql/海外区域统一增量-MySQL8.sql"); + String normalized = normalize(overseas); - assertThat(overseas).contains( - "ALTER TABLE ll_outbound_order RENAME COLUMN tenant_id TO customer_id", - "ALTER TABLE ll_outbound_pallet_item RENAME COLUMN tenant_id TO customer_id", - "ALTER TABLE ll_delivery_order RENAME COLUMN tenant_id TO customer_id", - "ALTER TABLE ll_outbound_order ADD COLUMN outbound_type", - "ALTER TABLE ll_outbound_order ADD COLUMN lx_codes", - "ALTER TABLE ll_outbound_pallet_item ADD COLUMN picked_package_count", - "ALTER TABLE ll_delivery_order ADD COLUMN overseas_warehouse_name", - "ALTER TABLE ll_delivery_order ADD COLUMN waybill_no", - "ALTER TABLE ll_dropship_order_profile ADD COLUMN source_pallet_no", - "ALTER TABLE ll_dropship_order_profile ADD COLUMN manifest_version", - "ALTER TABLE ll_dropship_order_profile ADD COLUMN manifest_package_count", - "ALTER TABLE ll_dropship_order_profile ADD COLUMN manifest_fragment_count", - "ALTER TABLE ll_dropship_order_profile ADD COLUMN manifest_hash", - "ALTER TABLE ll_inbound_plan_pallet ADD COLUMN version", - "ADD KEY idx_ll_dropship_profile_manifest(source_pallet_no,manifest_hash)", - "ADD UNIQUE KEY uk_ll_dropship_sku_inventory(profile_id,sku_code)", - "MODIFY COLUMN source_pallet_no VARCHAR(128) NOT NULL"); - int relabelCreateEnd = overseas.indexOf(") ENGINE=InnoDB COMMENT='换标货物冻结明细';"); - int relabelModify = overseas.indexOf("MODIFY COLUMN destination_fba_warehouse_code", relabelCreateEnd); - for (String column : List.of("destination_fba_warehouse_code", "source_inventory_balance_id", - "source_inventory_position_id", "source_inbound_order_id")) { - assertThat(overseas.indexOf("ALTER TABLE ll_relabel_item ADD COLUMN " + column, relabelCreateEnd)) - .as(column + " converges before grouped MODIFY") - .isBetween(relabelCreateEnd, relabelModify - 1); - } - int start = overseas.indexOf("-- 五期正式结构必须由海外固定入口自行补齐"); - int end = overseas.indexOf("USE baiyue_lastleg;", start); - String phase5 = overseas.substring(start, end); - assertThat(phase5).doesNotContain("\nUPDATE ", "\nDELETE ", "\nINSERT "); + assertThat(createStatement(overseas, "ll_outbound_order")).contains( + "customer_id", "outbound_type", "lx_codes", "request_hash"); + assertThat(createStatement(overseas, "ll_outbound_pallet_item")).contains("picked_package_count"); + assertThat(createStatement(overseas, "ll_delivery_order")).contains( + "customer_id", "overseas_warehouse_name", "waybill_no"); + assertThat(createStatement(overseas, "ll_dropship_order_profile")).contains( + "source_pallet_no", "manifest_version", "manifest_package_count", + "manifest_fragment_count", "manifest_hash"); + assertThat(normalized).contains( + "keyidx_ll_dropship_profile_manifest(source_pallet_no,manifest_hash)", + "uniquekeyuk_ll_dropship_sku_inventory(profile_id,sku_code)"); + assertThat(createStatement(overseas, "ll_relabel_item")).contains( + "destination_fba_warehouse_code", "source_inventory_balance_id", + "source_inventory_position_id", "source_inbound_order_id"); + assertThat(overseas).doesNotContain("\nUPDATE ", "\nDELETE ", "ALTER TABLE ll_"); } @Test - void overseasFixedEntryUsesTheCurrentBaselineDefinitions() throws Exception { + void overseasFixedEntryAndModuleBaselineBothDefineCompletePhase5Tables() throws Exception { String baseline = repositoryFile("lastleg-service/src/main/resources/db/lastleg.sql"); String overseas = repositoryFile("deploy/sql/海外区域统一增量-MySQL8.sql"); for (String table : PHASE5_TABLES) { - assertThat(normalize(createStatement(overseas, table))) - .as(table) - .isEqualTo(normalize(createStatement(baseline, table))); + assertThat(createStatement(overseas, table)).as(table) + .contains("PRIMARY KEY", "COMMENT='"); + assertThat(createStatement(baseline, table)).as(table) + .contains("PRIMARY KEY", "COMMENT='"); } } @Test void overseasFixedAdminProjectionAssertionMatchesItsAuthorityArray() throws Exception { String overseas = repositoryFile("deploy/sql/海外区域统一增量-MySQL8.sql"); - int start = overseas.indexOf("SET @platform_admin_overseas_authorities = JSON_ARRAY("); - int end = overseas.indexOf(");", start); - String authorityArray = overseas.substring(start, end); - int authorityCount = count(authorityArray, "'platform:"); + int start = overseas.indexOf("VALUES ('AUTHORIZATION','1000001'"); + int end = overseas.indexOf(";", start); + String authorityRow = overseas.substring(start, end); + int authorityCount = count(authorityRow, "platform:"); assertThat(authorityCount).isPositive(); - assertThat(overseas).contains("JSON_LENGTH(authorities_json) <> " + authorityCount); + assertThat(authorityRow).contains("baseline-platform-admin-authorization-v0"); + assertThat(overseas).doesNotContain("@platform_admin_overseas_authorities"); } @Test - void overseasReturnOrderBackfillNormalizesLegacyColumnCollations() throws Exception { + void overseasReturnOrdersUseTheFinalCleanSchemaWithoutLegacyBackfill() throws Exception { String overseas = repositoryFile("deploy/sql/海外区域统一增量-MySQL8.sql"); - assertThat(overseas).contains( - "o.source_order_id COLLATE utf8mb4_unicode_ci = CAST(r.id AS CHAR) COLLATE utf8mb4_unicode_ci", - "o.source_order_no COLLATE utf8mb4_unicode_ci = r.return_no COLLATE utf8mb4_unicode_ci"); + assertThat(overseas).contains("CREATE TABLE ll_return_order (", "CREATE TABLE ll_return_order_item (") + .doesNotContain("o.source_order_id COLLATE", "o.source_order_no COLLATE"); } @Test @@ -122,6 +107,10 @@ class Phase5OverseasUpgradeSqlContractTest { private static String createStatement(String sql, String table) { String token = "CREATE TABLE IF NOT EXISTS " + table + " ("; int start = sql.indexOf(token); + if (start < 0) { + token = "CREATE TABLE " + table + " ("; + start = sql.indexOf(token); + } int end = sql.indexOf(';', start); assertThat(start).as(table).isGreaterThanOrEqualTo(0); assertThat(end).as(table).isGreaterThan(start); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/schema/SchemaChineseCommentContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/schema/SchemaChineseCommentContractTest.java index ad3f1905b..8e0607d16 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/schema/SchemaChineseCommentContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/schema/SchemaChineseCommentContractTest.java @@ -25,8 +25,8 @@ class SchemaChineseCommentContractTest { private static final Pattern CHINESE = Pattern.compile("[\\u4e00-\\u9fff]"); private static final Pattern TABLE = Pattern.compile( - "(?is)CREATE\\s+TABLE\\s+IF\\s+NOT\\s+EXISTS\\s+(ll_[a-z0-9_]+)" - + "\\s*\\((.*?)\\)\\s*ENGINE\\s*=\\s*InnoDB\\s+COMMENT\\s*=\\s*'([^']*)'\\s*;"); + "(?is)CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(ll_[a-z0-9_]+)" + + "\\s*\\((.*?)\\)\\s*ENGINE\\s*=\\s*InnoDB[^;]*?COMMENT\\s*=\\s*'([^']*)'\\s*;"); @Test void allLastlegTablesAndColumnsHaveChineseComments() throws Exception { @@ -39,7 +39,7 @@ class SchemaChineseCommentContractTest { List baselineTables = assertAllChineseComments(baseline, "基线 SQL", 56); assertAllChineseComments(incremental, "增量 SQL", 50); - assertAllChineseComments(overseasUnified, "海外统一增量 SQL", 10); + assertAllChineseComments(overseasUnified, "海外统一初始化 SQL", 127); assertCommentMigrationCovers(incremental, overseasUnified, baselineTables); } @@ -92,7 +92,9 @@ class SchemaChineseCommentContractTest { for (MigrationColumn column : required) { assertSpecificAlterationHasChineseComment(baseline, column); } - assertAllAlteredColumnsHaveChineseComments(overseasUnified, "海外统一增量 SQL"); + for (MigrationColumn column : required) { + assertTableColumnHasChineseComment(overseasUnified, column, "海外统一初始化 SQL"); + } } private static List assertAllChineseComments(String sql, String source, int expectedCount) { @@ -157,8 +159,8 @@ class SchemaChineseCommentContractTest { private static boolean hasCommentedCreate(String sql, String table) { return Pattern.compile( - "(?is)CREATE\\s+TABLE\\s+IF\\s+NOT\\s+EXISTS\\s+" + Pattern.quote(table) - + "\\s*\\(.*?\\)\\s*ENGINE\\s*=\\s*InnoDB\\s+COMMENT\\s*=\\s*'[^']*[\\u4e00-\\u9fff][^']*'\\s*;") + "(?is)CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?" + Pattern.quote(table) + + "\\s*\\(.*?\\)\\s*ENGINE\\s*=\\s*InnoDB[^;]*?COMMENT\\s*=\\s*'[^']*[\\u4e00-\\u9fff][^']*'\\s*;") .matcher(sql).find(); } @@ -199,6 +201,19 @@ class SchemaChineseCommentContractTest { "字段增量缺少中文备注: " + target.table() + "." + target.column()); } + private static void assertTableColumnHasChineseComment(String sql, MigrationColumn target, String source) { + Matcher table = Pattern.compile( + "(?is)CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?" + Pattern.quote(target.table()) + + "\\s*\\((.*?)\\)\\s*ENGINE\\s*=\\s*InnoDB").matcher(sql); + assertTrue(table.find(), source + " 缺少表: " + target.table()); + String definition = splitTopLevel(table.group(1)).stream() + .filter(value -> value.stripLeading().replace("`", "").startsWith(target.column() + " ")) + .findFirst().orElse(""); + assertTrue(Pattern.compile("(?is)\\bCOMMENT\\s+'[^']*[\\u4e00-\\u9fff][^']*'") + .matcher(definition).find(), + source + " 字段缺少中文备注: " + target.table() + "." + target.column()); + } + private static void assertAllAlteredColumnsHaveChineseComments(String sql, String source) { Pattern pattern = Pattern.compile("(?i)\\b(?:ADD|MODIFY)\\s+COLUMN\\s+([a-z0-9_]+)\\b"); Matcher matcher = pattern.matcher(sql); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/security/LastlegSecurityContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/security/LastlegSecurityContractTest.java index be718d5ae..0fca27fe0 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/security/LastlegSecurityContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/security/LastlegSecurityContractTest.java @@ -26,8 +26,7 @@ class LastlegSecurityContractTest { "table_name='ll_security_projection' AND column_name='authorities_json'", "UPDATE ll_security_projection SET roles_json=JSON_ARRAY() WHERE roles_json IS NULL", "UPDATE ll_security_projection SET authorities_json=JSON_ARRAY() WHERE authorities_json IS NULL"); - for (String sql : new String[]{baseline, migration}) { - assertThat(sql).contains("SET @platform_admin_overseas_roles = JSON_ARRAY('PLATFORM_ADMIN')", + assertThat(baseline).contains("SET @platform_admin_overseas_roles = JSON_ARRAY('PLATFORM_ADMIN')", "SET @platform_admin_overseas_authorities = JSON_ARRAY(", "('USER','1000001',0,'ACTIVE',0,JSON_ARRAY(),JSON_ARRAY(),0", "('AUTHORIZATION','1000001',0,NULL,0,@platform_admin_overseas_roles,@platform_admin_overseas_authorities,0", @@ -42,10 +41,25 @@ class LastlegSecurityContractTest { "AND aggregate_version=0 AND last_event_id='baseline-platform-admin-authorization-v0'") .doesNotContain("('AUTHORIZATION','1000001',0,NULL,0,JSON_ARRAY(),JSON_ARRAY(),0", "baseline-merchant-admin"); - } + assertThat(migration).contains( + "CREATE TABLE ll_security_event_inbox (", + "CREATE TABLE ll_security_projection (", + "VALUES ('AUTHORIZATION','1000001',0,NULL,0,", + "VALUES ('USER','1000001',0,'ACTIVE',0,'[]','[]',0", + "PLATFORM_ADMIN", + "baseline-platform-admin-user-v0", + "baseline-platform-admin-authorization-v0", + "platform:overseas-warehouse:last-mile-order:list", + "platform:overseas-warehouse:inbound-order:list", + "platform:overseas-warehouse:dispatch:list", + "platform:overseas-business:purchase:list", + "platform:overseas-master-data:quote:list", + "platform:finance:fee-source:review") + .doesNotContain("SET @platform_admin_overseas_roles", "UPDATE ll_security_projection", + "baseline-merchant-admin"); assertThat(authSeed).contains("(1000001, 'platform_admin'", "default_tenant_id", "default_app_code"); - assertThat(migration).contains("USE baiyue_lastleg;", "table_schema='baiyue_lastleg'") + assertThat(migration).contains("USE baiyue_lastleg;") .doesNotContain("CREATE DATABASE IF NOT EXISTS baiyue_overseas_security", "USE baiyue_overseas_security;", "CREATE TABLE IF NOT EXISTS overseas_security_event_inbox", "CREATE TABLE IF NOT EXISTS overseas_security_projection"); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/service/ContainerPlanProgressContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/service/ContainerPlanProgressContractTest.java index 6afad5ec2..cb0c2586c 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/service/ContainerPlanProgressContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/service/ContainerPlanProgressContractTest.java @@ -67,9 +67,11 @@ class ContainerPlanProgressContractTest { assertFalse(openApi.contains("platform:overseas-warehouse:order:list")); assertFalse(openApi.contains("platform:overseas-warehouse:order:edit")); assertTrue(openApi.contains("ll_order.estimated_warehouse_at")); - assertTrue(schema.contains("idx_ll_order_container_progress(source_container_booking_id,estimated_warehouse_at,created_at,id)")); - assertTrue(deployment.contains("idx_ll_order_container_progress(source_container_booking_id,estimated_warehouse_at,created_at,id)")); - assertTrue(deployment.contains("assert_container_progress_index_ready")); + assertTrue(schema.replaceAll("\\s+", "").contains( + "idx_ll_order_container_progress(source_container_booking_id,estimated_warehouse_at,created_at,id)")); + assertTrue(deployment.replaceAll("\\s+", "").contains( + "idx_ll_order_container_progress(source_container_booking_id,estimated_warehouse_at,created_at,id)")); + assertFalse(deployment.contains("assert_container_progress_index_ready")); } @Test diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/trucking/UsTruckOrderSchemaContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/trucking/UsTruckOrderSchemaContractTest.java index a3bc9d3d8..b6f698d62 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/trucking/UsTruckOrderSchemaContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/trucking/UsTruckOrderSchemaContractTest.java @@ -30,12 +30,22 @@ class UsTruckOrderSchemaContractTest { } @Test - void unifiedIncrementalContainsOperationLedgerAndHistoryColumns() throws Exception { + void unifiedInitializationContainsOperationLedgerAndHistoryColumns() throws Exception { String sql = Files.readString(Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"), StandardCharsets.UTF_8); - assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_us_truck_order_operation")); - assertTrue(sql.contains("uk_ll_us_truck_operation_key(customer_id,order_id,idempotency_key)")); - assertTrue(sql.contains("table_name='ll_us_truck_order_status_history' AND column_name='operation_type'")); - assertTrue(sql.contains("table_name='ll_us_truck_order_status_history' AND column_name='idempotency_key'")); + String lower = sql.toLowerCase(); + String normalized = lower.replaceAll("\\s+", ""); + assertTrue(lower.contains("create table ll_us_truck_order_operation")); + assertTrue(normalized.contains("uk_ll_us_truck_operation_key(customer_id,order_id,idempotency_key)")); + String history = tableBlock(lower, "ll_us_truck_order_status_history"); + assertTrue(history.contains("operation_type varchar(64)")); + assertTrue(history.contains("idempotency_key varchar(255)")); + org.junit.jupiter.api.Assertions.assertFalse(lower.contains("alter table ll_us_truck_order_status_history")); + } + + private static String tableBlock(String sql, String table) { + int start = sql.indexOf("create table " + table + " ("); + int end = sql.indexOf(';', start); + return start < 0 || end < 0 ? "" : sql.substring(start, end); } } diff --git a/order/src/test/java/com/baiyue/order/controller/DomesticWarehouseOrderExportPermissionSqlContractTest.java b/order/src/test/java/com/baiyue/order/controller/DomesticWarehouseOrderExportPermissionSqlContractTest.java index 1cd1f893b..52bbba672 100644 --- a/order/src/test/java/com/baiyue/order/controller/DomesticWarehouseOrderExportPermissionSqlContractTest.java +++ b/order/src/test/java/com/baiyue/order/controller/DomesticWarehouseOrderExportPermissionSqlContractTest.java @@ -30,8 +30,9 @@ class DomesticWarehouseOrderExportPermissionSqlContractTest { } assertThat(iamSeed).contains("WHERE id IN (5102019, 5102024, 5102025, 5102026, 5102027)"); assertThat(unified) - .contains("WHERE id IN (5102019, 5102024, 5102025, 5102026, 5102027)") - .contains("管理员或订单运营角色缺少订单导出与下载中心权限"); + .contains("VALUES (4000001,5102026,0,CURRENT_TIMESTAMP(3))") + .contains("VALUES (4000003,5102026,0,CURRENT_TIMESTAMP(3))") + .doesNotContain("管理员或订单运营角色缺少订单导出与下载中心权限"); } private static Path resolve(String relative) { diff --git a/order/src/test/java/com/baiyue/order/controller/OrderDeclarationUpdateContractTest.java b/order/src/test/java/com/baiyue/order/controller/OrderDeclarationUpdateContractTest.java index df1f9e575..10b822081 100644 --- a/order/src/test/java/com/baiyue/order/controller/OrderDeclarationUpdateContractTest.java +++ b/order/src/test/java/com/baiyue/order/controller/OrderDeclarationUpdateContractTest.java @@ -99,13 +99,12 @@ class OrderDeclarationUpdateContractTest { Path iam = Files.exists(iamDirect) ? iamDirect : Path.of("..").resolve(iamDirect); String iamSource = Files.readString(iam, StandardCharsets.UTF_8); - assertThat(source) - .contains("expected_arrival_at DATETIME(3) NULL COMMENT '客户申报预计到货时间'") - .contains("channel_changed TINYINT(1) NOT NULL DEFAULT 0 COMMENT '订单历史上是否由平台变更过渠道'") - .contains("CALL unified_assert_column('baiyue_order', 'ord_order', 'expected_arrival_at')") - .contains("CALL unified_assert_column('baiyue_order', 'ord_order', 'channel_changed')") + assertThat(source.toLowerCase()) + .contains("expected_arrival_at datetime(3) default null comment '客户申报预计到货时间'") + .contains("channel_changed tinyint(1) not null default '0' comment '订单历史上是否由平台变更过渠道'") .contains("platform:order:declaration:edit") - .contains("WHERE id IN (5102019, 5102024, 5102025, 5102026, 5102027)"); + .contains("values (4000001,5102027,0,current_timestamp(3))") + .contains("values (4000003,5102027,0,current_timestamp(3))"); assertThat(iamSource) .contains("platform:order:declaration:edit") .contains("WHERE id IN (5102019, 5102024, 5102025, 5102026, 5102027)"); diff --git a/order/src/test/java/com/baiyue/order/repository/InboundCodeSequenceSqlContractTest.java b/order/src/test/java/com/baiyue/order/repository/InboundCodeSequenceSqlContractTest.java index 2daa67983..25d654e70 100644 --- a/order/src/test/java/com/baiyue/order/repository/InboundCodeSequenceSqlContractTest.java +++ b/order/src/test/java/com/baiyue/order/repository/InboundCodeSequenceSqlContractTest.java @@ -38,12 +38,15 @@ class InboundCodeSequenceSqlContractTest { String baseline = new ClassPathResource("db/order.sql").getContentAsString(StandardCharsets.UTF_8); String unified = Files.readString(resolveUnified(), StandardCharsets.UTF_8); + assertThat(baseline) + .contains("CREATE TABLE IF NOT EXISTS ord_inbound_code_daily_sequence"); for (String sql : java.util.List.of(baseline, unified)) { - assertThat(sql) - .contains("CREATE TABLE IF NOT EXISTS ord_inbound_code_daily_sequence") - .contains("business_date DATE NOT NULL") - .contains("current_value BIGINT UNSIGNED NOT NULL") - .contains("PRIMARY KEY (business_date)") + assertThat(sql.toLowerCase()) + .contains("create table") + .contains("ord_inbound_code_daily_sequence") + .contains("business_date date not null") + .contains("current_value bigint unsigned not null") + .contains("primary key (business_date)") .contains("按上海日期重新从1开始"); } } diff --git a/order/src/test/java/com/baiyue/order/repository/OrderParentChildSqlContractTest.java b/order/src/test/java/com/baiyue/order/repository/OrderParentChildSqlContractTest.java index 73b22f290..913129806 100644 --- a/order/src/test/java/com/baiyue/order/repository/OrderParentChildSqlContractTest.java +++ b/order/src/test/java/com/baiyue/order/repository/OrderParentChildSqlContractTest.java @@ -47,19 +47,22 @@ class OrderParentChildSqlContractTest { } @Test - void unifiedEntryContainsTheExactOrderBlockBeforeFirstleg() throws Exception { + void unifiedEntryContainsTheFinalOrderParentChildSchemaAndTriggers() throws Exception { String unified = read(resolve("deploy/sql/头程数据库统一增量-MySQL8.sql")); - String module = read(resolveModule()).trim(); - String marker = "-- 来源:order/src/main/resources/db/phase3-order-parent-child-schema.sql"; - String next = "-- 来源:firstleg/src/main/resources/db/phase3-inbound-parent-child-schema.sql"; - int start = unified.indexOf(marker); - int end = unified.indexOf(next, start + marker.length()); - assertThat(start).isGreaterThanOrEqualTo(0); - assertThat(end).isGreaterThan(start); - assertThat(unified.substring(start + marker.length(), end).trim()).startsWith(module); - assertThat(unified.lastIndexOf("SELECT 'PASS' AS migration_status")) - .isGreaterThan(end); + assertThat(unified) + .contains("CREATE TABLE ord_order_fulfillment_split") + .contains("source_order_id bigint unsigned NOT NULL") + .contains("request_hash char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL") + .contains("UNIQUE KEY uk_order_split_child_role (tenant_id,child_order_id,split_role)") + .contains("KEY idx_order_split_source (tenant_id,source_order_id,split_role)") + .contains("CREATE TRIGGER trg_order_fulfillment_split_insert") + .contains("NEW.split_role='LOADED' AND NEW.child_order_id<>NEW.source_order_id") + .contains("CREATE TRIGGER trg_order_fulfillment_split_update") + .contains("订单履约分片来源、子片与角色不可修改") + .contains("SELECT 'PASS' AS initialization_status, 'CN' AS region") + .doesNotContain("phase3_order_add_column") + .doesNotContain("phase3_order_add_index"); } private static Path resolve(String path) { diff --git a/order/src/test/java/com/baiyue/order/repository/OrderPickupSqlContractTest.java b/order/src/test/java/com/baiyue/order/repository/OrderPickupSqlContractTest.java index 88ada5c72..bcf54d795 100644 --- a/order/src/test/java/com/baiyue/order/repository/OrderPickupSqlContractTest.java +++ b/order/src/test/java/com/baiyue/order/repository/OrderPickupSqlContractTest.java @@ -20,18 +20,17 @@ class OrderPickupSqlContractTest { } @Test - void unifiedScriptRelaxesInboundCodeBeforeClearingDraftsAndAddsPickupColumn() throws Exception { + void unifiedScriptCreatesTheFinalPickupColumnsWithoutMigrationSteps() throws Exception { Path direct = Path.of("deploy/sql/头程数据库统一增量-MySQL8.sql"); Path path = Files.exists(direct) ? direct : Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql"); - String sql = Files.readString(path); - int relax = sql.indexOf("ALTER TABLE baiyue_order.ord_order MODIFY COLUMN inbound_code VARCHAR(40) NULL"); - int clearDraft = sql.indexOf("SET inbound_code = NULL"); - assertThat(relax).isGreaterThanOrEqualTo(0).isLessThan(clearDraft); + String sql = Files.readString(path).toLowerCase(); assertThat(sql).contains( - "column_name = 'requested_pickup_at'", - "ALTER TABLE baiyue_order.ord_order ADD COLUMN requested_pickup_at DATETIME(3) NULL", - "CREATE PROCEDURE assert_submitted_order_inbound_code()", - "SIGNAL SQLSTATE '45000'"); - assertThat(sql).doesNotContain("CONCAT('RK-', o.id)"); + "create table ord_order", + "inbound_code varchar(40) default null", + "requested_pickup_at datetime(3) default null"); + assertThat(sql) + .doesNotContain("alter table baiyue_order.ord_order modify column inbound_code") + .doesNotContain("set inbound_code = null") + .doesNotContain("concat('rk-', o.id)"); } } diff --git a/warehouse/src/test/java/com/baiyue/warehouse/repository/InitialWarehouseResourceSeedSqlContractTest.java b/warehouse/src/test/java/com/baiyue/warehouse/repository/InitialWarehouseResourceSeedSqlContractTest.java index e2dd761f1..83015c4fb 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/repository/InitialWarehouseResourceSeedSqlContractTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/repository/InitialWarehouseResourceSeedSqlContractTest.java @@ -13,8 +13,14 @@ class InitialWarehouseResourceSeedSqlContractTest { void initializesReusablePalletsAndYiwuLocationGrid() throws Exception { assertModuleSeed(Files.readString(Path.of("src", "main", "resources", "db", "phase2-pallet-seed.sql"), StandardCharsets.UTF_8)); - assertUnifiedSeed(Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"), - StandardCharsets.UTF_8)); + String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"), + StandardCharsets.UTF_8); + assertThat(unified) + .contains("CREATE TABLE wh_pallet (") + .contains("CREATE TABLE wh_location (") + .doesNotContain("assert_yiwu_location_seed") + .doesNotContain("INSERT INTO wh_location_reservation") + .doesNotContain("INSERT INTO wh_location_occupancy"); } private static void assertModuleSeed(String sql) { @@ -22,10 +28,6 @@ class InitialWarehouseResourceSeedSqlContractTest { assertThat(sql).doesNotContain("color,status,remark"); } - private static void assertUnifiedSeed(String sql) { - assertSeed(sql, "193000000000200001", "193000000000200002", "193000000000200"); - } - private static void assertSeed(String sql, String yiwuId, String dongguanId, String idPrefix) { String compactSql = sql.replaceAll("\\s+", ""); int locationSeedStart = sql.indexOf("WHEN zones.zone_index = 0 AND slots.slot_number = 1 THEN 201"); diff --git a/warehouse/src/test/java/com/baiyue/warehouse/repository/OverseasStorageFeeSqlContractTest.java b/warehouse/src/test/java/com/baiyue/warehouse/repository/OverseasStorageFeeSqlContractTest.java index 3d5af43e0..4d33a320d 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/repository/OverseasStorageFeeSqlContractTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/repository/OverseasStorageFeeSqlContractTest.java @@ -14,32 +14,15 @@ class OverseasStorageFeeSqlContractTest { String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql")); assertThat(sql) - .contains("CREATE TABLE IF NOT EXISTS wh_overseas_storage_fee_rule") - .contains("CREATE TABLE IF NOT EXISTS wh_overseas_storage_fee_tier") - .containsOnlyOnce("CREATE PROCEDURE migrate_overseas_storage_fee_schema()") - .doesNotContain("ADD COLUMN IF NOT EXISTS initialization_key") - .doesNotContain("ADD COLUMN IF NOT EXISTS fee_type") - .doesNotContain("ADD COLUMN IF NOT EXISTS billing_unit") - .contains("table_name = 'wh_overseas_storage_fee_rule'") - .contains("column_name = 'initialization_key'") - .contains("column_name = 'fee_type'") - .contains("column_name = 'billing_unit'") - .contains("PREPARE overseas_fee_statement FROM @overseas_fee_ddl") - .contains("EXECUTE overseas_fee_statement") - .contains("DEALLOCATE PREPARE overseas_fee_statement") - .contains("constraint_name = 'chk_overseas_fee_tier_days'") - .contains("constraint_name = 'chk_overseas_fee_tier_price'") - .contains("index_columns = 'rule_id,fee_type,active_start_day'") - .contains("Warehouse 迁移失败:海外仓费用阶梯存在非法天数或负价格") - .contains("Warehouse 迁移失败:海外仓费用阶梯目标唯一键存在重复数据") - .contains("CALL migrate_overseas_storage_fee_schema()") - .contains("LOWER(column_type) = 'varchar(191)'") - .contains("LOWER(column_type) = 'varchar(32)'") - .contains("LOWER(column_type) = 'varchar(16)'") - .contains("LOWER(column_type) = 'smallint unsigned'") - .contains("LOWER(column_type) = 'decimal(18,6)'") - .contains("统一迁移失败:海外仓阶梯费用字段签名不正确") - .contains("统一迁移失败:海外仓阶梯费用唯一键或 CHECK 结构不正确") + .contains("CREATE TABLE wh_overseas_storage_fee_rule") + .contains("CREATE TABLE wh_overseas_storage_fee_tier") + .contains("initialization_key varchar(191) DEFAULT NULL") + .contains("fee_type varchar(32) NOT NULL DEFAULT 'STORAGE'") + .contains("billing_unit varchar(16) NOT NULL DEFAULT 'CBM'") + .contains("start_day smallint unsigned NOT NULL") + .contains("unit_price decimal(18,6) NOT NULL") + .contains("CONSTRAINT chk_overseas_fee_tier_days CHECK") + .contains("CONSTRAINT chk_overseas_fee_tier_price CHECK") .contains("uk_overseas_fee_active_customer") .contains("idx_overseas_fee_page") .contains("idx_overseas_fee_customer_name") @@ -49,14 +32,7 @@ class OverseasStorageFeeSqlContractTest { .contains("platform:overseas-storage-fee:create") .contains("platform:overseas-storage-fee:edit") .contains("platform:overseas-storage-fee:delete") - .contains("CALL unified_assert_overseas_storage_fee()") - .contains("统一增量全部执行完成"); - - int addColumns = sql.indexOf("column_name = 'fee_type'"); - int migrate = sql.indexOf("CALL migrate_overseas_storage_fee_schema()"); - int assertSchema = sql.indexOf("CALL unified_assert_overseas_storage_fee()"); - assertThat(addColumns).isGreaterThan(0); - assertThat(migrate).isGreaterThan(addColumns); - assertThat(assertSchema).isGreaterThan(migrate); + .doesNotContain("migrate_overseas_storage_fee_schema") + .doesNotContain("unified_assert_overseas_storage_fee"); } } diff --git a/warehouse/src/test/java/com/baiyue/warehouse/repository/PalletLocationReleaseIdempotencySqlContractTest.java b/warehouse/src/test/java/com/baiyue/warehouse/repository/PalletLocationReleaseIdempotencySqlContractTest.java index 26110ab4d..f8a333fd1 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/repository/PalletLocationReleaseIdempotencySqlContractTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/repository/PalletLocationReleaseIdempotencySqlContractTest.java @@ -17,13 +17,15 @@ class PalletLocationReleaseIdempotencySqlContractTest { "头程数据库统一增量-MySQL8.sql")); for (String sql : new String[] {moduleSql, unifiedSql}) { - assertThat(sql) - .contains("CREATE TABLE IF NOT EXISTS wh_pallet_location_release_operation") - .contains("idempotency_key VARCHAR(128) NOT NULL") - .contains("request_hash CHAR(64) NOT NULL") - .contains("response_snapshot JSON NULL") + String normalized = sql.toLowerCase().replace(" default null", " null").replace(", ", ","); + assertThat(normalized) + .contains("create table ") + .contains("wh_pallet_location_release_operation") + .contains("idempotency_key varchar(128) not null") + .contains("request_hash char(64) not null") + .contains("response_snapshot json null") .contains("uk_pallet_location_release_idempotency") - .contains("tenant_id, source_service, business_id, idempotency_key"); + .contains("tenant_id,source_service,business_id,idempotency_key"); } } @@ -35,14 +37,17 @@ class PalletLocationReleaseIdempotencySqlContractTest { "头程数据库统一增量-MySQL8.sql")); for (String sql : new String[] {moduleSql, unifiedSql}) { - assertThat(sql) - .contains("CREATE TABLE IF NOT EXISTS wh_pallet_location_settlement_operation") - .contains("source_business_id BIGINT UNSIGNED NOT NULL") - .contains("target_business_id BIGINT UNSIGNED NOT NULL") - .contains("response_snapshot JSON NULL") + String normalized = sql.toLowerCase().replace(" default null", " null").replace(", ", ","); + assertThat(normalized) + .contains("create table ") + .contains("wh_pallet_location_settlement_operation") + .contains("source_business_id bigint unsigned not null") + .contains("target_business_id bigint unsigned not null") + .contains("response_snapshot json null") .contains("uk_pallet_location_settlement_idempotency") - .contains("tenant_id, source_service, source_business_id, idempotency_key") - .contains("DROP INDEX uk_pallet_location_settlement_idempotency"); + .contains("tenant_id,source_service,source_business_id,idempotency_key"); } + assertThat(moduleSql).contains("DROP INDEX uk_pallet_location_settlement_idempotency"); + assertThat(unifiedSql).doesNotContain("DROP INDEX uk_pallet_location_settlement_idempotency"); } } diff --git a/warehouse/src/test/java/com/baiyue/warehouse/repository/SupplierSqlContractTest.java b/warehouse/src/test/java/com/baiyue/warehouse/repository/SupplierSqlContractTest.java index ba704fb3b..e3d5b9481 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/repository/SupplierSqlContractTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/repository/SupplierSqlContractTest.java @@ -29,11 +29,13 @@ class SupplierSqlContractTest { @Test void unifiedMigrationContainsSupplierSchemaAndPermissionContract() throws IOException { String sql = Files.readString(resolveUnifiedSql(), StandardCharsets.UTF_8); - assertThat(sql).contains("CREATE TABLE IF NOT EXISTS wh_supplier", "CREATE TABLE IF NOT EXISTS wh_supplier_carrier", + String normalized = sql.replace("PRIMARY KEY (", "PRIMARY KEY("); + assertThat(normalized).contains("CREATE TABLE wh_supplier", "CREATE TABLE wh_supplier_carrier", "PRIMARY KEY(supplier_id,carrier_code)", "idx_wh_supplier_carrier_code", "uk_wh_supplier_name", "idx_wh_supplier_filter", "platform:supplier:list", "platform:supplier:create", - "platform:supplier:edit", "platform:supplier:status", "assert_platform_supplier_permissions"); - String supplierSection = sql.substring(sql.indexOf("-- 平台公共供应商业务档案及权限")); + "platform:supplier:edit", "platform:supplier:status") + .doesNotContain("assert_platform_supplier_permissions"); + String supplierSection = sql.substring(sql.indexOf("CREATE TABLE wh_supplier")); assertThat(supplierSection).doesNotContain("chk_wh_supplier_scope", "订舱分类范围"); } diff --git a/warehouse/src/test/java/com/baiyue/warehouse/repository/WarehouseSchemaContractTest.java b/warehouse/src/test/java/com/baiyue/warehouse/repository/WarehouseSchemaContractTest.java index 238c26a13..fde0613e6 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/repository/WarehouseSchemaContractTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/repository/WarehouseSchemaContractTest.java @@ -130,43 +130,40 @@ class WarehouseSchemaContractTest { .contains("193000000000400003,0,193000000000200003,'RECEIVING','海外收货',1") .contains("'洛杉矶仓海外接柜入仓收货能力'") .contains("193000000000400004,0,193000000000200004,'RECEIVING','海外收货',1") - .contains("'纽约仓海外接柜入仓收货能力'") - .contains("固定平台作业仓收货能力写入校验失败") - .contains("warehouse.warehouse_type = 'FBA'") - .contains("FBA公共地址不得创建平台作业能力"); + .contains("'纽约仓海外接柜入仓收货能力'"); } } @Test void unifiedSqlShouldConvergePalletsToReusableMasterData() throws IOException { String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql")); - String palletDefinition = tableDefinition(unified, "wh_pallet"); + String palletDefinition = tableDefinition(unified.toLowerCase(), "wh_pallet").replace(",", ", "); assertThat(palletDefinition) .contains("idx_pallet_warehouse (tenant_id, warehouse_id, deleted, pallet_code)") .contains("idx_pallet_type (tenant_id, warehouse_id, pallet_type, capacity_cbm, deleted)") - .doesNotContain("status VARCHAR") + .doesNotContain("status varchar") .doesNotContain("idx_pallet_available"); assertThat(unified) - .contains("ALTER TABLE wh_pallet DROP COLUMN status") - .contains("ALTER TABLE wh_location_reservation DROP COLUMN active_pallet_reservation") - .contains("ALTER TABLE wh_location_occupancy DROP COLUMN active_pallet_occupancy") - .contains("托盘动态资源状态或活动唯一约束仍存在") - .contains("库位活动预占或占用唯一约束不完整"); + .doesNotContain("ALTER TABLE wh_pallet DROP COLUMN status") + .doesNotContain("active_pallet_reservation") + .doesNotContain("active_pallet_occupancy"); } @Test void unifiedSqlUsesOnlyNonSupplierPartnerTypes() throws IOException { String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql")); - assertThat(tableDefinition(unified, "wh_partner")) - .contains("chk_wh_partner_type CHECK (partner_type IN ('CARRIER','SHIPPER','CONSIGNEE'))") - .contains("carrier_code VARCHAR(16) NULL") - .contains("chk_wh_partner_carrier_code CHECK (carrier_code IS NULL OR carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER'))") - .doesNotContain("CUSTOMS_BROKER", "SUPPLIER或BOTH"); + String partner = tableDefinition(unified.toLowerCase(), "wh_partner").replace("_utf8mb4", ""); + assertThat(partner) + .contains("chk_wh_partner_type check") + .contains("partner_type in ('carrier','shipper','consignee')") + .contains("carrier_code varchar(16) default null") + .contains("chk_wh_partner_carrier_code check") + .contains("carrier_code in ('matson','sml','zim','cosco','emc','hede','other')") + .doesNotContain("customs_broker", "supplier或both"); assertThat(unified) - .contains("CALL add_column_if_missing('wh_partner', 'carrier_code', 'VARCHAR(16) NULL") - .contains("UPDATE wh_partner SET carrier_code = 'OTHER'") - .contains("queue_column_comment('baiyue_warehouse', 'wh_partner', 'carrier_code'"); + .doesNotContain("CALL add_column_if_missing('wh_partner'") + .doesNotContain("queue_column_comment('baiyue_warehouse', 'wh_partner', 'carrier_code'"); } private static String read(String file) throws IOException { @@ -192,8 +189,14 @@ class WarehouseSchemaContractTest { } private static String tableDefinition(String sql, String table) { - int start = sql.indexOf("CREATE TABLE IF NOT EXISTS " + table + " ("); + String marker = sql.contains("CREATE TABLE IF NOT EXISTS " + table + " (") + ? "CREATE TABLE IF NOT EXISTS " + table + " (" + : "create table " + table + " ("; + int start = sql.indexOf(marker); int end = sql.indexOf("ENGINE=InnoDB", start); + if (end < 0) { + end = sql.indexOf("engine=innodb", start); + } return sql.substring(start, end); } }