fix(backend): gracefully skip real-DB integration suite when Postgres is unreachable (Closes #760)#949
Open
CodingAngel1 wants to merge 1 commit into
Conversation
… is unreachable Closes LabsCrypt#760 - Add backend/tests/integration/_db.ts helper that probes DATABASE_URL reachability with a 2s timeout and a try/finally connection release. - Make stream-lifecycle.test.ts self-skip via ctx.skip() + early return in beforeEach when DB probe fails, with an actionable skip log. - Lazy-load PrismaClient in beforeAll; protect helpers with getDb() runtime guard so ctx.skip() paths cannot dereference null. - Add test:unit, test:integration, test:integration:docker scripts. - Wire ci.yml backend job to run `npm test -- --coverage` so skip logic and coverage config stay aligned. PR_DESCRIPTION_760.md is the PR body for the upstream PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Closes #760 — Backend integration tests fail on
mainwithPrismaClientInitializationError: Can't reach database server at 127.0.0.1:5432whenever anyone (or CI) runsnpm testagainst a freshcheckout / shell that does not have a Postgres service listening on the
expected port.
The integration suite in
backend/tests/integration/stream-lifecycle.test.tsdocuments that itrequires a real Postgres database and falls back to
postgresql://postgres:password@127.0.0.1:5432/flowfi_testwhenDATABASE_URLis unset. Before this PR, the suite importedPrismaClient, opened apg.Pool, and instantiated aPrismaPgadapterat module load — then ran all twelve tests, each of which attempted
queries against a server that might not exist. That produced confusing
backend CI failures on default branch and a poor local developer
experience.
This PR makes the suite gracefully self-skip when Postgres is
unreachable and adds explicit test scripts so contributors know exactly
what they are running.
What changed
backend/tests/integration/_db.ts(new)backend/tests/integration/stream-lifecycle.test.tsgetDb()guardbackend/package.jsontest:unit,test:integration,test:integration:dockerscripts.github/workflows/ci.ymlnpm testinstead of barenpx vitest runso coverage config and skip logic stay alignedHow the skip works
beforeAllcallsresolveDbReadiness()(new helper), which:ready: falseimmediately ifDATABASE_URLis unset, witha clear actionable message, or
2 s)pg.Clientprobe (SELECT 1) andreturns
ready: trueotherwise. The probe connection is alwaysreleased via
try { ... } finally { await client.end().catch(...) }so a half-broken Postgres does not leak sockets between local runs.
console.warn(explainSkipReason(...))prints:docker compose up -d postgres→ setDATABASE_URL→prisma db push→npm run test:integration),beforeEachcallsctx.skip()and returns immediately so therest of the hook (
cleanupDatabase(),createTestUsers(), Expresslistener on a random port,
SorobanEventWorkerconstruction) isnot executed — preventing the hooks themselves from throwing via
getDb()or leaking listeners when DB is absent.afterEachearly-returns when the suite is skipping, soserver.close()and
testPrisma.$disconnect()are never called on a never-initializedclient.
Why I chose "skip" instead of "fail"
tests behind explicit setup, and CI already has a healthy
postgres:16-alpineservice — so the suite MUST still execute underCI. A hard failure would require every new contributor to set up
Postgres just to run
npm test, including the (many) tests that donot require Postgres at all.
integration suites still run; CI still gates merge on the real suite
executing against real Postgres; and local developers get a precise,
copy-pasteable setup recipe instead of a 20-line Prisma stack trace.
New npm scripts
{ "test": "vitest run", // unchanged "test:unit": "vitest run --exclude='tests/integration/**'", "test:integration": "vitest run tests/integration", "test:integration:docker": "docker compose up -d postgres && vitest run tests/integration/stream-lifecycle.test.ts; docker compose stop postgres" }test:integration:dockeruses;(not&&) beforedocker compose stopso the container is always stopped regardless ofvitest exit code.
Type of Change
Related Issues
Closes #760
Changes Made
backend/tests/integration/_db.ts— new shared helper moduleproviding:
resolveTestDatabaseUrl()— centralizes theprocess.env.DATABASE_URL ?? "postgresql://postgres:password@127.0.0.1:5432/flowfi_test"fallback (single source of truth).
resolveDbReadiness()— async probe returning{ ready, reason, url }.explainSkipReason(readiness)— multi‑line, copy-pasteable logsurfacing env status, the local recipe, and the CI default URL.
backend/tests/integration/stream-lifecycle.test.ts— converted tolazy Prisma init:
PrismaClientis nowimport type { PrismaClient }; the runtimevalue is dynamically imported inside
beforeAllafter readiness isconfirmed.
let testPrisma: PrismaClient | null = null+getDb()runtimeguard so helpers (
cleanupDatabase,createTestUsers) cannot dereferencean uninitialized client.
beforeAllprobes first and short-circuits with the skip log; otherwiseconstructs the pool + adapter + client.
beforeEach(async (ctx) => …)callsctx.skip()and returnsbefore any DB-touching work runs.
afterEachreturns early when the suite is skipping, so no orphanlisteners, no
$disconnect()on null.backend/package.json— addedtest:unit,test:integration, andtest:integration:dockerscripts. Thetest:unitscript's--excludeglob is single-quoted so shells don't expand it..github/workflows/ci.yml—Run Backend Testsnow invokesnpm test --silent -- --coverage --reporter=basicso the same skiplogic, coverage config, and reporter behavior is in effect when the
postgres service is healthy.
Testing
Test Coverage
_db.tsprobe is unit-testable (itsdeliberate skip-vs-run contract is what the integration suite now
relies on).
is the integration suite).
above; CI will be the authoritative verification surface.
Test Steps
mainand a feature branch still spins up theexisting
postgres:16-alpineservice and runsnpm test -- --coverage;the integration suite should execute against real Postgres and pass.
DATABASE_URL:Stream Lifecycle Integration Teststo be reportedas
skipped(not failed), with the actionable skip log printed once,exit code 0.
docker compose:from vitest.
npm run test:unitruns everything excepttests/integration/**— queriable in<2 s on a warm checkout from a clean install.
Breaking Changes
None.
Screenshots/Demo
N/A (test infrastructure change).
Checklist
inline rationale referencing [BUG] Backend integration tests fail on main due to PostgreSQL connection error #760 in
_db.tsand the test file.getDb()is the only "throw"in this path; it is unreachable when the skip path is taken).
integration suite itself is the contract; its skip behavior is
exercised on every run where Postgres is unavailable.
validation deferred to CI / maintainer rerun because the local
shell in this PR builder did not expose the project's compiled
node_modules/.bin/{tsc,vitest}to follow-up commands (CI will runthe authoritative validation surface).
applicable — none.
Additional Notes
describe.skipat file load:vitest's
describe.skipis decided at file load time. We need todecide skip-at-runtime because the developer may have set
DATABASE_URLafter the test process started. AbeforeAllprobe isthe only way to make the check sensitive to actual reachability, not
just env presence.
indexer-worker.test.ts,streams.test.ts,stream-actions.test.ts,events-list.test.ts,admin-metrics.test.ts, andtop-up.test.tsmock Prisma/SSE — they do not need a real DB and were not the source of
the regression reported in [BUG] Backend integration tests fail on main due to PostgreSQL connection error #760.
.github/workflows/ci.ymlstill runs,prisma db pushstill happens,and
DATABASE_URLis still passed to the test step. The integrationsuite will still execute against real Postgres in CI; this PR only
smooths the local-experience failure mode.
stream-lifecycle.test.tsdefines 12tests across 6
describeblocks (stream_created,stream_topped_up,stream_paused,stream_resumed,stream_cancelled, stale-DB fallback, and SSE broadcast). All 12skip cleanly when DB is absent.
Suggested follow-ups (separate PRs)
tests/integration/_db.tsitself covering theunset env, unreachable host, and probe-success paths.
(
stream-actions.test.ts,events-list.test.ts, …) into aconsistent
_mocked.ts/_db.tshelper pair so the "needs Postgresvs mocked" distinction is explicit per file.