// server/supabase/functions/send-push/dispatch-response.test.ts // The dispatch presenter must not leak provider transport codes to end users. import { presentDispatchOutcome, type DispatchOutcome } from './dispatch-response.ts' function assert(condition: boolean, message: string): asserts condition { if (!condition) throw new Error(message) } const EVENT = 'transcription.completed' const RESOURCE = '11111111-2222-4333-8444-555555555555' function outcome(overrides: Partial = {}): DispatchOutcome { return { status: 'succeeded', complete: true, attempted: 1, delivered: 1, stale: 0, retryableFailed: 0, permanentFailed: 0, nextRetryAt: null, transientError: null, transientStatus: null, ...overrides, } } const TRANSIENT_CASES: ReadonlyArray<[string, number]> = [ ['webpush_send_timeout', 504], ['webpush_send_failed', 502], ['webpush_payload_too_large', 500], ['webpush_not_configured', 503], ['fcm_send_failed', 502], ] Deno.test('user callers see one generic failure regardless of the transport outcome', () => { const seen = new Set() for (const [code, status] of TRANSIENT_CASES) { const presented = presentDispatchOutcome('user', EVENT, RESOURCE, outcome({ status: 'pending', complete: false, delivered: 0, retryableFailed: 1, transientError: code, transientStatus: status, })) assert(presented.body.error === 'push_delivery_failed', `${code} leaked as ${String(presented.body.error)}`) assert(!JSON.stringify(presented.body).includes(code), `${code} must not appear in the body`) seen.add(`${presented.status}:${String(presented.body.error)}`) } assert(seen.size === 1, `user responses must be indistinguishable, got ${[...seen].join(', ')}`) }) Deno.test('system callers keep the detailed transport code and status', () => { for (const [code, status] of TRANSIENT_CASES) { const presented = presentDispatchOutcome('system', EVENT, RESOURCE, outcome({ status: 'pending', complete: false, delivered: 0, retryableFailed: 1, transientError: code, transientStatus: status, })) assert(presented.body.error === code, `system caller expects ${code}`) assert(presented.status === status, `system caller expects status ${status}`) } }) Deno.test('success, pending and partial responses keep their existing shape', () => { const ok = presentDispatchOutcome('user', EVENT, RESOURCE, outcome()) assert(ok.status === 200, 'complete dispatch is 200') assert(!('error' in ok.body), 'success carries no error') assert(ok.body.sent === 1 && ok.body.event_type === EVENT && ok.body.resource_id === RESOURCE, 'body fields preserved') assert(ok.body.duplicate === false, 'duplicate flag preserved') const pending = presentDispatchOutcome('system', EVENT, RESOURCE, outcome({ status: 'processing', complete: false })) assert(pending.status === 202, 'incomplete dispatch is 202') for (const audience of ['user', 'system'] as const) { const partial = presentDispatchOutcome(audience, EVENT, RESOURCE, outcome({ status: 'partial', permanentFailed: 1 })) assert(partial.status === 502 && partial.body.error === 'push_delivery_failed', `${audience} partial is 502`) } })