feat(M02-A): 建立多小程序租户配置模型
This commit is contained in:
@@ -6,6 +6,10 @@ import { loadConfig } from '../dist/config.js';
|
||||
import { closeMySqlPool, createMySqlPool } from '../dist/db/mysql.js';
|
||||
import { LegacyReadRepository } from '../dist/db/legacy-read-repository.js';
|
||||
import { TaskRepository } from '../dist/tasks/task-repository.js';
|
||||
import {
|
||||
AmbiguousAppTenantError,
|
||||
PlatformConfigRepository
|
||||
} from '../dist/tenancy/platform-config-repository.js';
|
||||
import {
|
||||
executeMigrationPlan,
|
||||
loadMigrationPlan,
|
||||
@@ -21,9 +25,12 @@ const expectedTables = [
|
||||
'qipai_orders',
|
||||
'qipai_outbox_events',
|
||||
'qipai_payments',
|
||||
'qipai_platform_apps',
|
||||
'qipai_rooms',
|
||||
'qipai_schema_migrations',
|
||||
'qipai_stores',
|
||||
'qipai_tenant_apps',
|
||||
'qipai_tenant_configs',
|
||||
'qipai_tenants'
|
||||
];
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
@@ -45,9 +52,9 @@ async function readMigrationVersions(pool) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT version, name
|
||||
FROM qipai_schema_migrations
|
||||
WHERE version IN (?, ?)
|
||||
WHERE version IN (?, ?, ?)
|
||||
ORDER BY version`,
|
||||
['2026061601', '2026061802']
|
||||
['2026061601', '2026061802', '2026061803']
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
@@ -120,6 +127,48 @@ async function assertTaskDurability(pool) {
|
||||
assert.deepEqual(rows, [{ status: 'SUCCEEDED', attempts: 1 }]);
|
||||
}
|
||||
|
||||
async function assertPlatformTenantIsolation(pool) {
|
||||
const [tenantResult] = await pool.query(
|
||||
`INSERT INTO qipai_tenants (code, name)
|
||||
VALUES ('M02A-A', 'M02A tenant A'), ('M02A-B', 'M02A tenant B')`
|
||||
);
|
||||
const firstTenantId = Number(tenantResult.insertId);
|
||||
const secondTenantId = firstTenantId + 1;
|
||||
const [appResult] = await pool.query(
|
||||
`INSERT INTO qipai_platform_apps (appid, name)
|
||||
VALUES ('wx-m02a-shared', 'M02A shared app')`
|
||||
);
|
||||
const platformAppId = Number(appResult.insertId);
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO qipai_tenant_apps
|
||||
(tenant_id, platform_app_id, is_default)
|
||||
VALUES (?, ?, 1), (?, ?, 0)`,
|
||||
[firstTenantId, platformAppId, secondTenantId, platformAppId]
|
||||
);
|
||||
await pool.query(
|
||||
`INSERT INTO qipai_tenant_configs
|
||||
(tenant_id, platform_app_id, brand_name, theme_color)
|
||||
VALUES (?, ?, 'Tenant A Brand', '#111111'),
|
||||
(?, ?, 'Tenant B Brand', '#222222')`,
|
||||
[firstTenantId, platformAppId, secondTenantId, platformAppId]
|
||||
);
|
||||
|
||||
const repository = new PlatformConfigRepository(pool);
|
||||
const tenantA = await repository.resolveBootstrap('wx-m02a-shared', String(firstTenantId));
|
||||
const tenantB = await repository.resolveBootstrap('wx-m02a-shared', String(secondTenantId));
|
||||
assert.equal(tenantA?.brand.name, 'Tenant A Brand');
|
||||
assert.equal(tenantB?.brand.name, 'Tenant B Brand');
|
||||
assert.equal(
|
||||
await repository.resolveBootstrap('wx-m02a-shared', String(secondTenantId + 999)),
|
||||
null
|
||||
);
|
||||
await assert.rejects(
|
||||
() => repository.resolveBootstrap('wx-m02a-shared'),
|
||||
AmbiguousAppTenantError
|
||||
);
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
assert.equal(config.mysql.passwordConfigured, true, 'Live migration test requires a temporary password.');
|
||||
assert.match(
|
||||
@@ -145,11 +194,13 @@ try {
|
||||
assert.deepEqual(await readCoreTables(pool), expectedTables);
|
||||
assert.deepEqual(await readMigrationVersions(pool), [
|
||||
{ version: '2026061601', name: 'm01b_core_schema' },
|
||||
{ version: '2026061802', name: 'm01c_async_tasks' }
|
||||
{ version: '2026061802', name: 'm01c_async_tasks' },
|
||||
{ version: '2026061803', name: 'm02a_tenant_apps' }
|
||||
]);
|
||||
await assertTaskDurability(pool);
|
||||
await assertPlatformTenantIsolation(pool);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: first up, verify and durable task restart check completed.');
|
||||
console.log('PASS: first up, verify, durable task and tenant isolation checks completed.');
|
||||
|
||||
await executeMigrationPlan(pool, plans.down);
|
||||
assert.deepEqual(await readCoreTables(pool), []);
|
||||
@@ -161,7 +212,8 @@ try {
|
||||
assert.deepEqual(await readCoreTables(pool), expectedTables);
|
||||
assert.deepEqual(await readMigrationVersions(pool), [
|
||||
{ version: '2026061601', name: 'm01b_core_schema' },
|
||||
{ version: '2026061802', name: 'm01c_async_tasks' }
|
||||
{ version: '2026061802', name: 'm01c_async_tasks' },
|
||||
{ version: '2026061803', name: 'm02a_tenant_apps' }
|
||||
]);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: second up and verify restored the schema.');
|
||||
@@ -187,7 +239,9 @@ try {
|
||||
'orders',
|
||||
'devices',
|
||||
'tenant isolation',
|
||||
'decimal cents'
|
||||
'decimal cents',
|
||||
'app-to-tenant binding',
|
||||
'cross-tenant bootstrap rejection'
|
||||
]
|
||||
}, null, 2));
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user