// apps/hq/test/backup.test.ts — `npm run backup` (D33) must find the PRODUCTION database. // It used to read process.env.DATABASE_URL directly and exit when it was unset — but on the // prod box DATABASE_URL is not exported; the connection is RESOLVED (D19). So the backup // command could never reach the real data. It now goes through the one shared resolver, and // a SQLite outcome is a loud error rather than a pg_dump against a file it cannot read. import { describe, it, expect } from 'vitest' import { backupFileName, dumpsToRotate, resolveBackupTarget } from '../src/backup' describe('resolveBackupTarget (same resolver as the server + every maintenance script)', () => { it('uses DATABASE_URL when it is set', () => { expect(resolveBackupTarget({ DATABASE_URL: 'postgres://x/y' })).toBe('postgres://x/y') }) it('resolves the production target with no DATABASE_URL exported — the whole point of the fix', () => { // NODE_ENV=production with no DATABASE_URL used to be an instant "DATABASE_URL is not set" // exit; it must now resolve to the same Postgres the server picks. const url = resolveBackupTarget({ NODE_ENV: 'production' }) expect(url).toMatch(/^postgres:\/\//) }) it('refuses (never silently no-ops) when nothing resolves a target', () => { expect(() => resolveBackupTarget({})).toThrow(/refusing to run/i) }) it('says WHY, naming pg_dump, the SQLite path it landed on, and the SQLite snapshot script', () => { const err = (() => { try { resolveBackupTarget({ HQ_DATA_DIR: 'D:/hq-data' }); return '' } catch (e) { return String(e) } })() expect(err).toMatch(/pg_dump/) expect(err).toMatch(/backup-hq\.sh/) expect(err).toMatch(/hq\.db/) expect(err).toMatch(/DATABASE_URL=postgres/) }) }) describe('backup file naming + rotation', () => { it('names dumps hq-YYYYMMDD-HHMMSS.dump', () => { expect(backupFileName(new Date(2026, 6, 19, 2, 5, 9))).toBe('hq-20260719-020509.dump') }) it('rotates out everything past the newest KEEP, ignoring unrelated files', () => { const files = [ 'hq-20260101-000000.dump', 'hq-20260102-000000.dump', 'hq-20260103-000000.dump', 'notes.txt', 'hq-old.dump', ] expect(dumpsToRotate(files, 2)).toEqual(['hq-20260101-000000.dump']) expect(dumpsToRotate(files, 14)).toEqual([]) }) it('keeps at least one dump however low KEEP is set', () => { expect(dumpsToRotate(['hq-20260101-000000.dump'], 0)).toEqual([]) }) })