Run scheduled backups in an isolated subprocess
This commit is contained in:
@@ -35,6 +35,7 @@ test("WAL snapshots restore committed progress, revoke sessions, replicate, and
|
||||
expect(readdirSync(config.replica!)).toHaveLength(2);
|
||||
expect(readdirSync(config.directory)).toContain("keep.txt");
|
||||
const runner = startBackups(f.sqlite, config);
|
||||
await runner.ready;
|
||||
expect(runner.state.failed).toBe(false); expect(runner.state.lastSuccess).toBeGreaterThan(0); runner.stop();
|
||||
} finally { f.close(); rmSync(directory, { recursive: true, force: true }); }
|
||||
});
|
||||
@@ -71,3 +72,47 @@ test("backup and restore CLIs work against a live database without overwriting i
|
||||
expect(f.sqlite.query('SELECT count(*) AS n FROM sessions').get()).toEqual({ n: 2 });
|
||||
} finally { f.close(); rmSync(directory, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('scheduled backups yield to the event loop, retain WAL data, and verify replicas', async () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), 'minabot-async-backup-'));
|
||||
const path = join(directory, 'live.sqlite'), f = fixture(path);
|
||||
let runner: ReturnType<typeof startBackups> | undefined;
|
||||
try {
|
||||
f.sqlite.exec('PRAGMA journal_mode=WAL');
|
||||
await f.json('/habits', 'POST', { name: 'Async snapshot', method: 'manual' }, 201);
|
||||
const config = backupConfig(path, { BACKUP_REPLICA_DIR: 'replica' });
|
||||
mkdirSync(config.replica!);
|
||||
let yielded = false;
|
||||
const timer = new Promise<void>(resolve => setTimeout(() => { yielded = true; resolve(); }, 0));
|
||||
runner = startBackups(path, config);
|
||||
expect(runner.state.lastSuccess).toBe(0);
|
||||
await runner.ready;
|
||||
expect(yielded).toBe(true); await timer;
|
||||
expect(runner.state.failed).toBe(false);
|
||||
expect(runner.state.lastSuccess).toBeGreaterThan(0);
|
||||
const snapshots = readdirSync(config.directory).filter(file => file.endsWith('.sqlite'));
|
||||
expect(snapshots).toHaveLength(1);
|
||||
const check = new Database(join(config.replica!, snapshots[0]!), { readonly: true });
|
||||
try { expect(check.query('SELECT count(*) AS n FROM habits').get()).toEqual({ n: 1 }); } finally { check.close(); }
|
||||
await runner.tick();
|
||||
expect(readdirSync(config.directory).filter(file => file.endsWith('.sqlite'))).toHaveLength(1);
|
||||
} finally { runner?.stop(); f.close(); rmSync(directory, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('scheduled backup ticks coalesce, expose failure and recovery, and stop admitting work', async () => {
|
||||
const config = backupConfig('/tmp/test-only.sqlite', {});
|
||||
let calls = 0, release!: (value: number) => void;
|
||||
const run = async () => { calls++; return new Promise<number>(resolve => { release = resolve; }); };
|
||||
const runner = startBackups('/tmp/test-only.sqlite', config, run);
|
||||
expect(runner.tick()).toBe(runner.ready);
|
||||
expect(calls).toBe(1);
|
||||
release(123); await runner.ready;
|
||||
expect(runner.state.lastSuccess).toBe(123);
|
||||
const second = runner.tick(); expect(calls).toBe(2);
|
||||
runner.stop(); release(456); await second;
|
||||
await runner.tick(); expect(calls).toBe(2);
|
||||
const failed = startBackups('/tmp/test-only.sqlite', config, async () => { throw new Error('test failure'); });
|
||||
await failed.ready; expect(failed.state.failed).toBe(true); failed.stop();
|
||||
const disabled = startBackups('/tmp/test-only.sqlite', { ...config, enabled: false }, run);
|
||||
await disabled.ready; expect(calls).toBe(2); disabled.stop();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user