/** * Session 64 — nightly backup scheduler. * * The S62 backup script was mechanism-verified but never installed, so the * database went unbacked every night. These lock the arming semantics that make * "deploy == installed" true, and the honesty rules around durability. */ const sched = require('../../src/backupScheduler'); describe('arming (opt-OUT, because opt-in is what failed)', () => { test('armed whenever SUPABASE_DB_URL is present', () => { expect(sched.shouldArm({ SUPABASE_DB_URL: 'postgres://x' }).armed).toBe(true); }); test('BACKUP_CRON=0 is the kill switch', () => { const r = sched.shouldArm({ SUPABASE_DB_URL: 'postgres://x', BACKUP_CRON: '0' }); expect(r.armed).toBe(false); expect(r.reason).toMatch(/kill switch/); }); test('no SUPABASE_DB_URL → disarmed with a reason (nothing to dump)', () => { const r = sched.shouldArm({}); expect(r.armed).toBe(false); expect(r.reason).toMatch(/SUPABASE_DB_URL/); }); test('does NOT require an opt-in env var to arm', () => { // The whole S62 failure: BACKUP_CRON=1 was never set by anyone. expect(sched.shouldArm({ SUPABASE_DB_URL: 'postgres://x' }).armed).toBe(true); }); }); describe('durability honesty', () => { test('warns when neither a volume nor an off-box target is configured', () => { expect(sched.durabilityWarning({})).toMatch(/ephemeral/); }); test('no warning once BACKUP_REMOTE is set', () => { expect(sched.durabilityWarning({ BACKUP_REMOTE: 'u1@box:vyndr/' })).toBeNull(); }); test('no warning once BACKUP_DIR is set (persistent volume)', () => { expect(sched.durabilityWarning({ BACKUP_DIR: '/var/backups/vyndr' })).toBeNull(); }); }); describe('scheduling', () => { const env = { SUPABASE_DB_URL: 'postgres://x', BACKUP_REMOTE: 'u1@box:v/' }; test('runs at the configured minute, and only once per day', async () => { const runs = []; const at = (h, m, day = 19) => new Date(Date.UTC(2026, 6, day, h, m)); let clock = at(3, 10); const s = sched.startBackupScheduler({ env, now: () => clock, notify: async () => {}, runBackup: async () => { runs.push(clock.toISOString()); return { ok: true, code: 0, ms: 1 }; }, }); await s.tick(); await s.tick(); // same minute again — must not re-run expect(runs).toHaveLength(1); clock = at(3, 10, 20); // next day await s.tick(); expect(runs).toHaveLength(2); }); test('does nothing outside the scheduled minute', async () => { const runs = []; const s = sched.startBackupScheduler({ env, now: () => new Date(Date.UTC(2026, 6, 19, 14, 0)), notify: async () => {}, runBackup: async () => { runs.push(1); return { ok: true, code: 0, ms: 1 }; }, }); await s.tick(); expect(runs).toHaveLength(0); }); test('a failed backup pages high priority — silence is the danger', async () => { const alerts = []; const s = sched.startBackupScheduler({ env, now: () => new Date(Date.UTC(2026, 6, 19, 3, 10)), notify: async (msg, opts) => { alerts.push({ msg, opts }); }, runBackup: async () => ({ ok: false, code: 2, ms: 5 }), }); await s.tick(); expect(alerts).toHaveLength(1); expect(alerts[0].opts.priority).toBe('high'); expect(alerts[0].msg).toMatch(/FAILED/); }); test('disarmed scheduler returns null and schedules nothing', () => { expect(sched.startBackupScheduler({ env: {}, notify: async () => {} })).toBeNull(); }); }); describe('countRowsInDump — reading the backup back', () => { const { EventEmitter } = require('events'); const { Readable } = require('stream'); function fakeSpawn(stdoutText, code = 0) { return () => { const child = new EventEmitter(); child.stdout = Readable.from([stdoutText]); child.stderr = Readable.from([]); child.stdout.on('end', () => setImmediate(() => child.emit('close', code))); return child; }; } const COPY_BLOCK = [ '--', 'COPY public.ledger_entries (id, player_name) FROM stdin;', '1\tAlonso', '2\tHenderson', '3\tReese', '\\.', '', ].join('\n'); test('counts only the rows inside the COPY block', async () => { const r = await sched.countRowsInDump('/x.dump', 'ledger_entries', { spawn: fakeSpawn(COPY_BLOCK) }); expect(r.ok).toBe(true); expect(r.rows).toBe(3); }); test('an empty dump reports 0 rows, not a false success count', async () => { const empty = 'COPY public.ledger_entries (id) FROM stdin;\n\\.\n'; const r = await sched.countRowsInDump('/x.dump', 'ledger_entries', { spawn: fakeSpawn(empty) }); expect(r.rows).toBe(0); }); test('a failing pg_restore is reported, never counted as ok', async () => { const r = await sched.countRowsInDump('/x.dump', 'ledger_entries', { spawn: fakeSpawn('', 1) }); expect(r.ok).toBe(false); }); }); describe('latestDump', () => { test('picks the newest dump and ignores unrelated files', () => { const fs = { readdirSync: () => ['vyndr-old.dump', 'notes.txt', 'vyndr-new.dump'], statSync: (p) => ({ size: p.includes('new') ? 999 : 111, mtimeMs: p.includes('new') ? 200 : 100 }), }; const d = sched.latestDump('/app/backups', { fs }); expect(d.file).toBe('vyndr-new.dump'); expect(d.size).toBe(999); }); test('missing directory degrades to null, never throws', () => { const fs = { readdirSync: () => { throw new Error('ENOENT'); } }; expect(sched.latestDump('/nope', { fs })).toBeNull(); }); });