Skip to content

test(OUT-3708): unit + integration tests for invoice.created webhook#244

Draft
SandipBajracharya wants to merge 19 commits into
masterfrom
OUT-3708
Draft

test(OUT-3708): unit + integration tests for invoice.created webhook#244
SandipBajracharya wants to merge 19 commits into
masterfrom
OUT-3708

Conversation

@SandipBajracharya
Copy link
Copy Markdown
Collaborator

Summary

  • Adds 9 integration tests + 1 unit test pinning the production behavior of the invoice.created webhook handler.
  • Mirrors the existing priceCreated/ integration suite shape (testcontainers + module-mocked Copilot/Intuit).
  • Fixes a pre-existing flake in expiringSweep.test.ts surfaced by the higher integration-file count.

Linear: OUT-3708

What's covered

Integration (test/integration/quickbooks/invoiceCreated/):

  • happyPath — OPEN status, mapped product, new customer (asserts mapped-product path via Description from copilot.getProduct)
  • draftSkip — DRAFT gate sits before claim
  • idempotency — pre-existing CREATED log blocks second delivery
  • invoiceAlreadyExistsgetInvoiceByNumber short-circuits at the top of webhookInvoiceCreated, before customer resolution
  • qbCreateInvoiceFails — claim row flipped to FAILED with errorMessage
  • statusPaidCreatesPayment — PAID branch creates Payment with LinkedTxn linked to the just-created invoice
  • oneOffLineItem — line item without productId/priceId routes through the Assembly Service ref
  • createNewProductFlagOff — flag-off + unmapped line item falls back to one-off (no qb_product_sync row)
  • useCompanyNameFlag — company-only payload + flag-on creates customer with customerType='company'

Unit (test/unit/api/quickbooks/webhook/handleInvoiceCreated.test.ts):

  • 5 orchestration branches: parse-failure / DRAFT skip / already-claimed / happy path / service-throws → FAILED log

Side fix

expiringSweep.test.ts was using a per-file vi.mock('@/utils/intuit', …) factory. Under integration's pool:forks + fileParallelism:false + isolate:false config, that mock didn't survive across files once any earlier test transitively loaded the real @/utils/intuit (every webhook test does, via auth.service.ts). With master's 7 integration files this never landed in the bad slot; with this branch's 16, it landed ~50% of the time.

Fix: moved the module mock to test/integration/setup.ts and pinned the singleton on globalThis (Vitest re-evaluates the setupFiles factory more than once per session under these flags — a naive shared mock still produces multiple vi.fn() instances). Verified with 10 consecutive full-suite runs all green. Full rationale in commit 2664c98 and in the inline comment in setup.ts.

Out of scope (separate tickets)

  • INVOICE_UPDATED routing (uses the same handler with delayMs)
  • absorbedFeeFlag (gates PAYMENT_SUCCEEDED only, not invoice.created)
  • Tax math edge cases, fee-paid-by-client line item, sparse-customer-update, excluded-product

Test plan

  • CI green (test.yml + lint.yml)
  • Local yarn test — 22 files / 101 tests passing (verified 10/10 consecutive runs locally)
  • yarn lint:check — clean
  • yarn prettier:check — clean
  • priceCreated regression — 6/6 still green
  • Reviewer to spot-check the globalThis mock pin in test/integration/setup.ts is intelligible

🤖 Generated with Claude Code

SandipBajracharya and others added 19 commits May 8, 2026 17:38
Canary test verifying the shared infra (seeders, fixture, mocks, setup
helper) wires up correctly. Asserts QB customer + invoice creation,
mapping rows persisted, and a SUCCESS sync log written. Mock defaults
required no adjustment — flow is greener than expected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The per-file vi.mock factory in expiringSweep.test.ts didn't survive
Vitest's shared module registry under pool:forks + fileParallelism:false +
isolate:false. Once any earlier test transitively loaded @/utils/intuit
(via auth.service.ts), the per-file mock no longer applied.

Moves the module mock to test/integration/setup.ts so it's installed
before any test-file imports run, matching the existing pattern for
CopilotAPI/IntuitAPI/Sentry. expiringSweep.test.ts wires its per-test
behavior via vi.mocked(Intuit.getInstance) inside beforeEach.

The mock singleton is pinned on globalThis. Vitest's setupFiles get
evaluated more than once per run when separate test files spin up
fresh module-graph contexts under this config — a naive
`vi.mock(..., () => ({ default: { getInstance: vi.fn() } }))` produces
a different vi.fn() per factory invocation, so the test-file wiring
ends up on a different mock than the one tokenRefresh.ts closed over.
Storing the singleton on globalThis (single process, since
fileParallelism is false) makes every factory invocation return the
same getInstance mock, so beforeEach wiring is what the runtime sees.

Verified: 10 consecutive full integration-suite runs pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Exists comment

- happyPath: default getAnItem(undefined, id, true) mock returned undefined,
  collapsing the mapped-product path into the Assembly Service fallback.
  Both branches happened to yield Id '999' via createItem default. The
  ItemRef assertion was passing via the wrong path. Override getAnItem to
  return a real item when queried by id and pin the mapped-product branch
  via two new assertions: createItem is invoked exactly once for
  'Assembly Service' (handleServiceItem still runs because seedHealthyPortal
  does not set serviceItemRef) and the line Description comes from
  copilot.getProduct, which only happens on the mapped path.

- invoiceAlreadyExists: corrected the comment claiming the existence
  check runs after customer resolution. It runs first, before any service
  call. Added an explicit qb_customers count = 0 assertion to make the
  invariant load-bearing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 8, 2026

OUT-3708

@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 2026

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

Project Deployment Actions Updated (UTC)
quickbooks-sync Ready Ready Preview, Comment May 8, 2026 1:47pm

Request Review

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.

1 participant