import { assertEquals } from "@std/assert"; import { createApp } from "../server.ts"; import { addWebhookSubscription, listWebhooksByDid } from "../src/data/db.ts"; import { seedNote, withTestDb } from "./_helpers/db.ts"; import { requestApp } from "./_helpers/app.ts"; import { stubAuthenticate } from "./_helpers/auth.ts"; import { installFetchStub, jsonResponse } from "./_helpers/fetch_stub.ts"; const withAdminEnv = ( dids: string, fn: () => Promise | void, ): () => Promise => { return async () => { const previous = Deno.env.get("ADMIN_DIDS"); Deno.env.set("ADMIN_DIDS", dids); try { await fn(); } finally { if (previous === undefined) Deno.env.delete("ADMIN_DIDS"); else Deno.env.set("ADMIN_DIDS", previous); } }; }; const withOpenSearchEnv = (fn: () => Promise | void) => { return async () => { const previous = Deno.env.get("OPENSEARCH_URL"); Deno.env.set("OPENSEARCH_URL", "http://opensearch.test"); try { await fn(); } finally { if (previous === undefined) Deno.env.delete("OPENSEARCH_URL"); else Deno.env.set("OPENSEARCH_URL", previous); } }; }; Deno.test( "GET /health returns 200 with status ok", withTestDb(async () => { const res = await requestApp(createApp(), { path: "/health" }); assertEquals(res.status, 200); assertEquals(res.json(), { status: "ok" }); }), ); Deno.test( "GET /notes paginates rkey desc and honors cursor + limit", withTestDb(async () => { for (const rkey of ["a", "b", "c", "d", "e"]) { seedNote({ rkey }); } const app = createApp(); const first = await requestApp(app, { path: "/notes?limit=3" }); assertEquals(first.status, 200); const firstBody = first.json() as { notes: { rkey: string }[]; cursor?: string; }; assertEquals(firstBody.notes.map((n) => n.rkey), ["e", "d", "c"]); assertEquals(firstBody.cursor, "c"); const second = await requestApp(app, { path: `/notes?limit=3&cursor=${firstBody.cursor}`, }); const secondBody = second.json() as { notes: { rkey: string }[] }; assertEquals(secondBody.notes.map((n) => n.rkey), ["b", "a"]); }), ); Deno.test( "POST /notes/feed rejects empty dids and accepts a valid list", withTestDb(async () => { seedNote({ did: "did:plc:alice", rkey: "rk1" }); const app = createApp(); const empty = await requestApp(app, { method: "POST", path: "/notes/feed", body: { dids: [] }, }); assertEquals(empty.status, 400); const notArray = await requestApp(app, { method: "POST", path: "/notes/feed", body: { dids: "did:plc:alice" }, }); assertEquals(notArray.status, 400); const ok = await requestApp(app, { method: "POST", path: "/notes/feed", body: { dids: ["did:plc:alice"] }, }); assertEquals(ok.status, 200); const body = ok.json() as { notes: { rkey: string }[] }; assertEquals(body.notes.length, 1); }), ); Deno.test( "POST /:did/webhooks: 401 without auth, 403 on DID mismatch, 201 inserts create+delete by default", withTestDb(async () => { const app = createApp(); const noAuth = await requestApp(app, { method: "POST", path: "/did:plc:alice/webhooks", body: { method: "POST", url: "https://hook.example/x" }, }); assertEquals(noAuth.status, 401); const mismatch = stubAuthenticate("did:plc:bob"); try { const res = await requestApp(app, { method: "POST", path: "/did:plc:alice/webhooks", headers: { Authorization: "Bearer fake" }, body: { method: "POST", url: "https://hook.example/x" }, }); assertEquals(res.status, 403); } finally { mismatch.restore(); } const owned = stubAuthenticate("did:plc:alice"); try { const res = await requestApp(app, { method: "POST", path: "/did:plc:alice/webhooks", headers: { Authorization: "Bearer fake" }, body: { method: "POST", url: "https://hook.example/x" }, }); assertEquals(res.status, 201); const created = res.json() as { verb: string }[]; const verbs = created.map((w) => w.verb).sort(); assertEquals(verbs, ["create", "delete"]); } finally { owned.restore(); } }), ); Deno.test( "POST /:did/webhooks rejects unknown verb with 400", withTestDb(async () => { const auth = stubAuthenticate("did:plc:alice"); try { const res = await requestApp(createApp(), { method: "POST", path: "/did:plc:alice/webhooks", headers: { Authorization: "Bearer fake" }, body: { method: "POST", url: "https://hook.example/x", verb: "destroy", }, }); assertEquals(res.status, 400); } finally { auth.restore(); } }), ); Deno.test( "DELETE /:did/webhooks/:id: 400 on non-positive, 404 on unknown, 204 on success", withTestDb(async () => { const created = addWebhookSubscription({ did: "did:plc:alice", method: "POST", url: "https://hook.example/x", verb: "create", }); const auth = stubAuthenticate("did:plc:alice"); const app = createApp(); try { const bad = await requestApp(app, { method: "DELETE", path: "/did:plc:alice/webhooks/0", headers: { Authorization: "Bearer fake" }, }); assertEquals(bad.status, 400); const missing = await requestApp(app, { method: "DELETE", path: "/did:plc:alice/webhooks/9999", headers: { Authorization: "Bearer fake" }, }); assertEquals(missing.status, 404); const ok = await requestApp(app, { method: "DELETE", path: `/did:plc:alice/webhooks/${created.id}`, headers: { Authorization: "Bearer fake" }, }); assertEquals(ok.status, 204); assertEquals(listWebhooksByDid("did:plc:alice").length, 0); } finally { auth.restore(); } }), ); Deno.test( "GET /admin/webhooks: 403 for non-admin, 200 when ADMIN_DIDS contains caller", withAdminEnv( "did:plc:admin", withTestDb(async () => { const app = createApp(); const nonAdmin = stubAuthenticate("did:plc:other"); try { const res = await requestApp(app, { path: "/admin/webhooks", headers: { Authorization: "Bearer fake" }, }); assertEquals(res.status, 403); } finally { nonAdmin.restore(); } const admin = stubAuthenticate("did:plc:admin"); try { const res = await requestApp(app, { path: "/admin/webhooks", headers: { Authorization: "Bearer fake" }, }); assertEquals(res.status, 200); assertEquals(Array.isArray(res.json()), true); } finally { admin.restore(); } }), ), ); Deno.test( "GET /search: 400 on blank q, forwards discoverableOnly:true to opensearch", withOpenSearchEnv( withTestDb(async () => { const app = createApp(); const blank = await requestApp(app, { path: "/search?q=" }); assertEquals(blank.status, 400); const stub = installFetchStub(() => jsonResponse({ hits: { hits: [] } })); try { const res = await requestApp(app, { path: "/search?q=hello" }); assertEquals(res.status, 200); assertEquals(stub.calls.length, 1); const sent = JSON.parse(stub.calls[0].body) as { query: { bool: { filter: Record[] } }; }; assertEquals(sent.query.bool.filter, [ { term: { discoverable: true } }, ]); } finally { stub.restore(); } }), ), ); Deno.test( "OPTIONS preflight returns 204 with CORS headers", withTestDb(async () => { const res = await requestApp(createApp(), { method: "OPTIONS", path: "/notes", }); assertEquals(res.status, 204); assertEquals(res.headers.get("Access-Control-Allow-Origin"), "*"); assertEquals( res.headers.get("Access-Control-Allow-Methods")?.includes("POST"), true, ); assertEquals( res.headers.get("Access-Control-Allow-Headers")?.includes( "Authorization", ), true, ); }), );