Files
sqlite-migrate/tests/cli.test.ts
Brendan Chen 7a7c5adf92 test: port migrate suite + add probe-table and CLI specs
15 vitest specs total:
- 9 lifted from authd's tests/db/migrate.test.ts. Specs that referenced
  the on-disk migrations dir now write fixtures into a per-test tmpdir
  (package ships no migrations dir of its own).
- 3 new specs covering the genesisProbeTable parameter (default, custom
  table name, opt-out via stampGenesis=false).
- 3 new specs covering runMigrateCli (migrate / status / stamp) using
  PassThrough stream capture.
2026-05-12 02:16:29 -07:00

158 lines
4.6 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { PassThrough } from 'node:stream';
import Database from 'better-sqlite3';
import { runMigrateCli } from '../src/cli.ts';
function captureStreams() {
const stdout = new PassThrough();
const stderr = new PassThrough();
const stdoutBuf: Buffer[] = [];
const stderrBuf: Buffer[] = [];
stdout.on('data', (c) => stdoutBuf.push(Buffer.isBuffer(c) ? c : Buffer.from(c)));
stderr.on('data', (c) => stderrBuf.push(Buffer.isBuffer(c) ? c : Buffer.from(c)));
return {
stdout,
stderr,
get stdoutText() {
return Buffer.concat(stdoutBuf).toString('utf8');
},
get stderrText() {
return Buffer.concat(stderrBuf).toString('utf8');
},
};
}
describe('runMigrateCli', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = mkdtempSync(join(tmpdir(), 'sqlite-migrate-cli-test-'));
});
afterEach(() => {
rmSync(tmpDir, { recursive: true, force: true });
});
it("command='migrate' calls applyMigrations and writes expected lines", () => {
writeFileSync(join(tmpDir, '0001_init.sql'), 'CREATE TABLE x (a INTEGER);');
const caps = captureStreams();
const code = runMigrateCli({
openDb: () => new Database(':memory:'),
migrationsDir: tmpDir,
command: 'migrate',
stdout: caps.stdout,
stderr: caps.stderr,
});
expect(code).toBe(0);
expect(caps.stdoutText).toContain('applied:1');
expect(caps.stdoutText).toContain('migrations: 1 applied, 0 pending');
});
it("command='status' emits pending then applied then pending again", () => {
writeFileSync(join(tmpDir, '0001_init.sql'), 'CREATE TABLE x (a INTEGER);');
const sharedDb = new Database(':memory:');
const openDb = () => {
const proxy = new Proxy(sharedDb, {
get(target, prop) {
if (prop === 'close') return () => {};
const v = (target as any)[prop];
return typeof v === 'function' ? v.bind(target) : v;
},
});
return proxy as unknown as Database.Database;
};
const migrateCaps = captureStreams();
const migrateCode = runMigrateCli({
openDb,
migrationsDir: tmpDir,
command: 'migrate',
stdout: migrateCaps.stdout,
stderr: migrateCaps.stderr,
});
expect(migrateCode).toBe(0);
const statusCaps = captureStreams();
const statusCode = runMigrateCli({
openDb,
migrationsDir: tmpDir,
command: 'status',
stdout: statusCaps.stdout,
stderr: statusCaps.stderr,
});
expect(statusCode).toBe(0);
expect(statusCaps.stdoutText).toContain('applied:0001');
writeFileSync(join(tmpDir, '0002_more.sql'), 'CREATE TABLE y (b INTEGER);');
const statusCaps2 = captureStreams();
const statusCode2 = runMigrateCli({
openDb,
migrationsDir: tmpDir,
command: 'status',
stdout: statusCaps2.stdout,
stderr: statusCaps2.stderr,
});
expect(statusCode2).toBe(0);
expect(statusCaps2.stdoutText).toContain('pending:0002');
sharedDb.close();
});
it("command='stamp' marks applied without running body", () => {
writeFileSync(
join(tmpDir, '0001_init.sql'),
'CREATE TABLE only_if_applied (x INTEGER);',
);
const sharedDb = new Database(':memory:');
const openDb = () => {
const proxy = new Proxy(sharedDb, {
get(target, prop) {
if (prop === 'close') return () => {};
const v = (target as any)[prop];
return typeof v === 'function' ? v.bind(target) : v;
},
});
return proxy as unknown as Database.Database;
};
const stampCaps = captureStreams();
const code = runMigrateCli({
openDb,
migrationsDir: tmpDir,
command: 'stamp',
version: '0001',
stdout: stampCaps.stdout,
stderr: stampCaps.stderr,
});
expect(code).toBe(0);
expect(stampCaps.stdoutText).toContain('stamped:0001');
const tableMissing = sharedDb
.prepare(
`SELECT name FROM sqlite_master WHERE type='table' AND name='only_if_applied'`,
)
.get();
expect(tableMissing).toBeUndefined();
const noVersionCaps = captureStreams();
const noVersionCode = runMigrateCli({
openDb,
migrationsDir: tmpDir,
command: 'stamp',
stdout: noVersionCaps.stdout,
stderr: noVersionCaps.stderr,
});
expect(noVersionCode).toBe(1);
expect(noVersionCaps.stderrText).toContain('error:');
sharedDb.close();
});
});