pi-mono/packages/chord/test/next-node/http/authenticate.test.ts
Mario Zechner 8959ad6e68 feat(chord): Chord v2 slice 10, HTTP and workers
chord/next/http: a Web fetch handler built from the registry Http (routes, wraps,
sockets, hosts, relays, CSP sources) and Authenticators, responses as descriptions,
a security baseline on by default, Chord's own Node listener as a host-lifetime
object, nodeRequest for apps mounting it, links and plugin sockets.
chord/next/workers: a forked worker per address, started on demand, stopped when
idle, never restarted by itself, plugins built by the server and shipped.
Plugins: kinds with platform and host packages, createPluginRuntime, scripts(kind),
rollout results per host, imports of other plugins by name. Kernel: RouteHandle
(retry, suspend), no route backoff, anonymous host.root, origins only from relays.
ws is an optional peer dependency.
2026-10-31 20:40:36 +00:00

58 lines
2.3 KiB
TypeScript

// Tests of the authenticators: asked in contributor order, the first principal wins, a throwing one reported and
// skipped, none: anonymous.
import { describe, expect, it } from "vitest";
import { BACKGROUND_CONTEXT as context, withAbortSignal } from "../../../src/context/index.ts";
import type { RegistryTable } from "../../../src/next/registries/index.ts";
import { type Authenticator, authenticate, composeAuthenticators } from "../../../src/next-node/http/authenticate.ts";
import type { HttpRequest } from "../../../src/next-node/http/index.ts";
const request = { headers: new Headers() } as HttpRequest;
describe("authenticators", () => {
it("are asked in contributor order; the first principal wins; a throwing one is reported and skipped", async () => {
const asked: string[] = [];
const answer = (name: string, principal: { kind: string }) => {
asked.push(name);
return principal;
};
const table: RegistryTable<Authenticator> = [
{ plugin: "@app/a", version: 1, value: () => void asked.push("a") },
{
plugin: "@app/b",
version: 1,
value: () => {
asked.push("b");
throw new Error("broken");
},
},
{ plugin: "@app/c", version: 1, value: async () => answer("c", { kind: "owner" }) },
{ plugin: "@app/d", version: 1, value: () => answer("d", { kind: "guest" }) },
];
const failed: [string, unknown][] = [];
const principal = await authenticate(
composeAuthenticators(table),
request,
(plugin, error) => failed.push([plugin, error]),
context,
);
expect([principal, asked, failed]).toEqual([
{ kind: "owner" },
["a", "b", "c"],
[["@app/b", new Error("broken")]],
]);
});
// Sol's second recheck C: a fresh authenticator started after the request was cancelled.
it("start no authenticator once the context is aborted", async () => {
let calls = 0;
const table: RegistryTable<Authenticator> = [{ plugin: "@app/a", version: 1, value: () => void calls++ }];
const aborted = withAbortSignal(AbortSignal.abort(), context);
expect(await authenticate(composeAuthenticators(table), request, () => {}, aborted)).toBeUndefined();
expect(calls).toBe(0);
});
it("give no principal when none knows the request", async () => {
expect(await authenticate([], request, () => {}, context)).toBeUndefined();
});
});