GPU_GUARD_MONOREPO/packages/backend/test/skill_process_events.test.ts
2026-05-20 21:39:12 +08:00

142 lines
4.3 KiB
TypeScript

import { normalizeSkillProcessLine } from '../src/modules/netaclaw/service/skill_executor';
describe('skill process events', () => {
it('only parses explicit process_event JSON lines', () => {
const event = normalizeSkillProcessLine(
'vehicle-damage-inspection',
'{"type":"process_event","stage":"inspect","message":"Inspecting","taskId":"task-1","status":"completed","level":"warning","current":2,"total":4,"percent":55,"timestamp":"2026-05-06T00:00:00.000Z","note":"kept"}',
7,
);
expect(event).toEqual({
source: 'skill',
targetType: 'skill',
taskId: 'task-1',
stage: 'inspect',
status: 'completed',
level: 'warning',
message: 'Inspecting',
timestamp: '2026-05-06T00:00:00.000Z',
sequence: 7,
current: 2,
total: 4,
percent: 55,
payload: { note: 'kept' },
});
});
it('supports bracketed stderr prefixes before JSON', () => {
const event = normalizeSkillProcessLine(
'vehicle-damage-inspection',
'[vehicle-damage-inspection] {"type":"process_event","stage":"upload","time":"2026-05-06T00:00:00.000Z"}',
1,
);
expect(event).toEqual(expect.objectContaining({
source: 'skill',
targetType: 'skill',
stage: 'upload',
status: 'running',
level: 'info',
message: 'upload',
timestamp: '2026-05-06T00:00:00.000Z',
sequence: 1,
}));
});
it('derives percent from batch and totalBatches', () => {
const event = normalizeSkillProcessLine(
'vehicle-damage-inspection',
'{"type":"process_event","stage":"batching","batch":1,"totalBatches":3}',
2,
);
expect(event).toEqual(expect.objectContaining({
current: 1,
total: 3,
percent: 33,
}));
});
it('sanitizes payload before emitting process events', () => {
const event = normalizeSkillProcessLine(
'vehicle-damage-inspection',
JSON.stringify({
type: 'process_event',
message: 'request completed',
apiKey: 'secret',
rawText: 'x'.repeat(600),
nested: { value: 1 },
}),
3,
);
expect(event?.payload).toEqual({
apiKey: '[filtered]',
rawText: `${'x'.repeat(500)}...`,
nested: '[object]',
});
});
it('returns null for ordinary stderr', () => {
expect(normalizeSkillProcessLine(
'vehicle-damage-inspection',
'Traceback: failed to import module',
1,
)).toBeNull();
});
it('returns null for ordinary JSON without process_event type', () => {
expect(normalizeSkillProcessLine(
'vehicle-damage-inspection',
'{"stage":"inspect","message":"ordinary json"}',
1,
)).toBeNull();
});
});
describe('vehicle damage inspection process event output', () => {
it('main skill progress logs are explicit process events', async () => {
const scriptPath = require.resolve('../skills/vehicle-damage-inspection/scripts/index.cjs');
const originalWrite = process.stderr.write;
const writes: string[] = [];
jest.resetModules();
process.stderr.write = jest.fn((chunk: any) => {
writes.push(String(chunk));
return true;
}) as any;
try {
const skill = require(scriptPath);
await skill.run(
{ videoUrl: 'demo.mp4', taskId: 'task-1', mode: 'frames-only' },
{
RZYX_AI_WORKSPACE_ROOT: 'C:/data/workspace/vehicle-damage-inspection',
RZYX_AI_UPLOAD_ROOT: 'C:/data/uploads',
},
{
createWorkspace: () => ({
taskId: 'task-1',
workspacePath: 'C:/data/workspace/vehicle-damage-inspection/task-1',
}),
resolveVideoPath: () => 'C:/data/uploads/demo.mp4',
extractFrames: async () => ({
videoInfo: { duration: 1, resolution: '960x544', extractedFrames: 1 },
frames: [{ index: 0, timestamp: 0, path: 'frame.jpg' }],
}),
writeJson: () => undefined,
},
);
} finally {
process.stderr.write = originalWrite;
}
const parsed = writes
.map(line => line.match(/\[vehicle-damage-inspection\]\s*(\{.*\})/)?.[1])
.filter(Boolean)
.map(json => JSON.parse(json as string));
expect(parsed.length).toBeGreaterThan(0);
expect(parsed.every(item => item.type === 'process_event')).toBe(true);
});
});