import { describe, expect, it } from 'vitest' import corpusJson from '../../../contracts/golden/domain.json' import type { AttemptOutcomeRequestDto, EventFrameDto, JobSubmissionDto, } from '../api/contracts' import { decodeAttemptOutcome, decodeEventFrame, decodeJobFailure, decodeJobSubmission, } from '../api/decode' import { canonicalJson, jsonValue, requestFingerprint } from './fingerprint' import { failureDisposition, isJobPhase, isTerminalPhase, jobPhases, runtimeFailureClass, } from './vocabulary' import { bucketName, memberPath, storageScopeCovers, targetPrefix, } from './validation' const corpus = corpusJson const dtoCompileCases = [ { source: { kind: 'http', url: 'https://example.invalid/file', result_path: 'file.bin', }, }, ] as const satisfies readonly JobSubmissionDto[] const eventCompileCase = { schema_version: 1, kind: 'heartbeat', event_id: 'event_0001', sequence: 1, cursor: 'cursor-value-0001', occurred_at: '2026-07-15T00:00:00Z', } as const satisfies EventFrameDto const outcomeCompileCase = { operation_id: 'operation_01', fence: 1, worker_incarnation: 'worker_0001', outcome: { kind: 'failure', failure: { class: 'transient_source', message: 'source temporarily unavailable', }, }, } as const satisfies AttemptOutcomeRequestDto describe('shared domain corpus', () => { it('preserves protocol phase and failure vocabulary', () => { expect(corpus.job_phases).toEqual(jobPhases) expect(corpus.job_phases.every(isJobPhase)).toBe(true) expect( corpus.job_phases .slice(0, 4) .every((phase) => isJobPhase(phase) && !isTerminalPhase(phase)), ).toBe(true) expect( corpus.job_phases .slice(4) .every((phase) => isJobPhase(phase) && isTerminalPhase(phase)), ).toBe(true) for (const failure of corpus.runtime_failures) { expect(failureDisposition(runtimeFailureClass(failure.class))).toBe( failure.disposition, ) } }) it('validates and normalizes shared domain values', () => { for (const value of corpus.buckets.valid) expect(bucketName(value)).toBe(value) for (const value of corpus.buckets.invalid) { expect(() => bucketName(value)).toThrow('invalid bucket name') } for (const value of corpus.target_prefixes.valid) { expect(targetPrefix(value.input, [])).toBe(value.canonical) } for (const value of corpus.target_prefixes.invalid) { expect(() => targetPrefix(value, [])).toThrow('invalid target prefix') } for (const value of corpus.reserved_target_prefixes) { expect(() => targetPrefix(value.input, value.reserved)).toThrow( 'target prefix uses a reserved segment', ) } for (const value of corpus.member_paths.valid) { expect(memberPath(value.input)).toBe(value.canonical) } for (const value of corpus.member_paths.invalid) { expect(() => memberPath(value)).toThrow('invalid result member path') } }) it('matches RFC 8785 canonical request fingerprints', async () => { for (const value of corpus.fingerprints) { const request = jsonValue(value.value) expect(canonicalJson(request)).toBe(value.canonical) await expect(requestFingerprint(request)).resolves.toBe(value.sha256) } expect(() => jsonValue('\ud800')).toThrow( 'canonical JSON does not permit lone surrogates', ) expect(() => jsonValue({ '\udc00': true })).toThrow( 'canonical JSON does not permit lone surrogates', ) }) it('uses exact target tuples and path segments for scope coverage', () => { for (const value of corpus.scope_coverage) { const scope = { store: value.scope[0], bucket: bucketName(value.scope[1]), prefix: targetPrefix(value.scope[2], []), } const target = { store: value.target[0], bucket: bucketName(value.target[1]), prefix: targetPrefix(value.target[2], []), } expect(storageScopeCovers(scope, target)).toBe(value.covers) } }) it('round trips mutable job submission DTOs', () => { for (const value of corpus.job_submissions) { expect(decodeJobSubmission(value)).toEqual(value) } for (const value of corpus.event_frames) { expect(decodeEventFrame(value)).toEqual(value) } for (const value of corpus.attempt_outcomes) { expect(decodeAttemptOutcome(value)).toEqual(value) } for (const value of corpus.job_failures) { expect(decodeJobFailure(value)).toEqual(value) } expect(dtoCompileCases).toHaveLength(1) expect(eventCompileCase.kind).toBe('heartbeat') expect(outcomeCompileCase.outcome.kind).toBe('failure') }) it('rejects contradictory job failure dispositions', () => { expect(() => decodeJobFailure({ class: 'source_not_found', message: 'missing', retryable: true, }), ).toThrow('invalid retryable failure class') expect(() => decodeJobFailure({ class: 'transient_target', message: 'unavailable', retryable: false, }), ).toThrow('invalid permanent failure class') }) it('rejects WebSocket values outside the schema', () => { expect(() => decodeEventFrame({ schema_version: 2, kind: 'heartbeat', event_id: 'event_0001', sequence: 1, cursor: 'cursor-value-0001', occurred_at: '2026-07-15T00:00:00Z', }), ).toThrow('unsupported schema version') expect(() => decodeEventFrame({ schema_version: 1, kind: 'heartbeat', event_id: 'event_0001', sequence: 0, cursor: 'short', occurred_at: 'not-a-date', }), ).toThrow('expected positive integer') expect(() => decodeEventFrame({ schema_version: 1, kind: 'heartbeat', event_id: 'event_0001', sequence: 1, cursor: 'cursor-value-0001', occurred_at: '2026-02-30T00:00:00Z', }), ).toThrow('invalid RFC 3339 date-time') expect(() => decodeEventFrame({ schema_version: 1, kind: 'heartbeat', event_id: 'event_0001', sequence: 1, cursor: 'cursor-value-0001', occurred_at: '2026-01-01T24:00:00Z', }), ).toThrow('invalid RFC 3339 date-time') }) })