tests/presenceRoutes.test.ts
Rama main · réplica de lectura del repositorio original.
// Discord presence through the Hono app (server/presenceRoutes.ts and
// server/presence/host.ts), over a throwaway vault, with a sink standing
// where the desktop parent would: what a tab reports, what the policy lets
// out, and what would have been handed to Discord. The opted-out note, the
// excluded tag and the activity-only mode are the privacy promises, proved
// end to end on the rendered card.
import assert from "node:assert/strict";
import { after, before, beforeEach, describe, it } from "node:test";
import { Hono } from "hono";
import { initAuth } from "../server/auth.ts";
import { initIndexer } from "../server/indexer.ts";
import { initSite } from "../server/site.ts";
import { initVault } from "../server/vault.ts";
import { api } from "../server/api.ts";
import { patchSettings } from "../server/settings.ts";
import { initPresence, stopPresence } from "../server/presence/host.ts";
import type { DiscordActivity, PresenceStatus } from "../shared/presence/types.ts";
import type { MeData } from "../shared/types.ts";
import { makeDir, makeVault, note, removeVault } from "./helpers/vault.ts";
const data = makeDir();
const root = makeVault({
"Books/Deep/Chapter 3.md": note({ title: "Chapter 3" }, "# Three\n\nWords.\n"),
"Secret.md": note({ presence: "false" }, "# Secret\n\nShh.\n"),
"Drafty.md": "# Drafty\n\nHalf done. #draft\n",
});
const app = new Hono();
app.route("/api", api);
const told: { clientId: string | null; activity: DiscordActivity | null }[] = [];
const last = () => told.at(-1);
const post = (body: unknown, headers: Record<string, string> = {}) =>
app.request("/api/presence", { method: "POST", headers: { "Content-Type": "application/json", ...headers }, body: JSON.stringify(body) });
const HASH = {
ADMIN_PASSWORD_HASH: "$argon2id$v=19$m=65536,t=3,p=4$c2FsdHNhbHQ$aGFzaGhhc2hoYXNoaGFzaA",
SESSION_SECRET: "relsecret0123456789abcdef0123456789",
};
before(async () => {
initAuth({});
initSite({ ASTROLABE_DATA: data });
initVault(root);
await initIndexer();
patchSettings({ excludeTags: ["draft"], presence: { mode: "title", clientId: "123456789012345678" } });
initPresence({ sink: (clientId, activity) => told.push({ clientId, activity }) });
});
beforeEach(() => {
patchSettings({ presence: null });
patchSettings({ presence: { mode: "title", clientId: "123456789012345678" } });
});
after(async () => {
await stopPresence();
initAuth({});
removeVault(root);
removeVault(data);
});
describe("POST /api/presence (the owner)", () => {
it("renders the note's own title for the sink", async () => {
const res = await post({ client: "tab-000001", kind: "writing", path: "Books/Deep/Chapter 3.md", idleMs: 0 });
assert.equal(res.status, 200);
assert.deepEqual(await res.json(), { ok: true });
assert.equal(last()?.clientId, "123456789012345678");
assert.equal(last()?.activity?.details, "Writing");
assert.equal(last()?.activity?.state, "Chapter 3");
assert.doesNotMatch(JSON.stringify(last()), /Books|Deep|\.md/, "no path leaves");
});
it("a note with presence: false shows the kind only", async () => {
await post({ client: "tab-000001", kind: "writing", path: "Secret.md", idleMs: 0 });
assert.equal(last()?.activity?.details, "Writing");
assert.equal(last()?.activity?.state, undefined);
});
it("a note carrying an excluded tag shows the kind only", async () => {
await post({ client: "tab-000001", kind: "reading", path: "Drafty.md", idleMs: 0 });
assert.equal(last()?.activity?.details, "Reading");
assert.equal(last()?.activity?.state, undefined);
});
it("activity mode never says the title", async () => {
patchSettings({ presence: { mode: "activity" } });
await post({ client: "tab-000001", kind: "writing", path: "Books/Deep/Chapter 3.md", idleMs: 0 });
assert.equal(last()?.activity?.details, "Writing");
assert.equal(last()?.activity?.state, undefined);
});
it("mode off tells the sink null, once", async () => {
await post({ client: "tab-000001", kind: "drawing", path: "Books/Deep/Chapter 3.md", idleMs: 0 });
assert.notEqual(last()?.activity, null);
patchSettings({ presence: { mode: "off" } });
const before = told.length;
await post({ client: "tab-000001", kind: "writing", path: "Books/Deep/Chapter 3.md", idleMs: 0 });
assert.deepEqual(last(), { clientId: null, activity: null });
await post({ client: "tab-000001", kind: "library", idleMs: 0 });
assert.equal(told.length, before + 1, "no change, no message");
});
it("a bad body is a 400 naming the problem", async () => {
const res = await post({ client: "tab-000001", kind: "writing", path: "/etc/passwd", idleMs: 0 });
assert.equal(res.status, 400);
assert.match(JSON.stringify(await res.json()), /relative/);
assert.equal((await post({ client: "x", kind: "writing", idleMs: 0 })).status, 400);
});
it("GET is the status row", async () => {
await post({ client: "tab-000002", kind: "library", idleMs: 0 });
const status = (await (await app.request("/api/presence")).json()) as PresenceStatus;
assert.equal(status.enabled, true);
assert.equal(status.mode, "title");
assert.equal(status.applicationId, "set");
assert.equal(status.host, "desktop", "a sink stands where the parent would");
assert.equal(typeof status.connection, "string");
assert.ok(status.clients >= 1);
assert.equal(status.activity?.details, "In the library");
});
it("/api/me says presence is on, for the owner", async () => {
const me = (await (await app.request("/api/me")).json()) as MeData;
assert.equal(me.presence, true);
patchSettings({ presence: { mode: "off" } });
const off = (await (await app.request("/api/me")).json()) as MeData;
assert.equal(off.presence, undefined);
});
});
describe("/api/presence (the owner previewing as a visitor)", () => {
it("is refused both ways, and /api/me does not say", async () => {
const preview = { "x-astrolabe-preview": "visitor" };
// The auth guard answers a previewing mutation before the route does.
const res = await post({ client: "tab-000008", kind: "writing", idleMs: 0 }, preview);
assert.ok(res.status === 401 || res.status === 404, `refused, got ${res.status}`);
assert.equal((await app.request("/api/presence", { headers: preview })).status, 404);
const me = (await (await app.request("/api/me", { headers: preview })).json()) as MeData;
assert.equal(me.presence, undefined);
});
});
describe("/api/presence (a visitor)", () => {
before(() => initAuth(HASH));
after(() => initAuth({}));
it("GET is a 404, POST is refused, and /api/me never says", async () => {
assert.equal((await app.request("/api/presence")).status, 404);
const res = await post({ client: "tab-000009", kind: "writing", idleMs: 0 });
assert.ok(res.status === 401 || res.status === 404, `refused, got ${res.status}`);
const me = (await (await app.request("/api/me")).json()) as MeData;
assert.equal(me.presence, undefined);
});
});