Loading federation/handler.test.ts 0 → 100644 +142 −0 Original line number Diff line number Diff line import { assert, assertEquals, assertFalse } from "jsr:@std/assert@^0.218.2"; import { createRequestContext } from "../testing/context.ts"; import { Person } from "../vocab/vocab.ts"; import { ActorDispatcher } from "./callback.ts"; import { acceptsJsonLd, handleActor } from "./handler.ts"; Deno.test("acceptsJsonLd()", () => { assert(acceptsJsonLd( new Request("https://example.com/", { headers: { Accept: "application/activity+json" }, }), )); assert(acceptsJsonLd( new Request("https://example.com/", { headers: { Accept: "application/ld+json" }, }), )); assert(acceptsJsonLd( new Request("https://example.com/", { headers: { Accept: "application/json" }, }), )); assertFalse(acceptsJsonLd( new Request("https://example.com/", { headers: { Accept: "application/ld+json; q=0.5, text/html; q=0.8" }, }), )); assertFalse(acceptsJsonLd( new Request("https://example.com/", { headers: { Accept: "application/ld+json; q=0.4, application/xhtml+xml; q=0.9", }, }), )); }); Deno.test("handleActor()", async () => { let context = createRequestContext<void>({ data: undefined, url: new URL("https://example.com/"), getActorUri(handle) { return new URL(`https://example.com/users/${handle}`); }, }); const actorDispatcher: ActorDispatcher<void> = (ctx, handle, _key) => { if (handle !== "someone") return null; return new Person({ id: ctx.getActorUri(handle), name: "Someone", }); }; let onNotFoundCalled: Request | null = null; const onNotFound = (request: Request) => { onNotFoundCalled = request; return new Response("Not found", { status: 404 }); }; let onNotAcceptableCalled: Request | null = null; const onNotAcceptable = (request: Request) => { onNotAcceptableCalled = request; return new Response("Not acceptable", { status: 406 }); }; let response = await handleActor( context.request, { context, handle: "someone", onNotFound, onNotAcceptable, }, ); assertEquals(response.status, 404); assertEquals(onNotFoundCalled, context.request); assertEquals(onNotAcceptableCalled, null); onNotFoundCalled = null; response = await handleActor( context.request, { context, handle: "someone", actorDispatcher, onNotFound, onNotAcceptable, }, ); assertEquals(response.status, 406); assertEquals(onNotFoundCalled, null); assertEquals(onNotAcceptableCalled, context.request); onNotAcceptableCalled = null; context = createRequestContext<void>({ ...context, request: new Request(context.url, { headers: { Accept: "application/activity+json", }, }), }); response = await handleActor( context.request, { context, handle: "someone", actorDispatcher, onNotFound, onNotAcceptable, }, ); assertEquals(response.status, 200); assertEquals(await response.json(), { "@context": [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", { discoverable: "toot:discoverable", indexable: "toot:indexable", memorial: "toot:memorial", suspended: "toot:suspended", toot: "http://joinmastodon.org/ns#", }, ], id: "https://example.com/users/someone", type: "Person", name: "Someone", }); assertEquals(onNotFoundCalled, null); assertEquals(onNotAcceptableCalled, null); response = await handleActor( context.request, { context, handle: "no-one", actorDispatcher, onNotFound, onNotAcceptable, }, ); assertEquals(response.status, 404); assertEquals(onNotFoundCalled, context.request); assertEquals(onNotAcceptableCalled, null); }); federation/handler.ts +2 −4 Original line number Diff line number Diff line Loading @@ -17,7 +17,7 @@ import { } from "./callback.ts"; import { RequestContext } from "./context.ts"; function acceptsJsonLd(request: Request): boolean { export function acceptsJsonLd(request: Request): boolean { const types = accepts(request); if (types == null) return true; if (types[0] === "text/html" || types[0] === "application/xhtml+xml") { Loading @@ -31,7 +31,6 @@ function acceptsJsonLd(request: Request): boolean { export interface ActorHandlerParameters<TContextData> { handle: string; context: RequestContext<TContextData>; documentLoader: DocumentLoader; actorDispatcher?: ActorDispatcher<TContextData>; onNotFound(request: Request): Response | Promise<Response>; onNotAcceptable(request: Request): Response | Promise<Response>; Loading @@ -42,7 +41,6 @@ export async function handleActor<TContextData>( { handle, context, documentLoader, actorDispatcher, onNotFound, onNotAcceptable, Loading @@ -62,7 +60,7 @@ export async function handleActor<TContextData>( const response = onNotFound(request); return response instanceof Promise ? await response : response; } const jsonLd = await actor.toJsonLd({ documentLoader }); const jsonLd = await actor.toJsonLd(context); return new Response(JSON.stringify(jsonLd), { headers: { "Content-Type": "application/activity+json", Loading federation/middleware.ts +0 −1 Original line number Diff line number Diff line Loading @@ -654,7 +654,6 @@ export class Federation<TContextData> { return await handleActor(request, { handle: route.values.handle, context, documentLoader: this.#documentLoader, actorDispatcher: this.#actorCallbacks?.dispatcher, onNotFound, onNotAcceptable, Loading testing/context.ts 0 → 100644 +49 −0 Original line number Diff line number Diff line import { Context, RequestContext } from "../federation/context.ts"; import { RouterError } from "../federation/router.ts"; import { mockDocumentLoader } from "./docloader.ts"; export function createContext<TContextData>( { data, documentLoader, getActorUri, getOutboxUri, getInboxUri, getFollowingUri, getFollowersUri, getActorKey, sendActivity, }: Partial<Context<TContextData>> & { data: TContextData }, ): Context<TContextData> { function throwRouteError(_handle?: string): URL { throw new RouterError("Not implemented"); } return { data, documentLoader: documentLoader ?? mockDocumentLoader, getActorUri: getActorUri ?? throwRouteError, getOutboxUri: getOutboxUri ?? throwRouteError, getInboxUri: getInboxUri ?? throwRouteError, getFollowingUri: getFollowingUri ?? throwRouteError, getFollowersUri: getFollowersUri ?? throwRouteError, getActorKey: getActorKey ?? ((_handle) => { return Promise.resolve(null); }), sendActivity: sendActivity ?? ((_params) => { throw new Error("Not implemented"); }), }; } export function createRequestContext<TContextData>( args: Partial<RequestContext<TContextData>> & { url: URL; data: TContextData; }, ): RequestContext<TContextData> { return { ...createContext(args), request: args.request ?? new Request(args.url), url: args.url, }; } webfinger/handler.test.ts +5 −23 Original line number Diff line number Diff line import { assertEquals } from "jsr:@std/assert@^0.218.2"; import { ActorDispatcher } from "../federation/callback.ts"; import { RequestContext } from "../federation/context.ts"; import { Router, RouterError } from "../federation/router.ts"; import { mockDocumentLoader } from "../testing/docloader.ts"; import { Router } from "../federation/router.ts"; import { createRequestContext } from "../testing/context.ts"; import { CryptographicKey, Link, Person } from "../vocab/vocab.ts"; import { handleWebFinger } from "./handler.ts"; Deno.test("handleWebFinger()", async () => { const url = new URL("https://example.com/.well-known/webfinger"); let request = new Request(url); const context: RequestContext<void> = { const context = createRequestContext<void>({ url, request, data: undefined, documentLoader: mockDocumentLoader, getActorUri(handle) { return new URL(`https://example.com/users/${handle}`); }, getOutboxUri(_handle) { throw new RouterError("Not implemented"); }, getInboxUri(_handle?) { throw new RouterError("Not implemented"); }, getFollowingUri(_handle) { throw new RouterError("Not implemented"); }, getFollowersUri(_handle) { throw new RouterError("Not implemented"); }, getActorKey(_handle) { return Promise.resolve( new CryptographicKey({ Loading @@ -36,10 +20,7 @@ Deno.test("handleWebFinger()", async () => { }), ); }, sendActivity(_params) { throw new Error("Not implemented"); }, }; }); const router = new Router(); router.add("/users/{handle}", "actor"); const actorDispatcher: ActorDispatcher<void> = (ctx, handle, _key) => { Loading @@ -63,6 +44,7 @@ Deno.test("handleWebFinger()", async () => { return new Response("Not found", { status: 404 }); }; let request = context.request; let response = await handleWebFinger(request, { context, router, Loading Loading
federation/handler.test.ts 0 → 100644 +142 −0 Original line number Diff line number Diff line import { assert, assertEquals, assertFalse } from "jsr:@std/assert@^0.218.2"; import { createRequestContext } from "../testing/context.ts"; import { Person } from "../vocab/vocab.ts"; import { ActorDispatcher } from "./callback.ts"; import { acceptsJsonLd, handleActor } from "./handler.ts"; Deno.test("acceptsJsonLd()", () => { assert(acceptsJsonLd( new Request("https://example.com/", { headers: { Accept: "application/activity+json" }, }), )); assert(acceptsJsonLd( new Request("https://example.com/", { headers: { Accept: "application/ld+json" }, }), )); assert(acceptsJsonLd( new Request("https://example.com/", { headers: { Accept: "application/json" }, }), )); assertFalse(acceptsJsonLd( new Request("https://example.com/", { headers: { Accept: "application/ld+json; q=0.5, text/html; q=0.8" }, }), )); assertFalse(acceptsJsonLd( new Request("https://example.com/", { headers: { Accept: "application/ld+json; q=0.4, application/xhtml+xml; q=0.9", }, }), )); }); Deno.test("handleActor()", async () => { let context = createRequestContext<void>({ data: undefined, url: new URL("https://example.com/"), getActorUri(handle) { return new URL(`https://example.com/users/${handle}`); }, }); const actorDispatcher: ActorDispatcher<void> = (ctx, handle, _key) => { if (handle !== "someone") return null; return new Person({ id: ctx.getActorUri(handle), name: "Someone", }); }; let onNotFoundCalled: Request | null = null; const onNotFound = (request: Request) => { onNotFoundCalled = request; return new Response("Not found", { status: 404 }); }; let onNotAcceptableCalled: Request | null = null; const onNotAcceptable = (request: Request) => { onNotAcceptableCalled = request; return new Response("Not acceptable", { status: 406 }); }; let response = await handleActor( context.request, { context, handle: "someone", onNotFound, onNotAcceptable, }, ); assertEquals(response.status, 404); assertEquals(onNotFoundCalled, context.request); assertEquals(onNotAcceptableCalled, null); onNotFoundCalled = null; response = await handleActor( context.request, { context, handle: "someone", actorDispatcher, onNotFound, onNotAcceptable, }, ); assertEquals(response.status, 406); assertEquals(onNotFoundCalled, null); assertEquals(onNotAcceptableCalled, context.request); onNotAcceptableCalled = null; context = createRequestContext<void>({ ...context, request: new Request(context.url, { headers: { Accept: "application/activity+json", }, }), }); response = await handleActor( context.request, { context, handle: "someone", actorDispatcher, onNotFound, onNotAcceptable, }, ); assertEquals(response.status, 200); assertEquals(await response.json(), { "@context": [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", { discoverable: "toot:discoverable", indexable: "toot:indexable", memorial: "toot:memorial", suspended: "toot:suspended", toot: "http://joinmastodon.org/ns#", }, ], id: "https://example.com/users/someone", type: "Person", name: "Someone", }); assertEquals(onNotFoundCalled, null); assertEquals(onNotAcceptableCalled, null); response = await handleActor( context.request, { context, handle: "no-one", actorDispatcher, onNotFound, onNotAcceptable, }, ); assertEquals(response.status, 404); assertEquals(onNotFoundCalled, context.request); assertEquals(onNotAcceptableCalled, null); });
federation/handler.ts +2 −4 Original line number Diff line number Diff line Loading @@ -17,7 +17,7 @@ import { } from "./callback.ts"; import { RequestContext } from "./context.ts"; function acceptsJsonLd(request: Request): boolean { export function acceptsJsonLd(request: Request): boolean { const types = accepts(request); if (types == null) return true; if (types[0] === "text/html" || types[0] === "application/xhtml+xml") { Loading @@ -31,7 +31,6 @@ function acceptsJsonLd(request: Request): boolean { export interface ActorHandlerParameters<TContextData> { handle: string; context: RequestContext<TContextData>; documentLoader: DocumentLoader; actorDispatcher?: ActorDispatcher<TContextData>; onNotFound(request: Request): Response | Promise<Response>; onNotAcceptable(request: Request): Response | Promise<Response>; Loading @@ -42,7 +41,6 @@ export async function handleActor<TContextData>( { handle, context, documentLoader, actorDispatcher, onNotFound, onNotAcceptable, Loading @@ -62,7 +60,7 @@ export async function handleActor<TContextData>( const response = onNotFound(request); return response instanceof Promise ? await response : response; } const jsonLd = await actor.toJsonLd({ documentLoader }); const jsonLd = await actor.toJsonLd(context); return new Response(JSON.stringify(jsonLd), { headers: { "Content-Type": "application/activity+json", Loading
federation/middleware.ts +0 −1 Original line number Diff line number Diff line Loading @@ -654,7 +654,6 @@ export class Federation<TContextData> { return await handleActor(request, { handle: route.values.handle, context, documentLoader: this.#documentLoader, actorDispatcher: this.#actorCallbacks?.dispatcher, onNotFound, onNotAcceptable, Loading
testing/context.ts 0 → 100644 +49 −0 Original line number Diff line number Diff line import { Context, RequestContext } from "../federation/context.ts"; import { RouterError } from "../federation/router.ts"; import { mockDocumentLoader } from "./docloader.ts"; export function createContext<TContextData>( { data, documentLoader, getActorUri, getOutboxUri, getInboxUri, getFollowingUri, getFollowersUri, getActorKey, sendActivity, }: Partial<Context<TContextData>> & { data: TContextData }, ): Context<TContextData> { function throwRouteError(_handle?: string): URL { throw new RouterError("Not implemented"); } return { data, documentLoader: documentLoader ?? mockDocumentLoader, getActorUri: getActorUri ?? throwRouteError, getOutboxUri: getOutboxUri ?? throwRouteError, getInboxUri: getInboxUri ?? throwRouteError, getFollowingUri: getFollowingUri ?? throwRouteError, getFollowersUri: getFollowersUri ?? throwRouteError, getActorKey: getActorKey ?? ((_handle) => { return Promise.resolve(null); }), sendActivity: sendActivity ?? ((_params) => { throw new Error("Not implemented"); }), }; } export function createRequestContext<TContextData>( args: Partial<RequestContext<TContextData>> & { url: URL; data: TContextData; }, ): RequestContext<TContextData> { return { ...createContext(args), request: args.request ?? new Request(args.url), url: args.url, }; }
webfinger/handler.test.ts +5 −23 Original line number Diff line number Diff line import { assertEquals } from "jsr:@std/assert@^0.218.2"; import { ActorDispatcher } from "../federation/callback.ts"; import { RequestContext } from "../federation/context.ts"; import { Router, RouterError } from "../federation/router.ts"; import { mockDocumentLoader } from "../testing/docloader.ts"; import { Router } from "../federation/router.ts"; import { createRequestContext } from "../testing/context.ts"; import { CryptographicKey, Link, Person } from "../vocab/vocab.ts"; import { handleWebFinger } from "./handler.ts"; Deno.test("handleWebFinger()", async () => { const url = new URL("https://example.com/.well-known/webfinger"); let request = new Request(url); const context: RequestContext<void> = { const context = createRequestContext<void>({ url, request, data: undefined, documentLoader: mockDocumentLoader, getActorUri(handle) { return new URL(`https://example.com/users/${handle}`); }, getOutboxUri(_handle) { throw new RouterError("Not implemented"); }, getInboxUri(_handle?) { throw new RouterError("Not implemented"); }, getFollowingUri(_handle) { throw new RouterError("Not implemented"); }, getFollowersUri(_handle) { throw new RouterError("Not implemented"); }, getActorKey(_handle) { return Promise.resolve( new CryptographicKey({ Loading @@ -36,10 +20,7 @@ Deno.test("handleWebFinger()", async () => { }), ); }, sendActivity(_params) { throw new Error("Not implemented"); }, }; }); const router = new Router(); router.add("/users/{handle}", "actor"); const actorDispatcher: ActorDispatcher<void> = (ctx, handle, _key) => { Loading @@ -63,6 +44,7 @@ Deno.test("handleWebFinger()", async () => { return new Response("Not found", { status: 404 }); }; let request = context.request; let response = await handleWebFinger(request, { context, router, Loading