Files
remanso-jetstream/tests/server_test.ts
Julien Calixte e937863bc6
Some checks failed
CI / check (push) Failing after 12s
test(server): cover HTTP routes, auth, admin gate, and CORS preflight
2026-06-07 22:26:54 +02:00

288 lines
8.1 KiB
TypeScript

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> | void,
): () => Promise<void> => {
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> | 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<string, unknown>[] } };
};
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,
);
}),
);