Skip to content

OUT-3950: integration tests for invoice.created webhook + CI - #66

Merged
SandipBajracharya merged 10 commits into
mainfrom
OUT-3950
Jul 3, 2026
Merged

OUT-3950: integration tests for invoice.created webhook + CI#66
SandipBajracharya merged 10 commits into
mainfrom
OUT-3950

Conversation

@SandipBajracharya

Copy link
Copy Markdown
Collaborator

Summary

Integration tests for the invoice.created webhook flow on the testcontainers harness (OUT-3950), plus CI to run them on GitHub.

The tests drive the real /api/webhook route 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/:

  • happyPath — sync succeeds; Xero contact + invoice created, synced_invoices mapped, success sync_log, no failed_syncs. Parametrized over US and AU (asserts region-specific sales-account code 4000/9000 and tax reportTaxType undefined/OUTPUT).
  • noTaxtaxAmount=0 skips the tax-rate lookup; invoice still created (US + AU).
  • idempotency — replaying an already-synced invoice skips all Xero calls and adds no log.
  • draftInvoice / chargeAutomatically — skip gates ack 200 without syncing.
  • noLineItems — empty line items leave the invoice pending, no invoice created.
  • unsupportedRegioncountryCode='GB' → ack without syncing.
  • syncDisabledisSyncEnabled=false → controller short-circuits.
  • xeroCreateInvoiceFailscreateInvoice throws → invoice marked failed, failed sync_log + failed_syncs row, 500 surfaced.

Harness additions

  • Extended test/helpers/mocks.ts (invoice-path Xero/Copilot mocks), seed.ts (seedSyncedInvoice/seedSyncedContact), constants.ts.
  • New generic test/helpers/webhookTestSetup.ts and test/fixtures/invoiceCreated.webhook.ts factory.

CI

Added an integration-tests job to .github/workflows/code-quality.yml (pnpm test). No secrets required — .env.test is committed with non-secret stubs and globalSetup provisions the DB via Docker (preinstalled on ubuntu-latest).

Testing

Full integration suite green locally: 19 tests across 15 files.

Out of scope (follow-ups)

  • Migrating the product.created tests onto the shared setupWebhookTest helper (currently duplicates productCreatedTestSetup.ts).
  • Exhaustive downstream permutations (company-billed / placeholder / contact reuse, tax find-vs-create, account existing-vs-create), and re-sync of a non-success invoice record.

🤖 Generated with Claude Code

SandipBajracharya and others added 7 commits July 2, 2026 19:13
…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>
@linear-code

linear-code Bot commented Jul 2, 2026

Copy link
Copy Markdown

OUT-3950

@supabase

supabase Bot commented Jul 2, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project pkdwtcdqcefmlgxmcwmc because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
xero-integration Ready Ready Preview, Comment Jul 2, 2026 2:46pm

Request Review

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown

Greptile Summary

Adds 9 integration tests for the invoice.created webhook flow against a real Postgres testcontainer, with Copilot and Xero API clients mocked. The PR also consolidates the shared test setup helper (productCreatedTestSetup.tswebhookTestSetup.ts) and wires a CI job that runs only on PRs after linting and type-checking pass.

  • New test files: happyPath (parametrized US/AU), noTax (US/AU), idempotency, draftInvoice, chargeAutomatically, noLineItems, unsupportedRegion, syncDisabled, and xeroCreateInvoiceFails — covering the major control-flow branches of the handler end-to-end.
  • Shared harness additions: buildInvoiceCreatedWebhook fixture factory, seedSyncedInvoice/seedSyncedContact seed helpers, extended createMockXeroAPI/createMockCopilotAPI defaults, and the renamed setupWebhookTest helper now used by all product.created tests as well.
  • CI: integration-tests job added behind needs: [run-linter, type-check], runs only on pull_request events, timeout 20 minutes.

Confidence Score: 5/5

Pure 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

Filename Overview
.github/workflows/code-quality.yml Adds integration-tests job gated behind needs: [run-linter, type-check] and limited to pull_request events; also tightens the on: trigger to only main PRs/pushes. No issues found.
test/helpers/mocks.ts Extends CopilotAPI mock with getClient and XeroAPI mock with invoice-path methods (getItems, getAccounts, createSalesAccount, getTaxRates, createTaxRate, getContact, createContact, createInvoice). Clean defaults covering happy path; no issues.
test/helpers/seed.ts Adds seedSyncedInvoice and seedSyncedContact with sensible base objects and override support. No issues.
test/helpers/webhookTestSetup.ts Renamed from productCreatedTestSetup.ts; setupProductCreatedTestsetupWebhookTest, ProductCreatedTestHandleWebhookTestHandle. All product.created tests updated accordingly. No issues.
test/integration/webhook/invoiceCreated/happyPath.test.ts Parametrized over US/AU regions; asserts contact creation, sales-account code, tax reportTaxType, synced_invoices mapping, success sync log, and absence of failed_syncs. Thorough coverage.
test/integration/webhook/invoiceCreated/idempotency.test.ts Seeds a pre-existing success record and verifies all Xero write paths are skipped; asserts no new sync log is written. Coverage is exhaustive after the inline comment update in the previous review thread.
test/integration/webhook/invoiceCreated/syncDisabled.test.ts Verifies early controller exit when isSyncEnabled=false; missing syncLogs and failedSyncs empty assertions (same gap flagged for the other three skip-gate tests).
test/integration/webhook/invoiceCreated/noTax.test.ts Parametrized over US/AU; verifies tax-rate path is fully skipped when taxAmount=0. Missing failedSyncs empty assertion compared to happyPath.
test/integration/webhook/invoiceCreated/xeroCreateInvoiceFails.test.ts Overrides createInvoice to throw; asserts invoice marked failed, null xeroInvoiceId, failed sync log, and failed_syncs row. Well-structured error-path coverage.
test/integration/webhook/invoiceCreated/noLineItems.test.ts Asserts pending status when lineItems is empty, no Xero call, no sync log, no failed_syncs. Good coverage.

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
Loading
%%{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
Loading

Reviews (2): Last reviewed commit: "refactor(OUT-3950): migrate product.crea..." | Re-trigger Greptile

Comment thread .github/workflows/code-quality.yml
Comment thread test/integration/webhook/invoiceCreated/idempotency.test.ts
Comment thread test/helpers/webhookTestSetup.ts
…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>
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>
@SandipBajracharya

Copy link
Copy Markdown
Collaborator Author

@greptileai

@SandipBajracharya SandipBajracharya changed the title test(OUT-3950): integration tests for invoice.created webhook + CI OUT-3950: integration tests for invoice.created webhook + CI Jul 3, 2026

@priosshrsth priosshrsth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@SandipBajracharya
SandipBajracharya merged commit 495cc67 into main Jul 3, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants