@fedify/fedify
Version:
An ActivityPub server framework
186 lines (185 loc) • 8.65 kB
JavaScript
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 {};