Files
SnapOtter/tests/integration/platform/sqlite-import-orchestrator.test.ts
T

82 lines
3.3 KiB
TypeScript
Raw Normal View History

import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import {
analyzeSqlite,
countLibraryBlobs,
evaluateBootState,
resolveSource,
} from "../../../apps/api/src/db/sqlite-import.js";
import { buildLegacySqlite, seedRealistic1xData } from "../../helpers/legacy-sqlite-fixture.js";
describe("resolveSource", () => {
it("returns null for the 'off' sentinel", () => {
expect(resolveSource({ SQLITE_MIGRATE_PATH: "off", DATA_DIR: "/data" })).toBeNull();
expect(resolveSource({ SQLITE_MIGRATE_PATH: "OFF", DATA_DIR: "/data" })).toBeNull();
});
it("prefers an explicit path", () => {
expect(resolveSource({ SQLITE_MIGRATE_PATH: "/x/foo.db", DATA_DIR: "/data" })).toBe(
"/x/foo.db",
);
});
it("probes DATA_DIR/snapotter.db when unset", () => {
const dir = mkdtempSync(join(tmpdir(), "src-"));
writeFileSync(join(dir, "snapotter.db"), "x");
expect(resolveSource({ SQLITE_MIGRATE_PATH: "", DATA_DIR: dir })).toBe(
join(dir, "snapotter.db"),
);
});
it("returns null when nothing is present", () => {
const dir = mkdtempSync(join(tmpdir(), "src-"));
expect(resolveSource({ SQLITE_MIGRATE_PATH: "", DATA_DIR: dir })).toBeNull();
});
});
describe("evaluateBootState", () => {
it("import when users empty, source present, no marker", () => {
expect(evaluateBootState({ usersCount: 0, source: "/a.db", marker: null })).toBe("import");
});
it("leftover when a completed marker exists", () => {
expect(
evaluateBootState({ usersCount: 5, source: "/a.db", marker: { status: "completed" } }),
).toBe("leftover");
});
it("locked when source present, users non-empty, no marker", () => {
expect(evaluateBootState({ usersCount: 1, source: "/a.db", marker: null })).toBe("locked");
});
it("none when no source", () => {
expect(evaluateBootState({ usersCount: 0, source: null, marker: null })).toBe("none");
});
});
describe("countLibraryBlobs", () => {
it("counts present and missing library blobs", async () => {
const dir = mkdtempSync(join(tmpdir(), "blob-"));
const dbPath = join(dir, "legacy.db");
buildLegacySqlite(dbPath);
await seedRealistic1xData(dbPath); // seeds user_files uf-1 -> stored_name abc123.png
const filesDir = join(dir, "files");
mkdirSync(filesDir, { recursive: true });
writeFileSync(join(filesDir, "abc123.png"), "img");
expect(await countLibraryBlobs(dbPath, filesDir)).toEqual({ present: 1, missing: 0 });
});
});
describe("analyzeSqlite", () => {
it("reports counts, blobs, and out-of-enum statuses without a live Postgres", async () => {
const dir = mkdtempSync(join(tmpdir(), "analyze-"));
const dbPath = join(dir, "legacy.db");
buildLegacySqlite(dbPath);
await seedRealistic1xData(dbPath); // 1 user, 2 jobs (one "error"), 1 user_file
const filesDir = join(dir, "files");
mkdirSync(filesDir, { recursive: true });
writeFileSync(join(filesDir, "abc123.png"), "img"); // the uf-1 blob
const report = await analyzeSqlite(dbPath, filesDir);
expect(report.tables.users).toBe(1);
expect(report.tables.jobs).toBe(2);
expect(report.blobs).toEqual({ present: 1, missing: 0 });
expect(report.badStatuses).toContain("error"); // will be mapped to "failed" on import
});
});