Files
obsidian-foundry-sync/tests/server.test.ts
2026-06-20 19:15:38 +00:00

120 lines
4.2 KiB
TypeScript

import { describe, it, expect, afterAll } from "vitest";
import { rm, access } from "node:fs/promises";
import { join } from "node:path";
import { startServer, type ServerConfig } from "../src/server.js";
import { ensureJournalFixture, ensureRefinedFixture, ensureCcFixture } from "./helpers.js";
const OUT = "/tmp/test-server-out";
const PORT = 17801;
const BASE = `http://127.0.0.1:${PORT}`;
const cfg: ServerConfig = {
journal: ensureJournalFixture(),
refinedDir: ensureRefinedFixture(),
ccDir: ensureCcFixture(),
outDir: OUT,
mode: "dev",
port: PORT,
host: "127.0.0.1",
};
let serverHandle: Awaited<ReturnType<typeof startServer>> | null = null;
async function boot(): Promise<void> {
if (serverHandle) return;
serverHandle = await startServer(cfg);
}
afterAll(async () => {
if (serverHandle) { serverHandle.server.close(); serverHandle = null; }
await rm(OUT, { recursive: true, force: true });
});
async function jget(p: string): Promise<any> {
const r = await fetch(BASE + p);
expect(r.status).toBe(200);
return r.json();
}
async function jpost(p: string, body: unknown): Promise<any> {
const r = await fetch(BASE + p, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
return r.json();
}
describe("dashboard server", () => {
it("serves the dashboard page at /", async () => {
await boot();
const r = await fetch(BASE + "/");
expect(r.status).toBe(200);
const html = await r.text();
expect(html).toContain("Foundry ⇄ Obsidian merge");
});
it("GET /api/status reports dev mode and the configured dirs", async () => {
await boot();
const s = await jget("/api/status");
expect(s.mode).toBe("dev");
expect(s.refinedDir).toBe(cfg.refinedDir);
expect(s.ccDir).toBe(cfg.ccDir);
expect(s.outDir).toBe(OUT);
});
it("GET /api/index returns the expected counts", async () => {
await boot();
const idx = await jget("/api/index");
expect(idx.counts.matched).toBe(107);
expect(idx.counts.ccOnly).toBe(52);
expect(idx.counts.refinedOnly).toBe(0);
});
it("GET /api/file returns refined, cc, and previews for a known note", async () => {
await boot();
const f = await jget("/api/file?name=" + encodeURIComponent("Fenris of House Quche"));
expect(f.row.name).toBe("Fenris of House Quche");
expect(f.refined).toBeTruthy();
expect(f.cc).toBeTruthy();
expect(f.seedPreview).toContain("foundry:");
expect(f.syncPreview).toContain("cc_id");
});
it("dry-run seedAll writes nothing and returns previews only", async () => {
await boot();
const out = await jpost("/api/action", { op: "seedAll", dryRun: true });
expect(out.written.length).toBe(0);
expect(out.preview.length).toBe(107);
// Sandbox out dir must not be created by a dry-run.
await expect(access(OUT)).rejects.toThrow();
});
it("dev seedAll writes only under --out, never to the source refined dir", async () => {
await boot();
const out = await jpost("/api/action", { op: "seedAll", dryRun: false });
expect(out.written.length).toBe(107);
expect(out.skipped.length).toBe(0);
for (const w of out.written) {
expect(w.path.startsWith(OUT)).toBe(true);
expect(w.path).not.toContain(cfg.refinedDir);
}
});
it("dev syncAll baselines both sides: writes refined + cc files under --out", async () => {
await boot();
const out = await jpost("/api/action", { op: "syncAll", dryRun: false });
// sync now writes BOTH the refreshed refined note and the regenerated cc per row.
expect(out.written.length).toBe(214);
const paths = out.written.map((w: { path: string }) => w.path);
// Every write lands under the sandbox out dir, never in a source dir.
for (const p of paths) {
expect(p.startsWith(OUT)).toBe(true);
expect(p).not.toContain(cfg.ccDir);
expect(p).not.toContain(cfg.refinedDir);
}
});
it("dev repullAll writes refined notes only under --out", async () => {
await boot();
const out = await jpost("/api/action", { op: "repullAll", dryRun: false });
// Cold start: no row is seeded, so re-pull still produces output for every linked row.
expect(out.written.length).toBe(107);
for (const w of out.written) expect(w.path.startsWith(OUT)).toBe(true);
});
});