115 lines
4.4 KiB
TypeScript
115 lines
4.4 KiB
TypeScript
import { describe, it, expect, afterAll } from "vitest";
|
|
import { rm } from "node:fs/promises";
|
|
import { cpSync } from "node:fs";
|
|
import { startServer, type ServerConfig } from "../src/server.js";
|
|
import { ensureJournalFixture, ensureRefinedFixture, ensureCcFixture } from "./helpers.js";
|
|
|
|
// Two server instances: one WITHOUT relay config (push/refresh must refuse
|
|
// cleanly), one WITH a relay config pointing at an unreachable port (the relay
|
|
// call must throw a determinate error, not hang). Each gets its own journal
|
|
// LevelDB copy — classic-level takes an exclusive lock, so two servers can't
|
|
// open the same LevelDB dir at once.
|
|
|
|
const OUT_A = "/tmp/test-server-push-out-a";
|
|
const OUT_B = "/tmp/test-server-push-out-b";
|
|
const JOURNAL_A = "/tmp/test-server-push-journal-a";
|
|
const PORT_A = 17810;
|
|
const PORT_B = 17811;
|
|
const BASE_A = `http://127.0.0.1:${PORT_A}`;
|
|
const BASE_B = `http://127.0.0.1:${PORT_B}`;
|
|
|
|
const cfgA: ServerConfig = {
|
|
journal: JOURNAL_A,
|
|
refinedDir: ensureRefinedFixture(),
|
|
ccDir: ensureCcFixture(),
|
|
outDir: OUT_A,
|
|
mode: "dev",
|
|
port: PORT_A,
|
|
host: "127.0.0.1",
|
|
// no relayCfg -> push/refresh must refuse with a clear error
|
|
};
|
|
|
|
const cfgB: ServerConfig = {
|
|
journal: ensureJournalFixture(),
|
|
refinedDir: ensureRefinedFixture(),
|
|
ccDir: ensureCcFixture(),
|
|
outDir: OUT_B,
|
|
mode: "dev",
|
|
port: PORT_B,
|
|
host: "127.0.0.1",
|
|
relayCfg: { url: "http://127.0.0.1:1", apiKey: "test-key", clientId: "test-client" },
|
|
foundryCfg: { container: "foundry", dataDir: "/tmp/no-such-data", world: "mardonar" },
|
|
};
|
|
|
|
let hA: Awaited<ReturnType<typeof startServer>> | null = null;
|
|
let hB: Awaited<ReturnType<typeof startServer>> | null = null;
|
|
|
|
async function bootA(): Promise<void> {
|
|
if (!hA) {
|
|
await rm(JOURNAL_A, { recursive: true, force: true });
|
|
cpSync(ensureJournalFixture(), JOURNAL_A, { recursive: true });
|
|
hA = await startServer(cfgA);
|
|
}
|
|
}
|
|
async function bootB(): Promise<void> { if (!hB) hB = await startServer(cfgB); }
|
|
|
|
afterAll(async () => {
|
|
if (hA) { hA.server.close(); hA = null; }
|
|
if (hB) { hB.server.close(); hB = null; }
|
|
await rm(OUT_A, { recursive: true, force: true });
|
|
await rm(OUT_B, { recursive: true, force: true });
|
|
await rm(JOURNAL_A, { recursive: true, force: true });
|
|
});
|
|
|
|
async function jpost(base: string, p: string, body: unknown): Promise<any> {
|
|
const r = await fetch(base + p, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
return { status: r.status, json: await r.json() };
|
|
}
|
|
|
|
describe("dashboard live-push endpoints", () => {
|
|
it("/api/push refuses when relay is not configured", async () => {
|
|
await bootA();
|
|
const { status, json } = await jpost(BASE_A, "/api/push", { name: "Fenris of House Quche", dryRun: true });
|
|
expect(status).toBe(500);
|
|
expect(json.error).toContain("relay not configured");
|
|
});
|
|
|
|
it("/api/refresh refuses when relay is not configured", async () => {
|
|
await bootA();
|
|
const { status, json } = await jpost(BASE_A, "/api/refresh", {});
|
|
expect(status).toBe(500);
|
|
expect(json.error).toContain("relay not configured");
|
|
});
|
|
|
|
it("/api/push 400s on missing name", async () => {
|
|
await bootB();
|
|
const { status, json } = await jpost(BASE_B, "/api/push", { dryRun: true });
|
|
expect(status).toBe(400);
|
|
expect(json.error).toContain("missing name");
|
|
});
|
|
|
|
it("/api/push 400s for an unknown name", async () => {
|
|
await bootB();
|
|
const { status, json } = await jpost(BASE_B, "/api/push", { name: "Nobody Matches This", dryRun: true });
|
|
expect(status).toBe(400);
|
|
expect(json.error).toContain("no refined note");
|
|
});
|
|
|
|
it("/api/push on a known but unseeded note surfaces a clear error (no cc_uuid)", async () => {
|
|
await bootB();
|
|
// The fixture's Fenris note is matched but not seeded -> pushNote reads it,
|
|
// finds no foundry.cc_uuid, and throws before ever touching the relay.
|
|
const { status, json } = await jpost(BASE_B, "/api/push", { name: "Fenris of House Quche", dryRun: true });
|
|
expect(status).toBe(500);
|
|
expect(json.error).toContain("cc_uuid");
|
|
});
|
|
|
|
it("/api/refresh with an unreachable relay returns a determinate error (no hang)", async () => {
|
|
await bootB();
|
|
const { status, json } = await jpost(BASE_B, "/api/refresh", {});
|
|
expect(status).toBe(500);
|
|
// The relay /search fetch fails (connection refused on port 1) -> surfaced, not hung.
|
|
expect(typeof json.error).toBe("string");
|
|
expect(json.error.length).toBeGreaterThan(0);
|
|
}, 15000);
|
|
}); |