90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
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',
|
|
});
|
|
});
|
|
});
|