Skip to content

OUT-3946: add integration tests for product.created webhook - #65

Merged
SandipBajracharya merged 5 commits into
mainfrom
OUT-3946
Jul 2, 2026
Merged

OUT-3946: add integration tests for product.created webhook#65
SandipBajracharya merged 5 commits into
mainfrom
OUT-3946

Conversation

@SandipBajracharya

@SandipBajracharya SandipBajracharya commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Integration tests for the product.created webhook flow, built on the testcontainers harness from OUT-3933 (#64).

Cases covered

  • happy path — creates a Xero item, maps it in synced_items, writes a success sync_log.
  • syncProductsAutomatically=false — service-level gate: 200, no Xero call, no rows.
  • isSyncEnabled=false — controller-level gate (short-circuits before dispatch): 200, no Xero call, no rows.
  • already mapped — pre-check short-circuits, no Xero call, seeded row unchanged, no log.
  • createItems fails — no mapping row, FAILED sync_log + failed_syncs record, returns 500.

Harness additions

  • productCreatedTestSetup.ts — per-flow beforeEach (truncate + mock install) returning a live { copilot, xero } handle.
  • productCreated.webhook.ts fixture, seedSyncedItem seeder, and a createItems default on the Xero mock.

Notes

  • The two green guard tests were confirmed to fail-for-the-right-reason (watched them go red with the guard defeated).
  • The onConflictDoNothing orphan-cleanup race branch is intentionally not covered — it only fires on a true insert-time race and can't be triggered deterministically (documented in idempotency.test.ts).

Verification

  • pnpm typecheck (src + test) — clean
  • biome check — clean
  • pnpm test — 8/8 passing (6 files)

🤖 Generated with Claude Code

Cover the product.created flow end-to-end on the testcontainers harness:
happy path (Xero item created, synced_items row, success sync_log), both
sync-disabled gates (workspace isSyncEnabled + automatic product sync),
already-mapped idempotency, and Xero createItems failure (FAILED sync_log +
failed_syncs, 500). Adds a per-flow setup helper, a webhook fixture, a
seedSyncedItem seeder, and a createItems mock default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 1, 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 5:19am

Request Review

@linear-code

linear-code Bot commented Jul 1, 2026

Copy link
Copy Markdown

OUT-3946

@supabase

supabase Bot commented Jul 1, 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 ↗︎.

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

Greptile Summary

Adds integration tests for the product.created webhook flow on top of the testcontainers harness from #64. All five cases (happy path, idempotency, two guard gates, and Xero API failure) are covered with real DB assertions.

  • test/helpers/productCreatedTestSetup.ts introduces a beforeEach wrapper that truncates tables and installs fresh mocks; mocks.ts gains a createItems default that returns unique UUIDs per item, and seed.ts adds TEST_PRODUCT_ID / TEST_XERO_ITEM_ID constants with correct v4 UUID format.
  • test/integration/webhook/productCreated/ — five new test files; previous review comments addressed: failedSyncs is now asserted empty in both the happy-path and idempotency tests, and the batch-mock ID collision is fixed by the index-based UUID generation.

Confidence Score: 5/5

Test-only change; no production code is touched. All five branches of the webhook flow are covered by real DB assertions against a containerised Postgres instance.

Every changed file is a test helper or test case. The previous round's feedback (unique mock IDs for batch calls, failedSyncs assertion on happy path and idempotency) has been fully addressed. UUID constants are now valid v4 format so schema validation passes. No logic, no migrations, no production risk.

No files require special attention.

Important Files Changed

Filename Overview
test/helpers/mocks.ts Adds createItems default to the Xero mock with index-based unique UUID generation; addresses previous batch-mock ID collision feedback.
test/helpers/productCreatedTestSetup.ts New per-flow beforeEach helper; mutable handle pattern is safe because tests only access it inside it() blocks, which run after beforeEach populates it.
test/helpers/seed.ts Updates TEST_TENANT_ID / TEST_INTERNAL_USER_ID to valid v4 UUIDs and adds TEST_PRODUCT_ID, TEST_XERO_ITEM_ID, TEST_OTHER_XERO_ITEM_ID + seedSyncedItem.
test/integration/webhook/productCreated/happyPath.test.ts Happy-path test covers Xero call args, synced_items mapping, sync_log fields, and asserts failedSyncs is empty.
test/integration/webhook/productCreated/idempotency.test.ts Verifies already-mapped products skip Xero, leave the existing row unchanged, and write no log or failure records.
test/integration/webhook/productCreated/xeroCreateItemFails.test.ts Injects a rejecting createItems mock via optsFactory; asserts no mapping row, FAILED sync_log, and a failed_syncs retry record on 500.
test/integration/webhook/productCreated/isSyncDisabled.test.ts Controller-level guard test: isSyncEnabled=false yields 200 with no Xero call and no DB rows across all three tables.
test/integration/webhook/productCreated/syncProductsAutomaticallyDisabled.test.ts Service-level guard test: syncProductsAutomatically=false yields 200 with no Xero call and no DB rows.
test/fixtures/productCreated.webhook.ts Minimal fixture exporting a ProductCreatedWebhookSchema-typed payload referencing TEST_PRODUCT_ID.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[POST /api/webhook] --> B{isSyncEnabled?}
    B -- false --> C[200 no DB writes]
    B -- true --> D{syncProductsAutomatically?}
    D -- false --> E[200 no DB writes]
    D -- true --> F{product already in synced_items?}
    F -- yes --> G[200 row unchanged]
    F -- no --> H[XeroAPI.createItems]
    H -- throws --> I[500 sync_log FAILED failed_syncs row]
    H -- success --> J[insert synced_items sync_log SUCCESS 200]
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"}}}%%
flowchart TD
    A[POST /api/webhook] --> B{isSyncEnabled?}
    B -- false --> C[200 no DB writes]
    B -- true --> D{syncProductsAutomatically?}
    D -- false --> E[200 no DB writes]
    D -- true --> F{product already in synced_items?}
    F -- yes --> G[200 row unchanged]
    F -- no --> H[XeroAPI.createItems]
    H -- throws --> I[500 sync_log FAILED failed_syncs row]
    H -- success --> J[insert synced_items sync_log SUCCESS 200]
Loading

Reviews (2): Last reviewed commit: "docs(OUT-3946): simplify test helper and..." | Re-trigger Greptile

Comment thread test/helpers/mocks.ts
Comment thread test/integration/webhook/productCreated/happyPath.test.ts
Comment thread test/integration/webhook/productCreated/idempotency.test.ts
@SandipBajracharya SandipBajracharya changed the title test(OUT-3946): add integration tests for product.created webhook OUT-3946: add integration tests for product.created webhook Jul 2, 2026
SandipBajracharya and others added 3 commits July 2, 2026 10:19
createItems now gives each batch item its own uuid (first stays
TEST_XERO_ITEM_ID), mirroring real Xero instead of repeating one id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Happy-path and already-mapped tests now check failed_syncs has no rows, so a
stray failure record can't slip through, matching the negative-path tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shorten verbose block comments to plain one-liners.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SandipBajracharya

Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread test/helpers/seed.ts Outdated
Comment on lines +9 to +19
// Valid v4 uuids (version nibble 4, variant nibble 8) — z.uuid() validates both.
export const TEST_TENANT_ID = '11111111-1111-4111-8111-111111111111'
export const TEST_INTERNAL_USER_ID = '22222222-2222-4222-8222-222222222222'
export const TEST_WEBHOOK_TOKEN = 'test-token-xyz'
export const TEST_ACCESS_TOKEN = 'test-access-token'
export const TEST_REFRESH_TOKEN = 'test-refresh-token'
// Copilot product id and the Xero item id it maps to (both stored in uuid columns).
export const TEST_PRODUCT_ID = '33333333-3333-4333-8333-333333333333'
export const TEST_XERO_ITEM_ID = '44444444-4444-4444-8444-444444444444'
// A second Xero item id, for asserting a pre-existing mapping is left untouched.
export const TEST_OTHER_XERO_ITEM_ID = '99999999-9999-4999-8999-999999999999'

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.

I think exporting all these constants individually will only make it harder. We need to understand each thing. What if we had single config or const object. Or even multiple objects that holds similiar data together? Might be more intuitive to use them. wdyt?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes it make sense. I will create a separate config file and export objects with similar data. Thanks.

Move the scattered TEST_* constants out of seed.ts into test/helpers/constants.ts,
grouped by domain (TEST_PORTAL, TEST_TOKENS, TEST_PRODUCT, TEST_XERO_ITEM). Files
that only need an id no longer pull in the DB-heavy seed module.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@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 85bfb58 into main Jul 2, 2026
5 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