Skip to content

test(testcontainers): add a DB connection-blip harness - #11

Open
anurag6569201 wants to merge 1 commit into
qa/agent-triggerdotdev-trigger-dev/pr-04-4862/basefrom
qa/agent-triggerdotdev-trigger-dev/pr-04-4862/head
Open

test(testcontainers): add a DB connection-blip harness#11
anurag6569201 wants to merge 1 commit into
qa/agent-triggerdotdev-trigger-dev/pr-04-4862/basefrom
qa/agent-triggerdotdev-trigger-dev/pr-04-4862/head

Conversation

@anurag6569201

Copy link
Copy Markdown

Summary

Adds a test-only harness for simulating a Postgres connection blip, so tests can prove their database code survives a dropped connection. It exports createDbBlipController and a postgresBlipTest fixture from @internal/testcontainers.

How it works

The harness severs connections from a separate admin connection using pg_terminate_backend, scoped to the test's own database, so it composes with any Prisma client under test and stays isolated across parallel tests. Two modes:

  • severIdle() kills idle backends. A pooled (driver-adapter) client absorbs this transparently: the pool evicts the dead connection and the next query just works.
  • severDuringNextStatement() kills a statement mid-flight, surfacing a connection error to the caller; the client then recovers on the next retry.

The bundled tests demonstrate both, plus the correctness property that matters before adding retries anywhere: a non-idempotent write double-applies when retried after a post-commit blip, while an idempotent write (deterministic id plus ON CONFLICT) stays at exactly one row.

Source merge-base: 43ecf15f80277c0eb931c08ddd89de55179cc795
Source head: a0da053d88d3f3fe9d221f0b950c94075fccabc1

@shipwright-agent

Copy link
Copy Markdown

⚠️ Shipwright · Approve with conditions

Recommendation: approve PR #11 with conditions · Tier T3
Checks: 0 total · 0 needing attention

Next step: an authorized approver must satisfy the approval condition.

Findings (6)

  • HIGH 'withRetry' is a local reimplementation of a shared retry utility, with a comment admitting it is a stand-in. · internal-packages/testcontainers/src/dbBlip.test.ts:7
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The 'blipFromContainer' helper is exported indirectly through 'postgresBlipTest' but its type signature mixes 'StartedPostgreSqlContainer' and 'TestContext' in a way that obscures · internal-packages/testcontainers/src/index.ts:358
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The literal-match regression test is racy: the slow query is started before the sever call, but the test only awaits the sever rejection and then disposes the client before awaitin · internal-packages/testcontainers/src/dbBlip.test.ts:160
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The non-idempotent write test asserts 'countTag(...) === 2', which depends on the retry actually re-executing the INSERT after the severed 'pg_sleep'. · internal-packages/testcontainers/src/dbBlip.test.ts:120
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH 'severIdle' and 'severDuringNextStatement' use 'pg_terminate_backend' against all client backends except the admin connection. · internal-packages/testcontainers/src/dbBlip.ts:55
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The admin connection swallows all 'error' events with an empty handler. · internal-packages/testcontainers/src/dbBlip.ts:48
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

Conditions

  • human approval required (T3): apply the approval label

Fireworks usage: 13,527 input · 686 output · 14,213 total tokens · $0.0034 · 14s · 0 fix iteration(s)

Open the Shipwright check for full evidence and the audit bundle. Use /shipwright rerun to verify again.

import { PrismaClient } from "@trigger.dev/database";
import { postgresBlipTest } from "./index";

// A minimal infra retry, standing in for the shared read-retry util so this

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

'withRetry' is a local reimplementation of a shared retry utility, with a comment admitting it is a stand-in.

Impact: 'withRetry' is a local reimplementation of a shared retry utility, with a comment admitting it is a stand-in. This duplicates behavior and will drift from the real utility; a new hire cannot tell whether the retry semantics here match production.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

}
);

export type PostgresBlipTestContext = PostgresTestContext & { blip: DbBlipController };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The 'blipFromContainer' helper is exported indirectly through 'postgresBlipTest' but its type signature mixes 'StartedPostgreSqlContainer' and 'TestContext' in a way that obscures

Impact: The 'blipFromContainer' helper is exported indirectly through 'postgresBlipTest' but its type signature mixes 'StartedPostgreSqlContainer' and 'TestContext' in a way that obscures which fields are actually available. The 'TestContext' intersection appears unused and will confuse readers about the contract.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

// wildcard `_`) would wrongly match and terminate it. The literal matcher must not,
// so the sever times out instead of killing the wrong statement.
postgresBlipTest(
"severDuringNextStatement matches queryContains literally, not as an ILIKE pattern",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The literal-match regression test is racy: the slow query is started before the sever call, but the test only awaits the sever rejection and then disposes the client before awaitin

Impact: The literal-match regression test is racy: the slow query is started before the sever call, but the test only awaits the sever rejection and then disposes the client before awaiting 'slow'. If the query is still running when 'dispose()' runs, 'pool.end()' may hang or the catch may never settle, causing a flaky timeout or unhandled rejection.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

let nonIdempotentAttempts = 0;
const nonIdempotentWrite = async () => {
nonIdempotentAttempts++;
await client.$executeRawUnsafe(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The non-idempotent write test asserts 'countTag(...) === 2', which depends on the retry actually re-executing the INSERT after the severed 'pg_sleep'.

Impact: The non-idempotent write test asserts 'countTag(...) === 2', which depends on the retry actually re-executing the INSERT after the severed 'pg_sleep'. If the sever lands before the INSERT commits, the retry produces 1 row and the test fails; if the sever lands after the sleep completes, the first attempt succeeds and the test also fails. This makes the test timing-dependent and likely flaky in CI.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

`SELECT pg_terminate_backend(pid) AS terminated
FROM pg_stat_activity
WHERE datname = current_database()
AND pid <> pg_backend_pid()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

'severIdle' and 'severDuringNextStatement' use 'pg_terminate_backend' against all client backends except the admin connection.

Impact: 'severIdle' and 'severDuringNextStatement' use 'pg_terminate_backend' against all client backends except the admin connection. If a test database is shared or a non-test client connects with a different 'application_name', this harness will terminate unrelated backends, potentially disrupting other tests or local development sessions.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

await admin.connect();
// Swallow async connection errors so a consumer that severs a DB the admin
// isn't excluded from (or drops it while open) can't crash the test worker.
admin.on("error", () => {});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The admin connection swallows all 'error' events with an empty handler.

Impact: The admin connection swallows all 'error' events with an empty handler. This can hide authentication failures, connection drops, or protocol errors during setup, causing the harness to fail later with misleading 'no active statement' errors instead of surfacing the real cause.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

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