// apps/hq/test/db-resolve.test.ts — pre-ship audit FIX A: server.ts and the deploy scripts // (apps/hq/scripts/apply-onboarding-pipeline.ts, apps/hq/scripts/migrate-onboarding-templates.ts) // must all resolve the DB engine + target through the ONE shared function. Before this fix // the scripts computed it as `DATABASE_URL ?? ''` (no production fallback), so on the // production box — where DATABASE_URL is unset — each script silently opened a brand-new // empty SQLite file instead of the real Postgres. import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { describe, it, expect } from 'vitest' import { pgTargetDescription, requireDbUrlForEnv, resolveDbUrl } from '../src/db-pg' const HARDCODED = 'postgres://u:p@h:5432/db' describe('resolveDbUrl (D19 engine selection, the one shared resolver)', () => { it('DATABASE_URL wins regardless of NODE_ENV', () => { expect(resolveDbUrl({ DATABASE_URL: 'postgres://x/y', NODE_ENV: 'production' })).toBe('postgres://x/y') expect(resolveDbUrl({ DATABASE_URL: 'postgres://x/y' })).toBe('postgres://x/y') }) it('falls back to the hardcoded production URL only when NODE_ENV=production and DATABASE_URL is unset', () => { expect(resolveDbUrl({ NODE_ENV: 'production' }, HARDCODED)).toBe(HARDCODED) }) it('resolves to SQLite (empty string) outside production with no DATABASE_URL — dev/tests stay green', () => { expect(resolveDbUrl({})).toBe('') expect(resolveDbUrl({ NODE_ENV: 'development' })).toBe('') expect(resolveDbUrl({ NODE_ENV: 'test' })).toBe('') }) }) describe('requireDbUrlForEnv (deploy-script guard — OUTCOME-based, not NODE_ENV-based)', () => { it('behaves exactly like resolveDbUrl when a Postgres target is available (no SQLite file check runs at all)', () => { expect(requireDbUrlForEnv({ DATABASE_URL: 'postgres://x/y' })).toBe('postgres://x/y') expect(requireDbUrlForEnv({ NODE_ENV: 'production' }, HARDCODED)).toBe(HARDCODED) }) it('resolves to SQLite without hard-failing when the target file already exists (mocked fs check)', () => { expect(requireDbUrlForEnv({}, undefined, () => true)).toBe('') // Even NODE_ENV=production is fine as long as the SQLite file it would land on already exists. expect(requireDbUrlForEnv({ NODE_ENV: 'production' }, '', () => true)).toBe('') }) it(':memory: never hard-fails — the guard never even calls the file-existence check', () => { expect( requireDbUrlForEnv({ HQ_DATA_DIR: ':memory:' }, undefined, () => { throw new Error('must not be called for :memory:') }), ).toBe('') }) it('refuses when resolution selects SQLite and the target does not exist — regardless of NODE_ENV', () => { // This is the original prod bug: an operator shell has no NODE_ENV set at all, so the old // NODE_ENV==='production' gate never fired. The guard must fire on the OUTCOME instead. expect(() => requireDbUrlForEnv({}, undefined, () => false)).toThrow(/refusing to run/i) expect(() => requireDbUrlForEnv({}, undefined, () => false)).toThrow(/CREATE a new SQLite database/) // Also still refuses in the NODE_ENV=production case (hardcoded emptied out + no existing file). expect(() => requireDbUrlForEnv({ NODE_ENV: 'production' }, '', () => false)).toThrow(/refusing to run/i) }) }) describe('requireDbUrlForEnv — real filesystem (the actual script entry path, no mocks)', () => { it('refuses a non-existent SQLite path', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'hq-db-resolve-')) try { expect(() => requireDbUrlForEnv({ HQ_DATA_DIR: dir })).toThrow(/refusing to run/i) } finally { fs.rmSync(dir, { recursive: true, force: true }) } }) it('accepts an existing SQLite path', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'hq-db-resolve-')) try { fs.writeFileSync(path.join(dir, 'hq.db'), '') expect(requireDbUrlForEnv({ HQ_DATA_DIR: dir })).toBe('') } finally { fs.rmSync(dir, { recursive: true, force: true }) } }) it(':memory: still works with no filesystem check', () => { expect(requireDbUrlForEnv({ HQ_DATA_DIR: ':memory:' })).toBe('') }) }) describe('pgTargetDescription (never leaks the password into logs)', () => { it('extracts host:port/dbname', () => { expect(pgTargetDescription('postgres://user:secret@db.example.com:5432/hq')) .toBe('db.example.com:5432/hq') }) it('omits the port when absent', () => { expect(pgTargetDescription('postgres://user:secret@db.example.com/hq')).toBe('db.example.com/hq') }) it('never includes the credentials', () => { expect(pgTargetDescription('postgres://user:hunter2@host/db')).not.toContain('hunter2') }) it('degrades gracefully on an unparsable string', () => { expect(pgTargetDescription('not-a-url')).toMatch(/unparsable/i) }) })