import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' const sql = readFileSync(new URL('../../deploy/sql/头程数据库统一增量-MySQL8.sql', import.meta.url), 'utf8') const overseasSql = readFileSync(new URL('../../deploy/sql/海外区域统一增量-MySQL8.sql', import.meta.url), 'utf8') const lastlegBaseline = readFileSync(new URL('../../lastleg-service/src/main/resources/db/lastleg.sql', import.meta.url), 'utf8') const lastlegPermissionSeed = readFileSync(new URL('../../iam/src/main/resources/db/lastleg-permissions.sql', import.meta.url), 'utf8') const phase5DeliveryPermissionSeed = readFileSync(new URL('../../iam/src/main/resources/db/phase5-delivery-permissions.sql', import.meta.url), 'utf8') const overseasWarehousePermissions = readFileSync(new URL('../../../apps/admin-web/src/modules/overseas-warehouse/permissions.ts', import.meta.url), 'utf8') const overseasBusinessPermissions = readFileSync(new URL('../../../apps/admin-web/src/modules/overseas-business/permissions.ts', import.meta.url), 'utf8') const overseasMasterDataPermissions = readFileSync(new URL('../../../apps/admin-web/src/modules/overseas-master-data/permissions.ts', import.meta.url), 'utf8') const iamSeed = readFileSync(new URL('../../iam/src/main/resources/db/phase1-admin-rbac.sql', import.meta.url), 'utf8') const oceanBookingSchema = readFileSync(new URL('../../firstleg/src/main/resources/db/phase3-ocean-booking-schema.sql', import.meta.url), 'utf8') const truckAppointmentSchema = readFileSync(new URL('../../firstleg/src/main/resources/db/phase3-truck-appointment-schema.sql', import.meta.url), 'utf8') test('统一初始化 SQL 只保留平台管理员且不创建固定客户', () => { assert.match(sql, /最小登录基线只保留平台系统管理员/) assert.equal((sql.match(/'platform_admin'/g) || []).length, 1) assert.doesNotMatch(sql, /'merchant_admin'|'IT_TEST'|'IT 测试客户'/) assert.doesNotMatch(iamSeed, /'IT_TEST'|'IT 测试客户'|\(10001,/) const accountSeed = sql.match(/INSERT INTO auth_account \([\s\S]*?deleted = 0;/)?.[0] assert.ok(accountSeed, 'missing auth account seed') assert.doesNotMatch(accountSeed, /ON DUPLICATE KEY UPDATE[\s\S]*?password_hash\s*=/, 're-running the unified SQL must not reset an existing account password') }) test('统一初始化直接提供tenant0商户角色与权限模板', () => { assert.match(sql, /4000100, 0, 'MERCHANT', 'MERCHANT_OWNER'/) assert.match(sql, /5300000, 0, 'MERCHANT', 'MENU',[\s\S]*?'merchant:enterprise'/) assert.match(sql, /5301024, 0, 'MERCHANT', 'BUTTON',[\s\S]*?'merchant:order:quick-create'/) assert.match(sql, /5305000, 0, 'MERCHANT', 'MENU',[\s\S]*?'merchant:finance:statement'/) assert.match(sql, /r\.id=4000100[\s\S]*?客户全局管理员 Finance 模板授权不完整/) }) test('统一初始化 SQL 提供人工验收所需公共仓储主数据', () => { for (const marker of [ "'TP-001'", "'TP-002'", "'A1 普通库位'", "'A0 共享溢出位'", "'CAR-001'", "'CARRIER'", "'SHIPPER'", "'EXP-TEST'", "'IT 测试快递'", ]) assert.ok(sql.includes(marker), `missing initialization marker: ${marker}`) assert.match(sql, /'EXP-TEST', 'IT 测试快递'[\s\S]*?'ACTIVE'/) assert.match(sql, /193000000000400003,0,193000000000200003,'RECEIVING','海外收货',1,'16:00:00',[\s\S]*?'洛杉矶仓海外接柜入仓收货能力'/) assert.match(sql, /193000000000400004,0,193000000000200004,'RECEIVING','海外收货',1,'16:00:00',[\s\S]*?'纽约仓海外接柜入仓收货能力'/) assert.match(sql, /固定平台作业仓收货能力写入校验失败/) const palletSeed = sql.match(/INSERT INTO wh_pallet \([\s\S]*?deleted = 0;/)?.[0] assert.ok(palletSeed, 'missing pallet initialization seed') assert.doesNotMatch(palletSeed, /\bstatus\b|'AVAILABLE'/) }) test('国内统一初始化支持真实空库且不越界创建海外业务库', () => { assert.doesNotMatch(sql, /CREATE DATABASE IF NOT EXISTS baiyue_lastleg|USE baiyue_lastleg;/) assert.match(sql, /IF EXISTS \(SELECT 1 FROM information_schema\.tables WHERE table_schema=DATABASE\(\)[\s\S]*?table_name='fl_ocean_booking'\) THEN[\s\S]*?UPDATE fl_container_booking container_booking[\s\S]*?JOIN fl_ocean_booking ocean_booking/) assert.ok( sql.indexOf("table_name='fl_ocean_booking') THEN") < sql.indexOf('CREATE TABLE IF NOT EXISTS fl_ocean_booking ('), 'empty-database backfill must be guarded before the ocean-booking baseline is created', ) }) test('国内统一初始化在注释门禁前创建订单申报快照表', () => { for (const table of ['fl_order_declaration_snapshot', 'fl_order_declaration_event']) { const createIndex = sql.indexOf(`CREATE TABLE IF NOT EXISTS ${table} (`) const commentIndex = sql.indexOf(`CALL queue_table_comment('baiyue_firstleg', '${table}'`) assert.ok(createIndex >= 0, `missing domestic baseline table: ${table}`) assert.ok(commentIndex >= 0, `missing domestic table comment gate: ${table}`) assert.ok(createIndex < commentIndex, `${table} must exist before the table-comment gate runs`) } }) test('国内统一增量在 Firstleg 补列前重建已由 Warehouse 段删除的过程', () => { const warehouseDrop = sql.indexOf('DROP PROCEDURE IF EXISTS add_column_if_missing;', sql.indexOf('CALL assert_platform_public_resource_scope();')) const firstlegCall = sql.indexOf("CALL add_column_if_missing('fl_price_book_version', 'voided_by'") const firstlegCreate = sql.lastIndexOf('CREATE PROCEDURE add_column_if_missing(', firstlegCall) const firstlegDrop = sql.indexOf('DROP PROCEDURE IF EXISTS add_column_if_missing;', firstlegCall) assert.ok(warehouseDrop >= 0, 'Warehouse migration must clean up its helper procedure') assert.ok(firstlegCreate > warehouseDrop, 'Firstleg must recreate the helper after the Warehouse cleanup') assert.ok(firstlegCreate < firstlegCall, 'Firstleg helper must exist before the price-version ALTER calls') assert.ok(firstlegDrop > firstlegCall, 'Firstleg must clean up the helper after its ALTER calls') }) test('海外统一初始化内联空库基线且不依赖模块 SQL', () => { assert.match(overseasSql, /BEGIN INLINE EMPTY-DATABASE BASELINE: LASTLEG/) assert.match(overseasSql, /BEGIN INLINE EMPTY-DATABASE BASELINE: OVERSEAS WAREHOUSE/) assert.match(overseasSql, /CREATE DATABASE IF NOT EXISTS baiyue_lastleg/) assert.match(overseasSql, /CREATE DATABASE IF NOT EXISTS baiyue_lastlag_warehouse/) assert.match(overseasSql, /CREATE TABLE IF NOT EXISTS ll_order \(/) assert.match(overseasSql, /CREATE TABLE IF NOT EXISTS wh_location \(/) assert.ok( overseasSql.indexOf('CREATE TABLE IF NOT EXISTS ll_order (') < overseasSql.indexOf('CREATE TABLE IF NOT EXISTS ll_order_source_pallet ('), 'll_order must exist before the source-pallet foreign key is created', ) assert.ok( overseasSql.indexOf('CREATE DATABASE IF NOT EXISTS baiyue_lastlag_warehouse') < overseasSql.indexOf('CREATE TABLE IF NOT EXISTS wh_location ('), 'the overseas warehouse schema must be selected before its baseline tables are created', ) }) test('海外空库基线为固定平台管理员提供完整version0授权投影', () => { const authorityBlock = lastlegBaseline.match( /SET @platform_admin_overseas_authorities = JSON_ARRAY\(([\s\S]*?)\n\);/, )?.[1] assert.ok(authorityBlock, 'missing platform_admin overseas authority snapshot') const authorities = [...authorityBlock.matchAll(/'([^']+)'/g)].map((match) => match[1]) const deferred = new Set([ 'platform:overseas-warehouse:carton:list', 'platform:overseas-warehouse:carton:create', 'platform:overseas-warehouse:carton:edit', 'platform:overseas-warehouse:product:list', 'platform:overseas-warehouse:product:create', 'platform:overseas-warehouse:product:edit', ]) assert.equal(authorities.length, 106) assert.equal(new Set(authorities).size, authorities.length, 'baseline authorities must not contain duplicates') assert.equal(authorities.filter((item) => item.startsWith('platform:finance:')).length, 34) assert.equal(authorities.filter((item) => item.startsWith('platform:overseas-business:')).length, 11) assert.equal(authorities.filter((item) => item.startsWith('platform:overseas-master-data:')).length, 15) assert.equal(authorities.filter((item) => item.startsWith('platform:overseas-warehouse:')).length, 46) for (const authority of authorities) { assert.ok(sql.includes(`'${authority}'`), `authority is absent from the domestic IAM baseline: ${authority}`) } for (const source of [overseasWarehousePermissions, overseasBusinessPermissions, overseasMasterDataPermissions]) { const declared = [...source.matchAll(/'((?:platform:overseas-warehouse|platform:overseas-business|platform:overseas-master-data):[^']+)'/g)] .map((match) => match[1]).filter((authority) => !deferred.has(authority)) for (const authority of declared) { assert.ok( [lastlegPermissionSeed, phase5DeliveryPermissionSeed].some((seed) => seed.includes(`'${authority}'`)), `Admin permission is absent from the IAM source seed: ${authority}`, ) assert.ok(authorities.includes(authority), `Admin permission is absent from the fixed-admin overseas snapshot: ${authority}`) } } for (const authority of deferred) { assert.ok(!lastlegPermissionSeed.includes(`'${authority}'`), `deferred permission entered the IAM source seed: ${authority}`) assert.ok(!authorities.includes(authority), `deferred permission entered the fixed-admin snapshot: ${authority}`) } for (const required of [ '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', ]) assert.ok(authorities.includes(required), `missing fixed-admin overseas authority: ${required}`) assert.match(lastlegBaseline, /SET @platform_admin_overseas_roles = JSON_ARRAY\('PLATFORM_ADMIN'\)/) assert.doesNotMatch(lastlegBaseline, /\('AUTHORIZATION','1000001',0,NULL,0,JSON_ARRAY\(\),JSON_ARRAY\(\)/) assert.doesNotMatch(overseasSql, /\('AUTHORIZATION','1000001',0,NULL,0,JSON_ARRAY\(\),JSON_ARRAY\(\)/) const inline = overseasSql.match( /-- BEGIN INLINE EMPTY-DATABASE BASELINE: LASTLEG\r?\n([\s\S]*?)\r?\n\r?\n-- END INLINE EMPTY-DATABASE BASELINE: LASTLEG/, )?.[1] assert.ok(inline, 'missing inline Lastleg baseline') assert.equal(inline.replace(/\r\n/g, '\n').trim(), lastlegBaseline.replace(/\r\n/g, '\n').trim()) }) test('国内统一初始化补齐入库费用人工调整基线字段', () => { assert.match(sql, /baseline_chargeable_quantity DECIMAL\(18,6\) NOT NULL/) assert.match(sql, /SET baseline_chargeable_quantity = chargeable_quantity[\s\S]*?WHERE baseline_chargeable_quantity IS NULL/) assert.match(sql, /tolerance_percent DECIMAL\(5,2\) NOT NULL DEFAULT 10\.00/) assert.match(sql, /unified_assert_column\('baiyue_firstleg', 'fl_inbound_fee', 'baseline_chargeable_quantity'\)/) assert.match(sql, /unified_assert_column\('baiyue_firstleg', 'fl_inbound_fee', 'tolerance_percent'\)/) }) test('三期权限迁移删除退役权限并只保留 canonical 权限', () => { for (const source of [sql, oceanBookingSchema]) { assert.match(source, /DELETE FROM iam_permission WHERE id IN \(5102141,5102143,5102144,5102145\)/) assert.match(source, /5102147[\s\S]*?platform:firstleg:ocean-booking:edit/) assert.match(source, /5102148[\s\S]*?platform:firstleg:ocean-booking:delete/) assert.doesNotMatch(source, /platform:firstleg:ocean-booking:(view|cancel|change)/) assert.ok( source.indexOf('DELETE FROM iam_permission WHERE id IN (5102141,5102143,5102144,5102145)') < source.indexOf('(5102147,0,\'PLATFORM\',\'BUTTON\',5102140,\'platform:firstleg:ocean-booking:edit\''), 'legacy ocean-booking edit permission must be removed before assigning the code to 5102147', ) } for (const source of [sql, truckAppointmentSchema]) { assert.match(source, /DELETE FROM iam_permission WHERE id=5102151/) assert.doesNotMatch(source, /platform:firstleg:truck-appointment:view[',]/) } assert.match(sql, /status='ACTIVE' AND deleted=0[\s\S]*?id BETWEEN 5102140 AND 5102149/) assert.match(sql, /status='ACTIVE' AND deleted=0[\s\S]*?id BETWEEN 5102150 AND 5102154/) }) test('海外仓物理托盘初始化与模块基线一致', () => { const baseline = readFileSync(new URL('../../warehouse-overseas-service/src/main/resources/db/warehouse.sql', import.meta.url), 'utf8') const inline = overseasSql.match(/-- BEGIN INLINE EMPTY-DATABASE BASELINE: OVERSEAS WAREHOUSE\r?\n([\s\S]*?)\r?\n\r?\n-- END INLINE EMPTY-DATABASE BASELINE: OVERSEAS WAREHOUSE/)?.[1] assert.ok(inline) assert.equal(inline.slice(inline.indexOf('-- This script is run')).replace(/\r\n/g, '\n').trim(), baseline.replace(/\r\n/g, '\n').trim()) })