OUT-3950: integration tests for invoice.created webhook + CI - #66
Conversation
…US and AU Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…is zero Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cally Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sabled sync Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…failed_syncs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
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.
|
Greptile SummaryAdds 9 integration tests for the
Confidence Score: 5/5Pure test and CI additions with no production code changes — safe to merge. No production code is touched. The CI job is correctly gated on static analysis and scoped to pull requests. Test logic is sound across all 9 new scenarios; the only gaps are minor missing table assertions on a few skip-gate tests. Skip-gate tests (syncDisabled, draftInvoice, chargeAutomatically, unsupportedRegion) could use additional empty-table assertions for syncLogs/failedSyncs to close the regression detection gap. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Test as Integration Test
participant Route as POST /api/webhook
participant Ctrl as InvoiceController
participant DB as Postgres (testcontainer)
participant Copilot as CopilotAPI (mock)
participant Xero as XeroAPI (mock)
Test->>Route: postWebhook(payload)
Route->>Ctrl: handleInvoiceCreated(event)
Ctrl->>DB: check isSyncEnabled / countryCode
alt sync disabled / unsupported region / draft / chargeAutomatically
Ctrl-->>Route: ack 200 (no DB writes)
else already synced (idempotency)
Ctrl->>DB: SELECT synced_invoices
DB-->>Ctrl: existing row
Ctrl-->>Route: ack 200 (no Xero calls)
else no line items
Ctrl->>DB: "INSERT synced_invoices (status=pending)"
Ctrl-->>Route: ack 200
else happy path
Ctrl->>Copilot: getClient(clientId)
Copilot-->>Ctrl: client data
Ctrl->>Xero: createContact / createSalesAccount / getTaxRates / createTaxRate
Xero-->>Ctrl: contact + account + tax rate
Ctrl->>Xero: createInvoice(tenantId, invoicePayload)
Xero-->>Ctrl: "{ invoiceID }"
Ctrl->>DB: "INSERT synced_invoices (status=success)"
Ctrl->>DB: "INSERT sync_logs (status=SUCCESS)"
Ctrl-->>Route: 200
else Xero failure
Ctrl->>Xero: createInvoice throws
Ctrl->>DB: "UPDATE synced_invoices (status=failed)"
Ctrl->>DB: "INSERT sync_logs (status=FAILED)"
Ctrl->>DB: INSERT failed_syncs
Ctrl-->>Route: 500
end
Route-->>Test: HTTP response
Test->>DB: SELECT assertions
%%{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 Test as Integration Test
participant Route as POST /api/webhook
participant Ctrl as InvoiceController
participant DB as Postgres (testcontainer)
participant Copilot as CopilotAPI (mock)
participant Xero as XeroAPI (mock)
Test->>Route: postWebhook(payload)
Route->>Ctrl: handleInvoiceCreated(event)
Ctrl->>DB: check isSyncEnabled / countryCode
alt sync disabled / unsupported region / draft / chargeAutomatically
Ctrl-->>Route: ack 200 (no DB writes)
else already synced (idempotency)
Ctrl->>DB: SELECT synced_invoices
DB-->>Ctrl: existing row
Ctrl-->>Route: ack 200 (no Xero calls)
else no line items
Ctrl->>DB: "INSERT synced_invoices (status=pending)"
Ctrl-->>Route: ack 200
else happy path
Ctrl->>Copilot: getClient(clientId)
Copilot-->>Ctrl: client data
Ctrl->>Xero: createContact / createSalesAccount / getTaxRates / createTaxRate
Xero-->>Ctrl: contact + account + tax rate
Ctrl->>Xero: createInvoice(tenantId, invoicePayload)
Xero-->>Ctrl: "{ invoiceID }"
Ctrl->>DB: "INSERT synced_invoices (status=success)"
Ctrl->>DB: "INSERT sync_logs (status=SUCCESS)"
Ctrl-->>Route: 200
else Xero failure
Ctrl->>Xero: createInvoice throws
Ctrl->>DB: "UPDATE synced_invoices (status=failed)"
Ctrl->>DB: "INSERT sync_logs (status=FAILED)"
Ctrl->>DB: INSERT failed_syncs
Ctrl-->>Route: 500
end
Route-->>Test: HTTP response
Test->>DB: SELECT assertions
Reviews (2): Last reviewed commit: "refactor(OUT-3950): migrate product.crea..." | Re-trigger Greptile |
01b74e6 to
8e6fe31
Compare
…e-check Integration tests run only on pull_request (not arbitrary pushes) and via needs:[run-linter, type-check], so a lint/type error fails fast without spinning up Docker + Testcontainers. Workflow triggers on PRs to main and main pushes to avoid duplicate runs. No secrets needed — .env.test is committed and globalSetup provisions the DB. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8e6fe31 to
2ed042f
Compare
Also assert createSalesAccount, getTaxRates and createTaxRate are not called, so a regression that reached the sync fan-out before the success short-circuit would be caught, not just createInvoice/createContact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ookTest setupWebhookTest and setupProductCreatedTest were character-for-character identical. Point the product.created tests at the canonical helper and delete the duplicate, so the shared beforeEach pattern lives in one place. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
e6aecd0 to
cb58ea8
Compare
Summary
Integration tests for the
invoice.createdwebhook flow on the testcontainers harness (OUT-3950), plus CI to run them on GitHub.The tests drive the real
/api/webhookroute end to end against an ephemeral Postgres container, mocking only the Copilot and Xero API clients. No production code changes.Coverage
9 test files under
test/integration/webhook/invoiceCreated/:synced_invoicesmapped, successsync_log, nofailed_syncs. Parametrized over US and AU (asserts region-specific sales-account code4000/9000and taxreportTaxTypeundefined/OUTPUT).taxAmount=0skips the tax-rate lookup; invoice still created (US + AU).pending, no invoice created.countryCode='GB'→ ack without syncing.isSyncEnabled=false→ controller short-circuits.createInvoicethrows → invoice markedfailed, failedsync_log+failed_syncsrow, 500 surfaced.Harness additions
test/helpers/mocks.ts(invoice-path Xero/Copilot mocks),seed.ts(seedSyncedInvoice/seedSyncedContact),constants.ts.test/helpers/webhookTestSetup.tsandtest/fixtures/invoiceCreated.webhook.tsfactory.CI
Added an
integration-testsjob to.github/workflows/code-quality.yml(pnpm test). No secrets required —.env.testis committed with non-secret stubs andglobalSetupprovisions the DB via Docker (preinstalled onubuntu-latest).Testing
Full integration suite green locally: 19 tests across 15 files.
Out of scope (follow-ups)
product.createdtests onto the sharedsetupWebhookTesthelper (currently duplicatesproductCreatedTestSetup.ts).successinvoice record.🤖 Generated with Claude Code