项目文件夹

文件
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

143 行
4.4 KiB
TypeScript

// Unit coverage for `deriveRunErrorCode`. The v2 analytics doc requires
// `run_finished` events with `result === 'failed'` to carry a non-empty
// `error_code` so dashboards keyed on it can break failures down by
// reason. Several daemon failure paths reach `runs.finish('failed',
// ...)` WITHOUT first emitting an SSE `error` event (ACP fatal,
// agentStreamError fall-through, child close with no diagnostic). When
// they do, the run status body's `errorCode` is `null`. The fallback
// chain in `run-result.ts` turns those signal/exitCode hints into a
// best-effort code so the wire emission never blanks out.
//
// This pins each failure shape's expected output. A future refactor
// that loses the fallback re-introduces the original symptom: PostHog
// shows `result=failed` events with no `error_code`.
import { describe, expect, it } from 'vitest';
import {
deriveRunErrorCode,
runResultFromStatus,
} from '../src/run-result.js';
describe('runResultFromStatus', () => {
it('maps succeeded -> success', () => {
expect(runResultFromStatus('succeeded')).toBe('success');
});
it('maps canceled -> cancelled (with the analytics doc spelling)', () => {
expect(runResultFromStatus('canceled')).toBe('cancelled');
});
it('maps failed -> failed', () => {
expect(runResultFromStatus('failed')).toBe('failed');
});
it('maps unknown / partial states to failed (defensive)', () => {
expect(runResultFromStatus('running')).toBe('failed');
expect(runResultFromStatus(undefined)).toBe('failed');
});
});
describe('deriveRunErrorCode', () => {
it('returns undefined for a successful run', () => {
expect(
deriveRunErrorCode({
status: 'succeeded',
errorCode: null,
exitCode: 0,
signal: null,
}),
).toBeUndefined();
});
it('forwards an explicit errorCode set via runs.emit(error, …)', () => {
expect(
deriveRunErrorCode({
status: 'failed',
errorCode: 'AGENT_EXECUTION_FAILED',
exitCode: 1,
signal: null,
}),
).toBe('AGENT_EXECUTION_FAILED');
});
it('derives AGENT_SIGNAL_* when the child died from a signal', () => {
expect(
deriveRunErrorCode({
status: 'failed',
errorCode: null,
exitCode: null,
signal: 'SIGSEGV',
}),
).toBe('AGENT_SIGNAL_SIGSEGV');
});
it('derives AGENT_EXIT_<code> when the child exited non-zero with no signal', () => {
expect(
deriveRunErrorCode({
status: 'failed',
errorCode: null,
exitCode: 1,
signal: null,
}),
).toBe('AGENT_EXIT_1');
expect(
deriveRunErrorCode({
status: 'failed',
errorCode: null,
exitCode: 137,
signal: null,
}),
).toBe('AGENT_EXIT_137');
});
it('falls back to AGENT_TERMINATED_UNKNOWN when neither signal nor non-zero exit is available', () => {
// This is the shape produced when `acpSession.hasFatalError()` is
// true even though the child exited cleanly (code=0, no signal) —
// see `child.on('close', ...)` in server.ts. Without this fallback,
// those failures would still blank out on PostHog.
expect(
deriveRunErrorCode({
status: 'failed',
errorCode: null,
exitCode: 0,
signal: null,
}),
).toBe('AGENT_TERMINATED_UNKNOWN');
});
it('returns undefined for a cancelled run with no stamped code', () => {
// Cancellation is the user's choice; not a diagnosable failure.
expect(
deriveRunErrorCode({
status: 'canceled',
errorCode: null,
exitCode: null,
signal: 'SIGTERM',
}),
).toBeUndefined();
});
it('forwards an explicit errorCode on a cancelled run (cancel during error recovery)', () => {
expect(
deriveRunErrorCode({
status: 'canceled',
errorCode: 'AGENT_AUTH_REQUIRED',
exitCode: 1,
signal: null,
}),
).toBe('AGENT_AUTH_REQUIRED');
});
it('treats empty-string errorCode as "missing" so the fallback chain still kicks in', () => {
// Defensive — `readString` in runs.ts trims and null-checks, but
// any future caller passing an empty string would otherwise emit a
// blank `error_code` field and reintroduce the original symptom.
expect(
deriveRunErrorCode({
status: 'failed',
errorCode: '',
exitCode: 1,
signal: null,
}),
).toBe('AGENT_EXIT_1');
});
});