OUT-3933: set up integration test harness with testcontainers - #64
Conversation
Add a reusable Vitest integration harness modeled on the quickbooks-sync pattern: an ephemeral Postgres via testcontainers, Drizzle migrations in globalSetup, globalThis-pinned module mocks for CopilotAPI/XeroAPI/Sentry, and seed/testDb/webhook helpers. Wire test typechecking into typecheck and pre-push since test/ is excluded from the root tsconfig. 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 SummaryThis PR establishes an integration test harness for the Xero integration, modeled on the quickbooks-sync setup — no application logic changes are included. The infrastructure is well-structured: a single ephemeral Postgres container managed by testcontainers, Drizzle migrations applied once in
Confidence Score: 5/5Safe to merge — changes are entirely additive test infrastructure with no modifications to application code paths. All production source files are untouched except the schema barrel addition of two already-existing tables. The harness is well-isolated: a dedicated testcontainer, a separate tsconfig, and explicit alias stubbing for server-only. The only notable design trade-off is clearMocks:true leaving mock implementations alive between tests, which is a footgun for future test authors but harmless for the smoke test included here. vitest.config.ts — the clearMocks vs resetMocks choice will affect every future integration test that exercises mock APIs. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant VS as Vitest Runner
participant GS as globalSetup.ts
participant TC as PostgreSQL Testcontainer
participant DZ as Drizzle Migrator
participant SF as setup.ts (setupFiles)
participant T as Test File
VS->>GS: execute globalSetup()
GS->>GS: dotenv.config(.env.test, override:true)
GS->>TC: PostgreSqlContainer.start()
TC-->>GS: container (connection URI)
GS->>GS: "process.env.DATABASE_URL = uri"
GS->>DZ: migrate(db, migrationsFolder)
DZ-->>GS: migrations applied
GS->>GS: migrationClient.end()
GS-->>VS: teardown fn registered
VS->>SF: load setupFiles (once, isolate:false)
SF->>SF: vi.mock CopilotAPI / XeroAPI / Sentry / sleep
SF->>SF: pin vi.fn() constructors on globalThis
loop each test
VS->>T: beforeEach → truncateAllTestTables()
T->>T: installMockApis() [recommended]
T->>T: run test assertions
end
VS->>GS: teardown()
GS->>TC: container.stop()
%%{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 VS as Vitest Runner
participant GS as globalSetup.ts
participant TC as PostgreSQL Testcontainer
participant DZ as Drizzle Migrator
participant SF as setup.ts (setupFiles)
participant T as Test File
VS->>GS: execute globalSetup()
GS->>GS: dotenv.config(.env.test, override:true)
GS->>TC: PostgreSqlContainer.start()
TC-->>GS: container (connection URI)
GS->>GS: "process.env.DATABASE_URL = uri"
GS->>DZ: migrate(db, migrationsFolder)
DZ-->>GS: migrations applied
GS->>GS: migrationClient.end()
GS-->>VS: teardown fn registered
VS->>SF: load setupFiles (once, isolate:false)
SF->>SF: vi.mock CopilotAPI / XeroAPI / Sentry / sleep
SF->>SF: pin vi.fn() constructors on globalThis
loop each test
VS->>T: beforeEach → truncateAllTestTables()
T->>T: installMockApis() [recommended]
T->>T: run test assertions
end
VS->>GS: teardown()
GS->>TC: container.stop()
Reviews (2): Last reviewed commit: "chore(OUT-3933): add trailing new line" | Re-trigger Greptile |
Under isolate:false the seed module loads once for the whole run, so a module-level expires_at could drift into the past on a long suite and flip the seeded token to expired. Build it inside the seeder instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add syncLogs and failedSyncs to the schema barrel so it's complete, then derive truncateAllTestTables from Object.values(schema). New tables are now truncated automatically instead of needing a second manual update. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Reusable Vitest integration test harness for the Xero integration, modeled on the quickbooks-sync setup. Infra only — a smoke test verifies the harness boots; per-flow tests (e.g.
product.created) come next.What it provides
globalSetupbefore any worker starts;DATABASE_URLset dynamically and inherited by the test fork.pool: forks,maxWorkers: 1,fileParallelism: false,isolate: false);truncateAllTestTables(schema-derived) resets state inbeforeEach.sleep, pinned onglobalThisfor the shared-state run mode.clearMocks: truekeeps call-history clean between tests.seed(typed row seeders + constants),webhook(drives/api/webhookvianext-test-api-route-handler),mocks(mock API factories).server-onlyaliased to an empty stub (it throws at import outside an RSC context).Notes
.env.testholds non-secret stubs only and is un-ignored (!.env.test) so the harness works on clone.test/is excluded from the root tsconfig;pnpm typecheckandpre-pushnow also typecheck thetest/project so the typed mocks/seeders are gated.Verification
pnpm typecheck— clean (src + test)biome check— cleanpnpm test— 3/3 passing (container boots, migrations apply, seed/truncate work)🤖 Generated with Claude Code