4a9fa5e6c5
* fix(whatsapp): app-side unmute, close silent intake paths, health visibility - add POST /link/unmute and a Reactivate control on the Pausad state - company resolution: transient query errors release the row for sweep retry; genuine zero-options sends M19 instead of parking silently - media from unlinked senders bypasses the hourly greeting throttle (10 min burst window, daily cap kept) - GET /link returns 7-day failed-delivery and parked-inbound counts; sweep summary logs outboundFailed24h Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(documents): real HEIC/HEIF magic-byte validation, bilingual upload errors - detect ISO-BMFF ftyp brands (heic/heix/heim/heis/hevc/hevx/hevm/hevs, mif1/msf1) instead of exempting image/heic from validation; declared heic/heif accepts either family member (iOS labels vary) - new INBOX_UPLOAD_* structured error codes replace raw English strings on the inbox upload and attach-document routes - registry doc corrected to the real 10 MB cap Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(inbox): staged upload with instant ack and deferred AI extraction - web uploads insert the inbox item as status processing and respond immediately; Bedrock extraction and supplier match run via after() with a CAS flip to received (email and WhatsApp channels keep the synchronous path) - widen invoice_inbox_items.status CHECK to include processing (migration 20260813180000, pg-real test included) - crash-recovery sweep cron (*/2) flips stale processing rows; bulk-book skips extraction_in_progress items - workspace: processing chip, in-flight rows disable actions, realtime flip, retry-extraction button for empty extractions - picker accept list drops HEIC/HEIF so iOS transcodes library photos to JPEG; server allowlists unchanged (supersedes 2026-08-01 HEIC decision, see DECISIONS.md) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(migrations): bump inbox processing-status migration past main's latest Main merged 20260813210000 while this PR was in flight; an inserted version older than the latest applied aborts the prod db push at merge. Renamed 20260813180000 to 20260813213000 and updated references. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(decisions): log preview-tracker orphan repair after migration rename Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
53 lines
2.0 KiB
TypeScript
53 lines
2.0 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { getPool } from './setup'
|
|
import { seedCompany } from './fixtures'
|
|
|
|
/**
|
|
* The staged-upload status CHECK (migration 20260813213000).
|
|
*
|
|
* The web upload route inserts the inbox row before AI extraction runs and
|
|
* flips it to 'received' from a deferred worker (or the sweep cron after a
|
|
* crash). That requires 'processing' back in the CHECK that migration
|
|
* 20260504180000 tightened to received | error. These tests pin the widened
|
|
* enum, that the constraint still rejects everything else, and that the
|
|
* default stayed 'received'.
|
|
*/
|
|
describe('invoice_inbox_items staged-upload status (pg)', () => {
|
|
it("accepts the re-admitted 'processing' status", async () => {
|
|
const { userId, companyId } = await seedCompany()
|
|
|
|
const { rows } = await getPool().query<{ id: string; status: string }>(
|
|
`INSERT INTO public.invoice_inbox_items (company_id, user_id, source, status)
|
|
VALUES ($1, $2, 'upload', 'processing')
|
|
RETURNING id, status`,
|
|
[companyId, userId],
|
|
)
|
|
expect(rows[0].status).toBe('processing')
|
|
})
|
|
|
|
it('still refuses statuses outside the enum', async () => {
|
|
const { userId, companyId } = await seedCompany()
|
|
|
|
// 'ready' was one of the pre-20260504180000 AI states: it must stay out.
|
|
await expect(
|
|
getPool().query(
|
|
`INSERT INTO public.invoice_inbox_items (company_id, user_id, source, status)
|
|
VALUES ($1, $2, 'upload', 'ready')`,
|
|
[companyId, userId],
|
|
),
|
|
).rejects.toThrow(/invoice_inbox_items_status_check|violates check constraint/i)
|
|
})
|
|
|
|
it("defaults status to 'received' when the writer says nothing", async () => {
|
|
const { userId, companyId } = await seedCompany()
|
|
|
|
const { rows } = await getPool().query<{ status: string }>(
|
|
`INSERT INTO public.invoice_inbox_items (company_id, user_id, source)
|
|
VALUES ($1, $2, 'upload')
|
|
RETURNING status`,
|
|
[companyId, userId],
|
|
)
|
|
expect(rows[0].status).toBe('received')
|
|
})
|
|
})
|