feat: add platform tenant detail audit console

This commit is contained in:
Codex
2026-06-30 04:38:36 +08:00
parent e62653025c
commit d5d32e84b3
16 changed files with 688 additions and 111 deletions

View File

@@ -1138,6 +1138,108 @@ async function testLegacyAuthHeadersDisabled() {
assert.equal(legacyAdminKey.code, 'PLATFORM_ADMIN_KEY_DISABLED', 'platform admin key should be disabled when configured off');
}
async function testPlatformTenantOperationsAndAudit() {
const adminHeaders = { 'x-platform-admin-key': 'local-platform-admin-key' };
const slug = `integration-tenant-${Date.now().toString(36)}`;
const created = await request('/api/platform-admin/tenants', {
tenantId: false,
userId: false,
headers: adminHeaders,
method: 'POST',
body: {
slug,
name: '集成测试合作商',
legalName: '集成测试合作商有限公司',
brandName: '集成测试题库',
primaryHost: `${slug}.example.test`,
planCode: 'starter_yearly',
billingStatus: 'trial',
amountCents: 120000,
billing: {
billingName: '集成测试合作商有限公司',
taxId: 'TESTTAXNO',
contactName: '平台测试联系人',
contactPhone: '13800001111',
invoiceTitle: '集成测试合作商有限公司',
invoiceType: 'normal_vat',
},
},
});
assert.ok(created.item?.id, 'platform admin should create a tenant');
const tenantId = created.item.id;
const detail = await request('/api/platform-admin/tenants/detail', {
tenantId: false,
userId: false,
headers: adminHeaders,
query: { tenantId },
});
assert.equal(detail.item?.tenant?.slug, slug, 'tenant detail should return created tenant');
assert.ok(detail.item?.domains?.some(item => item.host === `${slug}.example.test`), 'tenant detail should include primary host');
assert.ok(detail.item?.subscriptions?.some(item => item.planCode === 'starter_yearly'), 'tenant detail should include subscription');
const billing = await request('/api/platform-admin/tenants/billing-profile', {
tenantId: false,
userId: false,
headers: adminHeaders,
method: 'PUT',
body: {
tenantId,
billingName: '集成测试更新主体',
taxId: 'UPDATEDTAXNO',
contactName: '更新联系人',
contactPhone: '13800002222',
contactEmail: 'finance@example.test',
invoiceTitle: '集成测试更新主体',
invoiceType: 'special_vat',
bankName: '测试银行',
bankAccountMasked: '****2222',
},
});
assert.equal(billing.item?.billingName, '集成测试更新主体', 'platform admin should update tenant billing profile');
const status = await request('/api/platform-admin/tenants/status', {
tenantId: false,
userId: false,
headers: adminHeaders,
method: 'PATCH',
body: {
tenantId,
status: 'active',
billingStatus: 'active',
reason: 'integration audit coverage',
},
});
assert.equal(status.item?.billingStatus, 'active', 'platform admin should update tenant billing status');
const updatedDetail = await request('/api/platform-admin/tenants/detail', {
tenantId: false,
userId: false,
headers: adminHeaders,
query: { tenantId },
});
assert.equal(updatedDetail.item?.tenant?.billingName, '集成测试更新主体', 'tenant detail should include updated billing profile');
assert.equal(updatedDetail.item?.tenant?.bankAccountMasked, '****2222', 'tenant detail should only expose masked bank account');
const audit = await request('/api/platform-admin/audit-logs', {
tenantId: false,
userId: false,
headers: adminHeaders,
query: { tenantId, limit: 20 },
});
const actions = new Set((audit.items || []).map(item => item.action));
assert.ok(actions.has('platform.tenant.created'), 'platform audit should include tenant creation');
assert.ok(actions.has('platform.tenant.billing_profile_upserted'), 'platform audit should include billing profile update');
assert.ok(actions.has('platform.tenant.status_updated'), 'platform audit should include status update');
const studentAuditDenied = await request('/api/platform-admin/audit-logs', {
tenantId: false,
userId: USER_ID,
expectStatus: 403,
});
assert.equal(studentAuditDenied.code, 'PLATFORM_ADMIN_REQUIRED', 'student must not read platform audit logs');
}
function stopServer() {
if (serverProcess && !serverProcess.killed) {
serverProcess.kill();
@@ -8234,6 +8336,7 @@ async function main() {
await check('Supabase JWT identity', testSupabaseJwtIdentity);
await check('Supabase JWKS JWT identity', testSupabaseJwksIdentity);
await check('legacy auth headers disabled', testLegacyAuthHeadersDisabled);
await check('platform tenant operations and audit', testPlatformTenantOperationsAndAudit);
await check('catalog and learning', testCatalogAndLearning);
await check('composite practice questions', testCompositePracticeQuestions);
await check('profile', testProfile);