Files
open-design/packages/contracts/tests/failure-detail.test.ts
PerishFireandGitHub 7bad1a45f2 feat(telemetry): classify publish and deploy failures additively (#8402)
Publish-public and deploy failures were only visible as one opaque bucket
each (`publish_failed`, generic HTTP_400 / Error), so neither PostHog nor the
automatic diagnostics bundles could tell why they failed.

Purely additive — no existing status, error code, event value, trigger or
copy changes:

- daemon: failed publish/unpublish/deploy responses gain an optional
  closed-token `failure` { stage, reason, upstreamStatus?, upstreamCode? }.
  Vela CLI failures are classified from the CLI's stable stderr contract
  (vela-cli 0.0.33–0.1.3); deploy failures separate provider rejection,
  unreachable provider, rejected Vercel token, local file and internal
  errors. Each failure also writes one structured, text-free log line.
- web: artifact_publish_result / artifact_deploy_result gain optional
  failed_stage, failure_reason, upstream_status, upstream_error_code, and
  (publish) daemon_error_code. `error_code` keeps its values.
- correlation: each attempt's analytics request id is sent as
  x-od-request-id, logged by the daemon, and reported as request_id.
2026-09-23 12:19:20 +00:00

41 lines
1.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
normalizeClientRequestId,
normalizeUpstreamCode,
parseApiFailureDetail,
} from '../src/api/failure-detail.js';
describe('parseApiFailureDetail', () => {
it('keeps known tokens and bounded numbers', () => {
expect(parseApiFailureDetail({
stage: 'push', reason: 'upstream_http', upstreamStatus: 503, upstreamCode: 'resource_hub_unavailable',
})).toEqual({ stage: 'push', reason: 'upstream_http', upstreamStatus: 503, upstreamCode: 'resource_hub_unavailable' });
});
it('rejects unknown stages or reasons', () => {
expect(parseApiFailureDetail({ stage: 'push', reason: 'see the logs' })).toBeUndefined();
expect(parseApiFailureDetail({ stage: 'upload', reason: 'timeout' })).toBeUndefined();
expect(parseApiFailureDetail('push')).toBeUndefined();
expect(parseApiFailureDetail([])).toBeUndefined();
});
it('drops optional fields that are not token-shaped', () => {
expect(parseApiFailureDetail({
stage: 'provider', reason: 'provider_rejected', upstreamStatus: 42, upstreamCode: 'has space',
})).toEqual({ stage: 'provider', reason: 'provider_rejected' });
});
});
describe('token normalizers', () => {
it('accepts numeric provider codes as strings', () => {
expect(normalizeUpstreamCode(6003)).toBe('6003');
expect(normalizeUpstreamCode('https://x/y')).toBeUndefined();
});
it('accepts only token-shaped request ids', () => {
expect(normalizeClientRequestId('4b1c2d3e-aaaa-bbbb-cccc-000000000001')).toBe('4b1c2d3e-aaaa-bbbb-cccc-000000000001');
expect(normalizeClientRequestId('short')).toBeUndefined();
expect(normalizeClientRequestId('has spaces in it')).toBeUndefined();
});
});