OUT-3952: integration tests for invoice.voided webhook - #68
Conversation
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ce is missing Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e then voids Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oice failure Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…or created log voidInvoice built its sync-log payloads by spreading prevSyncLog without setting the NOT-NULL entityType. When no invoice.created log exists the log INSERT crashed: the happy path returned 500 even though the Xero void succeeded, and on the failure path the crash preceded addFailedSyncRecord so no failed_syncs row was written. Hardcode entityType like syncPaidInvoiceToXero already does, and cover it with a regression test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9c1c114 to
f116e7d
Compare
Greptile SummaryThis PR adds a complete integration-test suite for the
Confidence Score: 4/5The voidInvoice fix and its regression test are correct and safe to merge; the unresolved syncPaidInvoiceToXero success-log omission is a real defect that can produce duplicate Xero payments on retry, but it pre-dates this PR. The core fix and all six voided-invoice test scenarios are well-constructed. The paid-invoice happy-path test avoids the unfixed bug by seeding a prior created log, but there is no equivalent noPriorCreatedLog test for the paid path. The syncPaidInvoiceToXero success log still lacks entityType — if the DB insert fails mid-transaction the Xero payment is already gone through and a retry would duplicate it. Addressing that in the same PR (one-liner, same pattern) would complete the fix class. src/features/invoice-sync/lib/SyncedInvoices.service.ts — the syncPaidInvoiceToXero success-log path and both deleteInvoice paths still need the entityType hardcode. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant C as Webhook Client
participant W as /api/webhook
participant S as SyncedInvoicesService
participant XL as SyncLogsService
participant X as XeroAPI
participant DB as Database
C->>W: POST invoice.voided
W->>S: voidInvoice(copilotInvoiceId)
S->>DB: getOrCreateInvoiceRecord()
alt xeroInvoiceId missing
S->>S: createMissingXeroInvoice()
S->>X: createInvoice()
end
S->>X: getInvoiceById()
alt invoice not found (404)
S-->>W: APIError NOT_FOUND → failed_syncs row
else invoice found
S->>XL: getInvoiceCreatedSyncLog()
S->>X: voidInvoice(tenantId, xeroInvoiceId)
alt void succeeds
S->>XL: "createSyncLog(VOIDED, SUCCESS, entityType=INVOICE)"
S-->>W: 200 OK
else void throws
S->>XL: "createSyncLog(VOIDED, FAILED, entityType=INVOICE)"
S-->>W: 500 → failed_syncs row
end
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant C as Webhook Client
participant W as /api/webhook
participant S as SyncedInvoicesService
participant XL as SyncLogsService
participant X as XeroAPI
participant DB as Database
C->>W: POST invoice.voided
W->>S: voidInvoice(copilotInvoiceId)
S->>DB: getOrCreateInvoiceRecord()
alt xeroInvoiceId missing
S->>S: createMissingXeroInvoice()
S->>X: createInvoice()
end
S->>X: getInvoiceById()
alt invoice not found (404)
S-->>W: APIError NOT_FOUND → failed_syncs row
else invoice found
S->>XL: getInvoiceCreatedSyncLog()
S->>X: voidInvoice(tenantId, xeroInvoiceId)
alt void succeeds
S->>XL: "createSyncLog(VOIDED, SUCCESS, entityType=INVOICE)"
S-->>W: 200 OK
else void throws
S->>XL: "createSyncLog(VOIDED, FAILED, entityType=INVOICE)"
S-->>W: 500 → failed_syncs row
end
end
|
What
Adds integration tests for the
invoice.voidedwebhook flow on the testcontainers harness, following the OUT-3951 (invoice.paid) pattern. Ref: OUT-3952.Drives the real
/api/webhookroute end-to-end, mocking only the Copilot and Xero API clients. Reuses the sharedsetupWebhookTest, seed helpers, and constants.Test cases (
test/integration/webhook/invoiceVoided/)happyPath.test.tsfailed_syncssyncDisabled.test.tsinvoiceNotFound.test.tsgetInvoiceById→ undefined → 404, no log, onefailed_syncsrowmissingXeroInvoice.test.tsxeroInvoiceIdnull → create-then-void; VOIDED success logxeroVoidFails.test.tsvoidInvoicethrows → 500; failed VOIDED log +failed_syncsrownoPriorCreatedLog.test.tsAlso adds
test/fixtures/voidedInvoice.webhook.tsand avoidInvoicedefault intest/helpers/mocks.ts(additive — existing suites untouched).Production fix
A review surfaced a real defect:
voidInvoicebuilt its sync-log payloads by spreadingprevSyncLogwithout setting theNOT NULLentityType. When no priorinvoice.createdlog exists, the log INSERT crashed — the happy path returned 500 even though the Xero void succeeded, and on the failure path the crash precededaddFailedSyncRecordso nofailed_syncsrow was written (silent drop, no retry).syncPaidInvoiceToXero's catch was already immune because it hardcodesentityType.Fix: hardcode
entityType: SyncEntityType.INVOICEinvoidInvoice's success log and failure payload, matching the existing paid pattern. Covered bynoPriorCreatedLog.test.ts(verified failing before the fix).Deferred / follow-ups
deleteInvoicehas the identicalentityTypedefect — deferred to theinvoice.deletedticket so the fix lands with its own test.syncPaidInvoiceToXero's success-log has the same missingentityType, inside the DB transaction aftermarkInvoicePaid— a crash there rolls back the payment record and risks a duplicate payment on retry. Worth its own ticket.createMissingXeroInvoicedouble-writes a CREATED log with inconsistenttaxAmountunits.Verification
pnpm test→ 27 files / 32 tests passpnpm typecheckandpnpm lintclean🤖 Generated with Claude Code