UNPKG

@fedify/fedify

Version:

An ActivityPub server framework

186 lines (185 loc) • 8.65 kB
import "@js-temporal/polyfill"; import "urlpattern-polyfill"; globalThis.addEventListener = () => {}; import { n as RouterError } from "../router-CrMLXoOr.mjs"; import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs"; import { i as assertExists } from "../std__assert-CRDpx_HF.mjs"; import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs"; import { t as MemoryKvStore } from "../kv-rV3vodCc.mjs"; import { n as createFederationBuilder } from "../builder-CaVN56-q.mjs"; import { test } from "@fedify/fixture"; import { Activity, Note, Person } from "@fedify/vocab"; //#region src/federation/builder.test.ts test("FederationBuilder", async (t) => { await t.step("should build a Federation object with registered components", async () => { const builder = createFederationBuilder(); const kv = new MemoryKvStore(); const actorDispatcher = (_ctx, _identifier) => { return null; }; builder.setActorDispatcher("/users/{identifier}", actorDispatcher); const inboxListener = (_ctx, _activity) => {}; builder.setInboxListeners("/users/{identifier}/inbox").on(Activity, inboxListener); const outboxListener = (_ctx, _activity) => {}; builder.setOutboxListeners("/users/{identifier}/outbox").on(Activity, outboxListener); const objectDispatcher = (_ctx, _values) => { return null; }; builder.setObjectDispatcher(Note, "/notes/{id}", objectDispatcher); const nodeInfo = { version: "2.1", software: { name: "test", version: "1.0.0" }, protocols: ["activitypub"], services: { inbound: [], outbound: [] }, openRegistrations: false, usage: { users: {}, localPosts: 0, localComments: 0 }, metadata: {} }; const nodeInfoDispatcher = (_ctx) => nodeInfo; builder.setNodeInfoDispatcher("/nodeinfo", nodeInfoDispatcher); const federation = await builder.build({ kv }); assertExists(federation); const impl = federation; assertEquals(impl.router.route("/.well-known/webfinger")?.name, "webfinger"); assertEquals(impl.router.route("/users/test123")?.name, "actor"); assertEquals(impl.router.route("/users/test123/inbox")?.name, "inbox"); assertEquals(impl.router.route("/users/test123/outbox")?.name, "outbox"); assertEquals(impl.router.route("/notes/456")?.name, `object:${Note.typeId.href}`); assertEquals(impl.router.route("/nodeinfo")?.name, "nodeInfo"); const actorCallbacksDispatcher = impl.actorCallbacks?.dispatcher; assertExists(actorCallbacksDispatcher); const inboxListeners = impl.inboxListeners; assertExists(inboxListeners); const outboxListeners = impl.outboxListeners; assertExists(outboxListeners); assertExists(impl.objectCallbacks[Note.typeId.href]); assertExists(impl.nodeInfoDispatcher); assertEquals(impl.router.build(`object:${Note.typeId.href}`, { id: "123" }), "/notes/123"); assertEquals(impl.router.build("actor", { identifier: "user1" }), "/users/user1"); assertEquals(impl.router.build("inbox", { identifier: "user1" }), "/users/user1/inbox"); await builder.build({ kv }); }); await t.step("should build with default options", async () => { const builder = createFederationBuilder(); const kv = new MemoryKvStore(); const federation = await builder.build({ kv }); assertExists(federation); assertEquals(federation.kv, kv); }); await t.step("should validate outbox listener paths", () => { const builder = createFederationBuilder(); builder.setOutboxDispatcher("/users/{identifier}/outbox", () => ({ items: [] })); assertThrows(() => builder.setOutboxListeners("/actors/{identifier}/outbox"), RouterError); assertThrows(() => builder.setOutboxListeners("/users/outbox"), RouterError); assertThrows(() => builder.setOutboxListeners("/users/{identifier}/outbox/{extra}"), RouterError); assertThrows(() => builder.setOutboxListeners("/users/{identifier}/outbox/{identifier}"), RouterError); const builderAfterInvalid = createFederationBuilder(); assertThrows(() => builderAfterInvalid.setOutboxListeners("/users/{identifier}/outbox/{extra}"), RouterError); builderAfterInvalid.setOutboxListeners("/users/{identifier}/outbox"); const builder2 = createFederationBuilder(); builder2.setOutboxListeners("/users{/identifier}/outbox"); assertThrows(() => builder2.setOutboxDispatcher("/actors/{identifier}/outbox", () => ({ items: [] })), RouterError); const builder3 = createFederationBuilder(); assertThrows(() => builder3.setOutboxListeners("/users{?identifier}/outbox"), RouterError); const builder4 = createFederationBuilder(); assertThrows(() => builder4.setOutboxDispatcher("/users{?identifier}/outbox", () => ({ items: [] })), RouterError); }); await t.step("should pass build options correctly", async () => { const builder = createFederationBuilder(); const kv = new MemoryKvStore(); const federation = await builder.build({ kv, kvPrefixes: { activityIdempotence: ["custom", "idem"] }, allowPrivateAddress: true, trailingSlashInsensitive: true, origin: "https://example.com" }); assertExists(federation); const impl = federation; assertEquals(impl.kv, kv); assertEquals(impl.kvPrefixes.activityIdempotence, ["custom", "idem"]); assertEquals(impl.allowPrivateAddress, true); assertEquals(impl.router.trailingSlashInsensitive, true); assertEquals(impl.origin?.webOrigin, "https://example.com"); }); await t.step("should handle firstKnock option", async () => { const builder = createFederationBuilder(); const kv = new MemoryKvStore(); const federationDefault = await builder.build({ kv }); assertExists(federationDefault); assertEquals(federationDefault.firstKnock, void 0); const federationCustom = await builder.build({ kv, firstKnock: "draft-cavage-http-signatures-12" }); assertExists(federationCustom); assertEquals(federationCustom.firstKnock, "draft-cavage-http-signatures-12"); const federationRfc = await builder.build({ kv, firstKnock: "rfc9421" }); assertExists(federationRfc); assertEquals(federationRfc.firstKnock, "rfc9421"); }); await t.step("should copy unverified activity handler into built federation", async () => { const builder = createFederationBuilder(); const kv = new MemoryKvStore(); const handler = (_ctx, _activity, _reason) => {}; builder.setInboxListeners("/users/{identifier}/inbox").onUnverifiedActivity(handler); assertEquals((await builder.build({ kv })).unverifiedActivityHandler, handler); }); await t.step("should register multiple object dispatchers and verify them", async () => { const builder = createFederationBuilder(); const kv = new MemoryKvStore(); const noteDispatcher = (_ctx, _values) => { return null; }; const personDispatcher = (_ctx, _values) => { return null; }; builder.setObjectDispatcher(Note, "/notes/{id}", noteDispatcher); builder.setObjectDispatcher(Person, "/people/{id}", personDispatcher); const impl = await builder.build({ kv }); assertExists(impl.objectCallbacks[Note.typeId.href]); assertExists(impl.objectCallbacks[Person.typeId.href]); assertEquals(impl.router.build(`object:${Note.typeId.href}`, { id: "123" }), "/notes/123"); assertEquals(impl.router.build(`object:${Person.typeId.href}`, { id: "456" }), "/people/456"); const noteRoute = impl.router.route("/notes/789"); assertEquals(noteRoute?.name, `object:${Note.typeId.href}`); assertEquals(noteRoute?.values.id, "789"); const personRoute = impl.router.route("/people/abc"); assertEquals(personRoute?.name, `object:${Person.typeId.href}`); assertEquals(personRoute?.values.id, "abc"); }); await t.step("should handle symbol names uniquely in custom collection dispatchers", () => { const builder = createFederationBuilder(); const unnamedSymbol1 = Symbol(); const unnamedSymbol2 = Symbol(); const namedSymbol1 = Symbol.for(""); const namedSymbol2 = Symbol.for(""); const strId = String(unnamedSymbol1); const dispatcher = (_ctx, _params) => ({ items: [] }); builder.setCollectionDispatcher(unnamedSymbol1, Note, "/unnamed-symbol1/{id}", dispatcher); assertThrows(() => { builder.setCollectionDispatcher(unnamedSymbol1, Note, "/unnamed-symbol1-duplicate/{id}", dispatcher); }, Error, "Collection dispatcher for Symbol() already set."); builder.setCollectionDispatcher(unnamedSymbol2, Note, "/unnamed-symbol2/{id}", dispatcher); builder.setCollectionDispatcher(namedSymbol1, Note, "/named-symbol/{id}", dispatcher); assertThrows(() => { builder.setCollectionDispatcher(namedSymbol2, Note, "/named-symbol/{id}", dispatcher); }); builder.setCollectionDispatcher(strId, Note, "/string-id/{id}", dispatcher); }); }); //#endregion export {};