Files
qipai/backend/tests/system-operations.test.mjs
T
2026-08-10 13:28:54 +08:00

118 lines
5.4 KiB
JavaScript

import assert from 'node:assert/strict';
import { buildApp } from '../dist/app.js';
import { signAccessToken } from '../dist/auth/jwt.js';
import { SystemOperationsRepository } from '../dist/operations/system-operations-repository.js';
const secret = 'test-only-system-operations-secret-32';
const token = signAccessToken({
sub: '21', sid: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tid: '7', aid: '9', rv: 1
}, secret, 900);
let auditQuery;
let overviewTenantId;
let updateCall;
const repository = {
async listAuditLogs(input) {
auditQuery = input;
return { items: [], total: 0, page: input.page, pageSize: input.pageSize };
},
async getSystemOverview(tenantId) {
overviewTenantId = tenantId;
return {
tenant: { id: tenantId, code: 'demo', name: '演示租户', status: 'ACTIVE', timezone: 'Asia/Shanghai' },
counts: { storeCount: 1, userCount: 2, activeSessionCount: 1, appBindingCount: 1, auditTodayCount: 3 },
latestMigration: { version: '2026081003', name: 'm08d_franchise_leads' }
};
},
async updateTenant(actor, tenantId, input) {
updateCall = { actor, tenantId, input };
return { tenantId, updated: true };
}
};
const authRepository = {
async validateSession() {
return {
id: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tenantId: '7', platformAppId: '9',
expiresAt: new Date(Date.now() + 60000),
user: { id: '21', tenantId: '7', userType: 'STAFF', status: 'ACTIVE', roleVersion: 1,
nickname: '管理员', avatarUrl: '', phone: '' }
};
}
};
const tenantAccess = { roles: ['TENANT_ADMIN'], capabilities: ['tenant.manage'], storeIds: [] };
const app = await buildApp({ systemOperations: {
repository, authRepository,
accessControl: { async getAccessProfile() { return tenantAccess; } },
jwtSecret: secret
} });
const auth = { authorization: `Bearer ${token}` };
const listed = await app.inject({ method: 'GET',
url: '/admin-api/audit-logs?action=ORDER_CREATED&resourceType=ORDER&page=2&pageSize=10', headers: auth });
assert.equal(listed.statusCode, 200);
assert.deepEqual({ tenantId: auditQuery.tenantId, action: auditQuery.action,
resourceType: auditQuery.resourceType, page: auditQuery.page },
{ tenantId: '7', action: 'ORDER_CREATED', resourceType: 'ORDER', page: 2 });
assert.ok(auditQuery.from instanceof Date);
assert.ok(auditQuery.to instanceof Date);
const crossTenant = await app.inject({ method: 'GET', url: '/admin-api/audit-logs?tenantId=8', headers: auth });
assert.equal(crossTenant.statusCode, 403);
const invalidRange = await app.inject({ method: 'GET',
url: '/admin-api/audit-logs?from=2025-01-01&to=2026-08-01', headers: auth });
assert.equal(invalidRange.statusCode, 400);
const overview = await app.inject({ method: 'GET', url: '/admin-api/system/overview', headers: auth });
assert.equal(overview.statusCode, 200);
assert.equal(overviewTenantId, '7');
assert.equal(overview.json().data.counts.auditTodayCount, 3);
const updated = await app.inject({ method: 'PUT', url: '/admin-api/system/tenant', headers: auth,
payload: { name: '新租户名称', timezone: 'Asia/Shanghai' } });
assert.equal(updated.statusCode, 200);
assert.deepEqual({ tenantId: updateCall.tenantId, name: updateCall.input.name,
timezone: updateCall.input.timezone },
{ tenantId: '7', name: '新租户名称', timezone: 'Asia/Shanghai' });
const invalidTimezone = await app.inject({ method: 'PUT', url: '/admin-api/system/tenant', headers: auth,
payload: { name: '新租户名称', timezone: 'Mars/Olympus' } });
assert.equal(invalidTimezone.statusCode, 400);
const tenantStatusChange = await app.inject({ method: 'PUT', url: '/admin-api/system/tenant', headers: auth,
payload: { name: '新租户名称', timezone: 'Asia/Shanghai', status: 'DISABLED' } });
assert.equal(tenantStatusChange.statusCode, 403);
assert.equal(tenantStatusChange.json().code, 'SYSTEM_STATUS_FORBIDDEN');
await app.close();
const platformApp = await buildApp({ systemOperations: {
repository, authRepository,
accessControl: { async getAccessProfile() {
return { roles: ['PLATFORM_ADMIN'], capabilities: ['platform.manage'], storeIds: [] };
} }, jwtSecret: secret
} });
const platformUpdate = await platformApp.inject({ method: 'PUT', url: '/admin-api/system/tenant', headers: auth,
payload: { tenantId: '8', name: '平台目标租户', timezone: 'UTC', status: 'DISABLED' } });
assert.equal(platformUpdate.statusCode, 200);
assert.equal(updateCall.tenantId, '8');
assert.equal(updateCall.input.status, 'DISABLED');
await platformApp.close();
const sensitivePool = {
async execute(sql) {
if (sql.includes('COUNT(*)')) return [[{ total: 1 }], []];
return [[{
id: '99', tenantId: '7', actorType: 'USER', actorId: '21', actorName: '管理员',
action: 'CONFIG_UPDATED', resourceType: 'TENANT', resourceId: '7', traceId: 'trace-99',
ip: '192.168.10.22', userAgent: 'test',
metadata: JSON.stringify({ orderId: '88', phone: '13800138000',
nested: { apiKey: 'secret-key', safe: 'visible' } }), createdAt: new Date()
}], []];
}
};
const sanitized = await new SystemOperationsRepository(sensitivePool).listAuditLogs({
tenantId: '7', page: 1, pageSize: 20
});
assert.equal(sanitized.items[0].ip, '192.168.***.***');
assert.equal(sanitized.items[0].metadata.phone, '[REDACTED]');
assert.equal(sanitized.items[0].metadata.nested.apiKey, '[REDACTED]');
assert.equal(sanitized.items[0].metadata.nested.safe, 'visible');
console.log('PASS: M08-D tenant-scoped audit logs, metadata redaction and system settings routes are present.');