64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
|
|
/**
|
||
|
|
* Unit test for `runPgDump` in backup.service.ts.
|
||
|
|
*
|
||
|
|
* Regression: the dump promise must resolve once BOTH (a) the child process
|
||
|
|
* exits 0 and (b) the output file is fully flushed — regardless of which event
|
||
|
|
* fires first. The original implementation attached the file's `finish`
|
||
|
|
* listener *inside* the child `close` handler, but `stdout.pipe(out)`
|
||
|
|
* auto-ends the file when the child's stdout closes, so `finish` frequently
|
||
|
|
* fired before the listener was attached → the promise hung forever (observed
|
||
|
|
* end-to-end against a real pg_dump).
|
||
|
|
*
|
||
|
|
* We drive the spawn with `node` instead of `pg_dump` (injected via opts) so
|
||
|
|
* the test is deterministic and needs no database.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { readFileSync, mkdtempSync, rmSync } from 'node:fs';
|
||
|
|
import { tmpdir } from 'node:os';
|
||
|
|
import path from 'node:path';
|
||
|
|
|
||
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||
|
|
|
||
|
|
import { runPgDump } from '@/lib/services/backup.service';
|
||
|
|
|
||
|
|
describe('runPgDump', () => {
|
||
|
|
let dir: string;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
dir = mkdtempSync(path.join(tmpdir(), 'pn-pgdump-'));
|
||
|
|
});
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
rmSync(dir, { recursive: true, force: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('resolves and writes the child stdout to the output file on exit 0', async () => {
|
||
|
|
const out = path.join(dir, 'a.dump');
|
||
|
|
await runPgDump('ignored://url', out, {
|
||
|
|
command: process.execPath,
|
||
|
|
buildArgs: () => ['-e', 'process.stdout.write("DUMP-CONTENTS-1234")'],
|
||
|
|
});
|
||
|
|
expect(readFileSync(out, 'utf8')).toBe('DUMP-CONTENTS-1234');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects with the captured stderr when the child exits non-zero', async () => {
|
||
|
|
const out = path.join(dir, 'b.dump');
|
||
|
|
await expect(
|
||
|
|
runPgDump('ignored://url', out, {
|
||
|
|
command: process.execPath,
|
||
|
|
buildArgs: () => ['-e', 'process.stderr.write("kaboom"); process.exit(3)'],
|
||
|
|
}),
|
||
|
|
).rejects.toThrow(/exited 3.*kaboom/s);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects when the command cannot be spawned', async () => {
|
||
|
|
const out = path.join(dir, 'c.dump');
|
||
|
|
await expect(
|
||
|
|
runPgDump('ignored://url', out, {
|
||
|
|
command: '/nonexistent/definitely-not-a-real-binary-xyz',
|
||
|
|
buildArgs: () => [],
|
||
|
|
}),
|
||
|
|
).rejects.toBeTruthy();
|
||
|
|
});
|
||
|
|
});
|