feat(ops): 增加运行时性能看板

This commit is contained in:
2026-07-08 17:47:36 +08:00
parent 58c98a3a3d
commit 15653b5b35
19 changed files with 933 additions and 2 deletions

View File

@@ -0,0 +1,89 @@
import { recordSlowPrismaQuery, recordSlowRequest, clearOpsRuntimeEventsForTests } from './ops-runtime.store';
import { OpsService } from './ops.service';
describe('OpsService', () => {
afterEach(() => clearOpsRuntimeEventsForTests());
it('returns runtime performance counters without exposing secrets', async () => {
const prisma = {
backgroundJob: {
findMany: jest.fn().mockResolvedValue([
{
id: 'job-queued',
type: 'xiaobao.summary.refresh',
status: 'queued',
attempts: 0,
maxAttempts: 5,
availableAt: new Date('2026-07-08T08:00:00.000Z'),
lockedUntil: null,
lastError: null,
updatedAt: new Date('2026-07-08T08:00:00.000Z'),
},
{
id: 'job-running',
type: 'xiaobao.ai.interpret',
status: 'running',
attempts: 1,
maxAttempts: 3,
availableAt: new Date('2026-07-08T08:01:00.000Z'),
lockedUntil: new Date('2026-07-08T08:05:00.000Z'),
lastError: null,
updatedAt: new Date('2026-07-08T08:02:00.000Z'),
},
{
id: 'job-failed',
type: 'xiaobao.ai.interpret',
status: 'failed',
attempts: 3,
maxAttempts: 3,
availableAt: new Date('2026-07-08T08:03:00.000Z'),
lockedUntil: null,
lastError: 'provider failed with sk-secret-token',
updatedAt: new Date('2026-07-08T08:04:00.000Z'),
},
]),
},
xiaobaoRiskSummary: {
count: jest.fn().mockResolvedValue(7),
},
};
recordSlowRequest({
method: 'POST',
url: '/api/v1/ai/risk-interpret?token=sk-secret-token',
durationMs: 1500,
thresholdMs: 1000,
occurredAt: new Date('2026-07-08T08:05:00.000Z'),
});
recordSlowPrismaQuery({
query: 'SELECT * FROM background_jobs WHERE last_error = "sk-secret-token"',
durationMs: 420,
thresholdMs: 300,
occurredAt: new Date('2026-07-08T08:06:00.000Z'),
});
const service = new OpsService(prisma as any);
const snapshot = await service.getRuntimeSnapshot(new Date('2026-07-08T08:07:00.000Z'));
expect(snapshot.dirtySummaryCount).toBe(7);
expect(snapshot.jobQueue.totals).toEqual({ queued: 1, running: 1, succeeded: 0, failed: 1, total: 3 });
expect(snapshot.jobQueue.byType).toEqual([
expect.objectContaining({ type: 'xiaobao.ai.interpret', running: 1, failed: 1, total: 2 }),
expect.objectContaining({ type: 'xiaobao.summary.refresh', queued: 1, total: 1 }),
]);
expect(snapshot.jobQueue.recentFailures).toEqual([
expect.objectContaining({
id: 'job-failed',
type: 'xiaobao.ai.interpret',
lastError: expect.stringContaining('[redacted]'),
}),
]);
expect(snapshot.slowRequests[0].path).toBe('/api/v1/ai/risk-interpret');
expect(snapshot.slowQueries[0].queryPreview).toContain('[redacted]');
expect(JSON.stringify(snapshot)).not.toContain('sk-secret-token');
expect(snapshot.access).toEqual({
requiredPermission: 'ops:view',
backendEnforced: false,
adapter: 'OpsPermissionAdapter',
});
});
});