mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
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.
41 lines
1.7 KiB
TypeScript
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();
|
|
});
|
|
});
|