188 lines
9.3 KiB
JavaScript
188 lines
9.3 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const backendRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
const repoRoot = dirname(backendRoot);
|
|
const read = (path) => readFileSync(join(repoRoot, path), 'utf8');
|
|
|
|
const upSql = read('database/migrations/2026061601_m01b_core_schema.up.sql');
|
|
const downSql = read('database/migrations/2026061601_m01b_core_schema.down.sql');
|
|
const verifySql = read('database/migrations/2026061601_m01b_core_schema.verify.sql');
|
|
const seedSql = read('database/seeds/2026061601_m01b_minimal_seed.sql');
|
|
const legacyFixtureSql = read('database/fixtures/2026061801_m01b_legacy_schema.sql');
|
|
const asyncUpSql = read('database/migrations/2026061802_m01c_async_tasks.up.sql');
|
|
const asyncDownSql = read('database/migrations/2026061802_m01c_async_tasks.down.sql');
|
|
const asyncVerifySql = read('database/migrations/2026061802_m01c_async_tasks.verify.sql');
|
|
const tenantAppsUpSql = read('database/migrations/2026061803_m02a_tenant_apps.up.sql');
|
|
const tenantAppsDownSql = read('database/migrations/2026061803_m02a_tenant_apps.down.sql');
|
|
const tenantAppsVerifySql = read('database/migrations/2026061803_m02a_tenant_apps.verify.sql');
|
|
const authUpSql = read('database/migrations/2026061804_m02b_wechat_auth.up.sql');
|
|
const authDownSql = read('database/migrations/2026061804_m02b_wechat_auth.down.sql');
|
|
const authVerifySql = read('database/migrations/2026061804_m02b_wechat_auth.verify.sql');
|
|
const rbacUpSql = read('database/migrations/2026061805_m02c_rbac.up.sql');
|
|
const rbacDownSql = read('database/migrations/2026061805_m02c_rbac.down.sql');
|
|
const rbacVerifySql = read('database/migrations/2026061805_m02c_rbac.verify.sql');
|
|
const userManagementUpSql = read('database/migrations/2026061806_m02d_user_management.up.sql');
|
|
const userManagementDownSql = read('database/migrations/2026061806_m02d_user_management.down.sql');
|
|
const userManagementVerifySql = read('database/migrations/2026061806_m02d_user_management.verify.sql');
|
|
const storeRoomUpSql = read('database/migrations/2026061807_m03a_store_room_domain.up.sql');
|
|
const storeRoomDownSql = read('database/migrations/2026061807_m03a_store_room_domain.down.sql');
|
|
const storeRoomVerifySql = read('database/migrations/2026061807_m03a_store_room_domain.verify.sql');
|
|
const contentUpSql = read('database/migrations/2026061808_m03b_decoration_ads_media.up.sql');
|
|
const contentDownSql = read('database/migrations/2026061808_m03b_decoration_ads_media.down.sql');
|
|
const contentVerifySql = read('database/migrations/2026061808_m03b_decoration_ads_media.verify.sql');
|
|
const discoveryUpSql = read('database/migrations/2026061809_m03c_store_discovery.up.sql');
|
|
const discoveryDownSql = read('database/migrations/2026061809_m03c_store_discovery.down.sql');
|
|
const discoveryVerifySql = read('database/migrations/2026061809_m03c_store_discovery.verify.sql');
|
|
const accessUpSql = read('database/migrations/2026061810_m03d_scene_wifi_access.up.sql');
|
|
const accessDownSql = read('database/migrations/2026061810_m03d_scene_wifi_access.down.sql');
|
|
const accessVerifySql = read('database/migrations/2026061810_m03d_scene_wifi_access.verify.sql');
|
|
|
|
const coreTables = [
|
|
'qipai_schema_migrations',
|
|
'qipai_tenants',
|
|
'qipai_stores',
|
|
'qipai_rooms',
|
|
'qipai_members',
|
|
'qipai_orders',
|
|
'qipai_payments',
|
|
'qipai_devices',
|
|
'qipai_audit_logs',
|
|
'qipai_legacy_table_mappings'
|
|
];
|
|
|
|
for (const table of coreTables) {
|
|
assert.match(upSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}\\b`));
|
|
assert.match(downSql, new RegExp(`DROP TABLE IF EXISTS ${table}\\b`));
|
|
assert.match(verifySql, new RegExp(`'${table}'`));
|
|
}
|
|
|
|
for (const table of [
|
|
'qipai_stores',
|
|
'qipai_rooms',
|
|
'qipai_members',
|
|
'qipai_orders',
|
|
'qipai_payments',
|
|
'qipai_devices',
|
|
'qipai_audit_logs'
|
|
]) {
|
|
const block = upSql.slice(upSql.indexOf(`CREATE TABLE IF NOT EXISTS ${table}`));
|
|
assert.match(block, /\btenant_id BIGINT UNSIGNED NOT NULL\b/);
|
|
}
|
|
|
|
for (const amountColumn of [
|
|
'base_price_cents',
|
|
'balance_cents',
|
|
'total_amount_cents',
|
|
'paid_amount_cents',
|
|
'amount_cents'
|
|
]) {
|
|
assert.match(upSql, new RegExp(`\\b${amountColumn}\\b`));
|
|
}
|
|
|
|
assert.doesNotMatch(upSql, /\bDECIMAL\b/i);
|
|
assert.match(upSql, /DATETIME\(3\)/);
|
|
assert.match(upSql, /legacy_store_id/);
|
|
assert.match(upSql, /legacy_order_id/);
|
|
assert.match(seedSql, /INSERT IGNORE INTO qipai_legacy_table_mappings/);
|
|
assert.match(seedSql, /member_order_info/);
|
|
for (const table of [
|
|
'member_store_info',
|
|
'member_room_info',
|
|
'member_order_info',
|
|
'member_device_info'
|
|
]) {
|
|
assert.match(legacyFixtureSql, new RegExp(`CREATE TABLE ${table}`));
|
|
}
|
|
assert.match(legacyFixtureSql, /DECIMAL\(10,\s*2\)/);
|
|
assert.match(legacyFixtureSql, /LEGACY-SANITIZED-001/);
|
|
assert.doesNotMatch(legacyFixtureSql, /(?:https?:\/\/|@|password|secret|token)/i);
|
|
|
|
for (const table of ['qipai_outbox_events', 'qipai_async_tasks']) {
|
|
assert.match(asyncUpSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}\\b`));
|
|
assert.match(asyncDownSql, new RegExp(`DROP TABLE IF EXISTS ${table}\\b`));
|
|
assert.match(asyncVerifySql, new RegExp(`'${table}'`));
|
|
}
|
|
assert.match(asyncUpSql, /UNIQUE KEY uq_qipai_outbox_events_idempotency/);
|
|
assert.match(asyncUpSql, /UNIQUE KEY uq_qipai_async_tasks_idempotency/);
|
|
assert.match(asyncUpSql, /lease_expires_at DATETIME\(3\)/);
|
|
assert.match(asyncUpSql, /COMPENSATION_REQUIRED|status VARCHAR/);
|
|
|
|
for (const table of ['qipai_platform_apps', 'qipai_tenant_apps', 'qipai_tenant_configs']) {
|
|
assert.match(tenantAppsUpSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}\\b`));
|
|
assert.match(tenantAppsDownSql, new RegExp(`DROP TABLE IF EXISTS ${table}\\b`));
|
|
assert.match(tenantAppsVerifySql, new RegExp(`'${table}'`));
|
|
}
|
|
assert.match(tenantAppsUpSql, /UNIQUE KEY uq_qipai_tenant_apps_tenant_app \(tenant_id, platform_app_id\)/);
|
|
assert.match(tenantAppsUpSql, /UNIQUE KEY uq_qipai_tenant_configs_tenant_app \(tenant_id, platform_app_id\)/);
|
|
assert.match(tenantAppsUpSql, /brand_name VARCHAR/);
|
|
assert.match(tenantAppsUpSql, /theme_color VARCHAR/);
|
|
|
|
for (const table of ['qipai_users', 'qipai_user_identities', 'qipai_auth_sessions']) {
|
|
assert.match(authUpSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}\\b`));
|
|
assert.match(authDownSql, new RegExp(`DROP TABLE IF EXISTS ${table}\\b`));
|
|
assert.match(authVerifySql, new RegExp(`'${table}'`));
|
|
}
|
|
assert.match(authUpSql, /role_version INT UNSIGNED/);
|
|
assert.match(authUpSql, /UNIQUE KEY uq_qipai_user_identities_tenant_app_openid/);
|
|
assert.match(authUpSql, /revoked_at DATETIME\(3\)/);
|
|
assert.match(authUpSql, /expires_at DATETIME\(3\)/);
|
|
|
|
for (const table of [
|
|
'qipai_permissions', 'qipai_roles', 'qipai_role_permissions',
|
|
'qipai_user_roles', 'qipai_user_store_scopes'
|
|
]) {
|
|
assert.match(rbacUpSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}\\b`));
|
|
assert.match(rbacDownSql, new RegExp(`DROP TABLE IF EXISTS ${table}\\b`));
|
|
assert.match(rbacVerifySql, new RegExp(`'${table}'`));
|
|
}
|
|
assert.match(rbacUpSql, /PRIMARY KEY \(tenant_id, user_id, store_id, scope_type\)/);
|
|
assert.match(userManagementUpSql, /CREATE TABLE IF NOT EXISTS qipai_user_admin_profiles/);
|
|
assert.match(userManagementDownSql, /DROP TABLE IF EXISTS qipai_user_admin_profiles/);
|
|
assert.match(userManagementVerifySql, /'qipai_user_admin_profiles'/);
|
|
for (const permission of ['user.read', 'staff.manage', 'session.reset']) {
|
|
assert.match(userManagementUpSql, new RegExp(permission.replace('.', '\\.')));
|
|
}
|
|
for (const table of [
|
|
'qipai_store_business_hours', 'qipai_room_categories', 'qipai_room_disabled_periods'
|
|
]) {
|
|
assert.match(storeRoomUpSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}`));
|
|
assert.match(storeRoomDownSql, new RegExp(`DROP TABLE IF EXISTS ${table}`));
|
|
assert.match(storeRoomVerifySql, new RegExp(`'${table}'`));
|
|
}
|
|
assert.match(storeRoomUpSql, /configuration_status VARCHAR/);
|
|
assert.match(storeRoomUpSql, /operational_status VARCHAR/);
|
|
assert.match(storeRoomUpSql, /weekday_price_cents INT UNSIGNED/);
|
|
assert.match(storeRoomUpSql, /CHECK \(ends_at > starts_at\)/);
|
|
for (const table of [
|
|
'qipai_media_assets', 'qipai_store_decorations', 'qipai_advertisements'
|
|
]) {
|
|
assert.match(contentUpSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}`));
|
|
assert.match(contentDownSql, new RegExp(`DROP TABLE IF EXISTS ${table}`));
|
|
assert.match(contentVerifySql, new RegExp(`'${table}'`));
|
|
}
|
|
assert.match(contentUpSql, /schema_version INT UNSIGNED/);
|
|
assert.match(contentUpSql, /scope_type VARCHAR/);
|
|
assert.match(contentUpSql, /checksum_sha256 CHAR\(64\)/);
|
|
for (const column of ['city', 'district']) {
|
|
assert.match(discoveryUpSql, new RegExp(`ADD COLUMN ${column}`));
|
|
assert.match(discoveryDownSql, new RegExp(`DROP COLUMN ${column}`));
|
|
assert.match(discoveryVerifySql, new RegExp(`'${column}'`));
|
|
}
|
|
assert.match(discoveryUpSql, /idx_qipai_stores_tenant_city_status/);
|
|
assert.match(discoveryUpSql, /idx_qipai_stores_tenant_coordinates/);
|
|
for (const table of [
|
|
'qipai_scene_codes', 'qipai_scene_scan_events', 'qipai_order_user_access'
|
|
]) {
|
|
assert.match(accessUpSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}`));
|
|
assert.match(accessDownSql, new RegExp(`DROP TABLE IF EXISTS ${table}`));
|
|
assert.match(accessVerifySql, new RegExp(`'${table}'`));
|
|
}
|
|
assert.match(accessUpSql, /scan_count BIGINT UNSIGNED/);
|
|
assert.match(accessUpSql, /target_type = 'STORE'/);
|
|
assert.match(accessUpSql, /PRIMARY KEY \(tenant_id, order_id, user_id\)/);
|
|
|
|
console.log('PASS: M01-B through M03-D migration contracts are present.');
|