Skip to content

[Test Infra] cleanupDatabase() TRUNCATE silently no-ops — integration-test tenant rows accumulate across suites #2205

Description

@ToddHebebrand

Description

cleanupDatabase() in apps/api/src/__tests__/integration/setup.ts attempts a per-test TRUNCATE of partners/organizations with CASCADE, but the statement never succeeds: the cascade transitively reaches audit_logs, whose audit_log_block_truncate trigger (apps/api/migrations/2026-05-25-k-audit-log-block-truncate.sql) unconditionally rejects any TRUNCATE touching the table — the append-only bypass GUC (breeze.allow_audit_retention) only exists for DELETE. The whole TRUNCATE fails and the error is swallowed by cleanupDatabase()'s try/catch, so tenant rows accumulate across every suite in an integration run.

No existing suite noticed because they all scope assertions to fixture IDs they created. It surfaced twice while building the loginContext integration suite for #2202 (the first suite to assert on global partner state):

  1. The endpoint counts ALL partner rows, so leftover partners from earlier suites broke the single-partner case.
  2. The first workaround (bare DELETE FROM organizations/partners) was green locally but red in CI — earlier suites leave rows in FK-child tables without ON DELETE CASCADE (backup_configs, 23503), so plain DELETEs are order-dependent.

Root Cause

  • audit_log_block_truncate trigger rejects TRUNCATE unconditionally (by design — append-only enforcement).
  • cleanupDatabase() catches and ignores the resulting failure, turning "reset the DB" into a silent no-op for the tenant-root tables.

Proposed Fix

The working pattern (shipped in loginContext.integration.test.ts via #2202, commit 38f6675) is:

await testDb.execute(sql`ALTER TABLE audit_logs DISABLE TRIGGER audit_log_block_truncate`);
try {
  await testDb.execute(sql`TRUNCATE TABLE partners, organizations CASCADE`);
} finally {
  await testDb.execute(sql`ALTER TABLE audit_logs ENABLE TRIGGER audit_log_block_truncate`);
}

(The test-DB user owns the table, so DISABLE TRIGGER is permitted.)

Options for the general fix, in rough order of preference:

  1. Move this pattern into cleanupDatabase() itself — but audit first whether any existing suite depends on the broken behavior (e.g. beforeAll fixtures that a suddenly-working per-test truncate would wipe mid-suite). This is the risky part and why fix(sso): post-merge review follow-ups for partner SSO + login branding (#2194) #2202 kept the fix suite-local.
  2. Keep cleanupDatabase() as-is but stop swallowing the error silently — log it once per run so the no-op is at least visible.
  3. Export the trigger-aware truncate as a helper in setup.ts for suites that need genuine global resets, and document that cleanupDatabase() does not reset tenant roots.

At minimum, option 2 (surface the swallowed error) should happen — a cleanup function that silently does nothing is how this stayed hidden.

Affected Files

  • apps/api/src/__tests__/integration/setup.ts (primary — cleanupDatabase())
  • apps/api/src/__tests__/integration/loginContext.integration.test.ts (reference implementation of the workaround)
  • apps/api/migrations/2026-05-25-k-audit-log-block-truncate.sql (the trigger, working as designed)

Reported By

Found during the post-merge review follow-up work for #2194 (PR #2202).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions