feat(M02-A): 建立多小程序租户配置模型

This commit is contained in:
Codex
2026-06-18 10:30:31 +08:00
parent 9d13f75dc0
commit 8b8fb28c36
15 changed files with 459 additions and 15 deletions
@@ -0,0 +1,88 @@
import assert from 'node:assert/strict';
import { buildApp } from '../dist/app.js';
import {
AmbiguousAppTenantError,
PlatformConfigRepository
} from '../dist/tenancy/platform-config-repository.js';
const calls = [];
const repository = new PlatformConfigRepository({
async execute(sql, params) {
calls.push([sql, params]);
return [[{
appId: 'wx-test-app',
tenantId: 7,
tenantCode: 'tenant-seven',
tenantName: 'Tenant Seven',
brandName: 'Seven棋牌',
logoUrl: 'https://api.txyundm.cn/uploads/tenant-7/logo.png',
themeColor: '#1677ff',
servicePhone: '4000000000',
franchisePhone: '',
shareTitle: 'Seven棋牌',
shareImageUrl: '',
defaultStoreId: 9
}], []];
}
});
const bootstrap = await repository.resolveBootstrap('wx-test-app', '7');
assert.equal(bootstrap.tenantId, '7');
assert.equal(bootstrap.defaultStoreId, '9');
assert.equal(bootstrap.brand.name, 'Seven棋牌');
assert.match(calls[0][0], /tc\.tenant_id = ta\.tenant_id/);
assert.match(calls[0][0], /AND ta\.tenant_id = \?/);
assert.deepEqual(calls[0][1], ['wx-test-app', '7']);
const ambiguousRepository = new PlatformConfigRepository({
async execute() {
return [[
{ tenantId: 1 },
{ tenantId: 2 }
], []];
}
});
await assert.rejects(
() => ambiguousRepository.resolveBootstrap('wx-multi-app'),
AmbiguousAppTenantError
);
const app = await buildApp({
platformConfigRepository: {
async resolveBootstrap(appId, tenantId) {
assert.equal(appId, 'wx-test-app');
assert.equal(tenantId, '7');
return bootstrap;
}
}
});
const response = await app.inject({
method: 'GET',
url: '/app-api/bootstrap',
headers: {
'x-wechat-appid': 'wx-test-app',
'tenant-id': '7',
'x-trace-id': 'm02a-test'
}
});
assert.equal(response.statusCode, 200);
assert.equal(response.json().data.tenantId, '7');
assert.equal(response.headers['x-trace-id'], 'm02a-test');
await app.close();
const missingContextApp = await buildApp({
platformConfigRepository: {
async resolveBootstrap() {
throw new Error('must not be called');
}
}
});
const invalidResponse = await missingContextApp.inject({
method: 'GET',
url: '/app-api/bootstrap'
});
assert.equal(invalidResponse.statusCode, 400);
assert.equal(invalidResponse.json().code, 'INVALID_APP_CONTEXT');
await missingContextApp.close();
console.log('PASS: M02-A app and tenant bootstrap enforces explicit bindings.');