
      import { Temporal } from "@js-temporal/polyfill";
      import { URLPattern } from "urlpattern-polyfill";
      globalThis.addEventListener = () => {};
    
import { Create, Note, Person } from "../lookup-Csp1y88W.js";
import { assertEquals } from "../assert_equals-DSbWqCm3.js";
import { assert } from "../assert-MZs1qjMx.js";
import "../assert_instance_of-DHz7EHNU.js";
import { MemoryKvStore } from "../kv-CRZrzyXm.js";
import { createFederation, handleActor, handleCollection, handleCustomCollection, handleInbox, handleObject, respondWithObject, respondWithObjectIfAcceptable } from "../middleware-BuMF-mtE.js";
import "../client-pY7-3icS.js";
import "../types-C2XVl6gj.js";
import "../actor-BV5H5O8U.js";
import "../key-DpaoFQ82.js";
import { signRequest } from "../http-CqzO68Am.js";
import "../ld-gP0eHSrh.js";
import "../owner-CuAVQ6t7.js";
import "../proof-BM2DN2Sn.js";
import "../docloader-C2NUa918.js";
import "../kv-cache-DN9pfMBe.js";
import { InboxListenerSet } from "../inbox-us8YQR88.js";
import "../builder-rmwKTih6.js";
import "../collection-BzWsN9pB.js";
import "../keycache-DbuBfl7N.js";
import "../negotiation-C4nFufNk.js";
import "../retry-CfF8Gn4d.js";
import "../send-BDf5TQos.js";
import { createInboxContext, createRequestContext, mockDocumentLoader, test } from "../testing-YUoSZp2V.js";
import "../std__assert-DWivtrGR.js";
import "../assert_rejects-Ce45JcFg.js";
import "../assert_throws-BNXdRGWP.js";
import "../assert_not_equals-C80BG-_5.js";
import { rsaPrivateKey3, rsaPublicKey2, rsaPublicKey3 } from "../keys-DLwYJ_nN.js";

//#region src/federation/handler.test.ts
test("handleActor()", async () => {
	const federation = createFederation({ kv: new MemoryKvStore() });
	let context = createRequestContext({
		federation,
		data: void 0,
		url: new URL("https://example.com/"),
		getActorUri(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		}
	});
	const actorDispatcher = (ctx, handle) => {
		if (handle !== "someone") return null;
		return new Person({
			id: ctx.getActorUri(handle),
			name: "Someone"
		});
	};
	let onNotFoundCalled = null;
	const onNotFound = (request) => {
		onNotFoundCalled = request;
		return new Response("Not found", { status: 404 });
	};
	let onUnauthorizedCalled = null;
	const onUnauthorized = (request) => {
		onUnauthorizedCalled = request;
		return new Response("Unauthorized", { status: 401 });
	};
	let response = await handleActor(context.request, {
		context,
		identifier: "someone",
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	response = await handleActor(context.request, {
		context,
		identifier: "no-one",
		actorDispatcher,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	context = createRequestContext({
		...context,
		request: new Request(context.url, { headers: { Accept: "application/activity+json" } })
	});
	response = await handleActor(context.request, {
		context,
		identifier: "someone",
		actorDispatcher,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/v1",
			"https://w3id.org/security/data-integrity/v1",
			"https://www.w3.org/ns/did/v1",
			"https://w3id.org/security/multikey/v1",
			{
				alsoKnownAs: {
					"@id": "as:alsoKnownAs",
					"@type": "@id"
				},
				manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
				movedTo: {
					"@id": "as:movedTo",
					"@type": "@id"
				},
				featured: {
					"@id": "toot:featured",
					"@type": "@id"
				},
				featuredTags: {
					"@id": "toot:featuredTags",
					"@type": "@id"
				},
				discoverable: "toot:discoverable",
				indexable: "toot:indexable",
				memorial: "toot:memorial",
				suspended: "toot:suspended",
				toot: "http://joinmastodon.org/ns#",
				schema: "http://schema.org#",
				PropertyValue: "schema:PropertyValue",
				value: "schema:value",
				misskey: "https://misskey-hub.net/ns#",
				_misskey_followedMessage: "misskey:_misskey_followedMessage",
				isCat: "misskey:isCat",
				Emoji: "toot:Emoji"
			}
		],
		id: "https://example.com/users/someone",
		type: "Person",
		name: "Someone"
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	response = await handleActor(context.request, {
		context,
		identifier: "no-one",
		actorDispatcher,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	response = await handleActor(context.request, {
		context,
		identifier: "someone",
		actorDispatcher,
		authorizePredicate: (_ctx, _handle, signedKey, signedKeyOwner) => signedKey != null && signedKeyOwner != null,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 401);
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, context.request);
	onUnauthorizedCalled = null;
	context = createRequestContext({
		...context,
		getSignedKey: () => Promise.resolve(rsaPublicKey2),
		getSignedKeyOwner: () => Promise.resolve(new Person({}))
	});
	response = await handleActor(context.request, {
		context,
		identifier: "someone",
		actorDispatcher,
		authorizePredicate: (_ctx, _handle, signedKey, signedKeyOwner) => signedKey != null && signedKeyOwner != null,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/v1",
			"https://w3id.org/security/data-integrity/v1",
			"https://www.w3.org/ns/did/v1",
			"https://w3id.org/security/multikey/v1",
			{
				alsoKnownAs: {
					"@id": "as:alsoKnownAs",
					"@type": "@id"
				},
				manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
				movedTo: {
					"@id": "as:movedTo",
					"@type": "@id"
				},
				featured: {
					"@id": "toot:featured",
					"@type": "@id"
				},
				featuredTags: {
					"@id": "toot:featuredTags",
					"@type": "@id"
				},
				discoverable: "toot:discoverable",
				indexable: "toot:indexable",
				memorial: "toot:memorial",
				suspended: "toot:suspended",
				toot: "http://joinmastodon.org/ns#",
				schema: "http://schema.org#",
				PropertyValue: "schema:PropertyValue",
				value: "schema:value",
				misskey: "https://misskey-hub.net/ns#",
				_misskey_followedMessage: "misskey:_misskey_followedMessage",
				isCat: "misskey:isCat",
				Emoji: "toot:Emoji"
			}
		],
		id: "https://example.com/users/someone",
		type: "Person",
		name: "Someone"
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
});
test("handleObject()", async () => {
	const federation = createFederation({ kv: new MemoryKvStore() });
	let context = createRequestContext({
		federation,
		data: void 0,
		url: new URL("https://example.com/"),
		getObjectUri(_cls, values) {
			return new URL(`https://example.com/users/${values.handle}/notes/${values.id}`);
		}
	});
	const objectDispatcher = (ctx, values) => {
		if (values.handle !== "someone" || values.id !== "123") return null;
		return new Note({
			id: ctx.getObjectUri(Note, values),
			summary: "Hello, world!"
		});
	};
	let onNotFoundCalled = null;
	const onNotFound = (request) => {
		onNotFoundCalled = request;
		return new Response("Not found", { status: 404 });
	};
	let onUnauthorizedCalled = null;
	const onUnauthorized = (request) => {
		onUnauthorizedCalled = request;
		return new Response("Unauthorized", { status: 401 });
	};
	let response = await handleObject(context.request, {
		context,
		values: {
			handle: "someone",
			id: "123"
		},
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	response = await handleObject(context.request, {
		context,
		values: {
			handle: "someone",
			id: "123"
		},
		objectDispatcher,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	response = await handleObject(context.request, {
		context,
		values: {
			handle: "no-one",
			id: "123"
		},
		objectDispatcher,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	response = await handleObject(context.request, {
		context,
		values: {
			handle: "someone",
			id: "not-exist"
		},
		objectDispatcher,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	context = createRequestContext({
		...context,
		request: new Request(context.url, { headers: { Accept: "application/activity+json" } })
	});
	response = await handleObject(context.request, {
		context,
		values: {
			handle: "someone",
			id: "123"
		},
		objectDispatcher,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/data-integrity/v1",
			{
				Emoji: "toot:Emoji",
				Hashtag: "as:Hashtag",
				sensitive: "as:sensitive",
				toot: "http://joinmastodon.org/ns#",
				_misskey_quote: "misskey:_misskey_quote",
				fedibird: "http://fedibird.com/ns#",
				misskey: "https://misskey-hub.net/ns#",
				quoteUri: "fedibird:quoteUri",
				quoteUrl: "as:quoteUrl",
				emojiReactions: {
					"@id": "fedibird:emojiReactions",
					"@type": "@id"
				}
			}
		],
		id: "https://example.com/users/someone/notes/123",
		summary: "Hello, world!",
		type: "Note"
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	response = await handleObject(context.request, {
		context,
		values: {
			handle: "no-one",
			id: "123"
		},
		objectDispatcher,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	response = await handleObject(context.request, {
		context,
		values: {
			handle: "someone",
			id: "not-exist"
		},
		objectDispatcher,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	response = await handleObject(context.request, {
		context,
		values: {
			handle: "someone",
			id: "123"
		},
		objectDispatcher,
		authorizePredicate: (_ctx, _values, signedKey, signedKeyOwner) => signedKey != null && signedKeyOwner != null,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 401);
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, context.request);
	onUnauthorizedCalled = null;
	context = createRequestContext({
		...context,
		getSignedKey: () => Promise.resolve(rsaPublicKey2),
		getSignedKeyOwner: () => Promise.resolve(new Person({}))
	});
	response = await handleObject(context.request, {
		context,
		values: {
			handle: "someone",
			id: "123"
		},
		objectDispatcher,
		authorizePredicate: (_ctx, _values, signedKey, signedKeyOwner) => signedKey != null && signedKeyOwner != null,
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/data-integrity/v1",
			{
				Emoji: "toot:Emoji",
				Hashtag: "as:Hashtag",
				sensitive: "as:sensitive",
				toot: "http://joinmastodon.org/ns#",
				_misskey_quote: "misskey:_misskey_quote",
				fedibird: "http://fedibird.com/ns#",
				misskey: "https://misskey-hub.net/ns#",
				quoteUri: "fedibird:quoteUri",
				quoteUrl: "as:quoteUrl",
				emojiReactions: {
					"@id": "fedibird:emojiReactions",
					"@type": "@id"
				}
			}
		],
		id: "https://example.com/users/someone/notes/123",
		summary: "Hello, world!",
		type: "Note"
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
});
test("handleCollection()", async () => {
	const federation = createFederation({ kv: new MemoryKvStore() });
	let context = createRequestContext({
		federation,
		data: void 0,
		url: new URL("https://example.com/"),
		getActorUri(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		}
	});
	const dispatcher = (_ctx, handle, cursor) => {
		if (handle !== "someone") return null;
		const items = [
			new Create({ id: new URL("https://example.com/activities/1") }),
			new Create({ id: new URL("https://example.com/activities/2") }),
			new Create({ id: new URL("https://example.com/activities/3") })
		];
		if (cursor != null) {
			const idx = parseInt(cursor);
			return {
				items: [items[idx]],
				nextCursor: idx < items.length - 1 ? (idx + 1).toString() : null,
				prevCursor: idx > 0 ? (idx - 1).toString() : null
			};
		}
		return { items };
	};
	const counter = (_ctx, handle) => handle === "someone" ? 3 : null;
	const firstCursor = (_ctx, handle) => handle === "someone" ? "0" : null;
	const lastCursor = (_ctx, handle) => handle === "someone" ? "2" : null;
	let onNotFoundCalled = null;
	const onNotFound = (request) => {
		onNotFoundCalled = request;
		return new Response("Not found", { status: 404 });
	};
	let onUnauthorizedCalled = null;
	const onUnauthorized = (request) => {
		onUnauthorizedCalled = request;
		return new Response("Unauthorized", { status: 401 });
	};
	let response = await handleCollection(context.request, {
		context,
		name: "collection",
		identifier: "someone",
		uriGetter(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		},
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	response = await handleCollection(context.request, {
		context,
		name: "collection",
		identifier: "someone",
		uriGetter(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		},
		collectionCallbacks: { dispatcher },
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	response = await handleCollection(context.request, {
		context,
		name: "collection",
		identifier: "no-one",
		uriGetter(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		},
		collectionCallbacks: { dispatcher },
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	context = createRequestContext({
		...context,
		request: new Request(context.url, { headers: { Accept: "application/activity+json" } })
	});
	response = await handleCollection(context.request, {
		context,
		name: "collection",
		identifier: "no-one",
		uriGetter(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		},
		collectionCallbacks: { dispatcher },
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	response = await handleCollection(context.request, {
		context,
		name: "collection",
		identifier: "someone",
		uriGetter(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		},
		collectionCallbacks: { dispatcher },
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	const createCtx = [
		"https://w3id.org/identity/v1",
		"https://www.w3.org/ns/activitystreams",
		"https://w3id.org/security/data-integrity/v1",
		{
			toot: "http://joinmastodon.org/ns#",
			misskey: "https://misskey-hub.net/ns#",
			fedibird: "http://fedibird.com/ns#",
			ChatMessage: "http://litepub.social/ns#ChatMessage",
			Emoji: "toot:Emoji",
			Hashtag: "as:Hashtag",
			sensitive: "as:sensitive",
			votersCount: {
				"@id": "toot:votersCount",
				"@type": "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"
			},
			_misskey_quote: "misskey:_misskey_quote",
			quoteUri: "fedibird:quoteUri",
			quoteUrl: "as:quoteUrl",
			emojiReactions: {
				"@id": "fedibird:emojiReactions",
				"@type": "@id"
			}
		}
	];
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/data-integrity/v1",
			{
				toot: "http://joinmastodon.org/ns#",
				misskey: "https://misskey-hub.net/ns#",
				fedibird: "http://fedibird.com/ns#",
				ChatMessage: "http://litepub.social/ns#ChatMessage",
				Emoji: "toot:Emoji",
				Hashtag: "as:Hashtag",
				sensitive: "as:sensitive",
				votersCount: "toot:votersCount",
				_misskey_quote: "misskey:_misskey_quote",
				quoteUri: "fedibird:quoteUri",
				quoteUrl: "as:quoteUrl",
				emojiReactions: {
					"@id": "fedibird:emojiReactions",
					"@type": "@id"
				}
			}
		],
		id: "https://example.com/users/someone",
		type: "OrderedCollection",
		orderedItems: [
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/1"
			},
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/2"
			},
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/3"
			}
		]
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	response = await handleCollection(context.request, {
		context,
		name: "collection",
		identifier: "someone",
		uriGetter(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		},
		collectionCallbacks: {
			dispatcher,
			authorizePredicate: (_ctx, _handle, key, keyOwner) => key != null && keyOwner != null
		},
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 401);
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, context.request);
	onUnauthorizedCalled = null;
	context = createRequestContext({
		...context,
		getSignedKey: () => Promise.resolve(rsaPublicKey2),
		getSignedKeyOwner: () => Promise.resolve(new Person({}))
	});
	response = await handleCollection(context.request, {
		context,
		name: "collection",
		identifier: "someone",
		uriGetter(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		},
		collectionCallbacks: {
			dispatcher,
			authorizePredicate: (_ctx, _handle, key, keyOwner) => key != null && keyOwner != null
		},
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/data-integrity/v1",
			{
				toot: "http://joinmastodon.org/ns#",
				misskey: "https://misskey-hub.net/ns#",
				fedibird: "http://fedibird.com/ns#",
				ChatMessage: "http://litepub.social/ns#ChatMessage",
				Emoji: "toot:Emoji",
				Hashtag: "as:Hashtag",
				sensitive: "as:sensitive",
				votersCount: "toot:votersCount",
				_misskey_quote: "misskey:_misskey_quote",
				quoteUri: "fedibird:quoteUri",
				quoteUrl: "as:quoteUrl",
				emojiReactions: {
					"@id": "fedibird:emojiReactions",
					"@type": "@id"
				}
			}
		],
		id: "https://example.com/users/someone",
		type: "OrderedCollection",
		orderedItems: [
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/1"
			},
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/2"
			},
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/3"
			}
		]
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	response = await handleCollection(context.request, {
		context,
		name: "collection",
		identifier: "someone",
		uriGetter(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		},
		collectionCallbacks: {
			dispatcher,
			counter,
			firstCursor,
			lastCursor
		},
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/data-integrity/v1",
			{
				toot: "http://joinmastodon.org/ns#",
				misskey: "https://misskey-hub.net/ns#",
				fedibird: "http://fedibird.com/ns#",
				ChatMessage: "http://litepub.social/ns#ChatMessage",
				Emoji: "toot:Emoji",
				Hashtag: "as:Hashtag",
				sensitive: "as:sensitive",
				votersCount: "toot:votersCount",
				_misskey_quote: "misskey:_misskey_quote",
				quoteUri: "fedibird:quoteUri",
				quoteUrl: "as:quoteUrl",
				emojiReactions: {
					"@id": "fedibird:emojiReactions",
					"@type": "@id"
				}
			}
		],
		id: "https://example.com/users/someone",
		type: "OrderedCollection",
		totalItems: 3,
		first: "https://example.com/?cursor=0",
		last: "https://example.com/?cursor=2"
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	let url = new URL("https://example.com/?cursor=0");
	context = createRequestContext({
		...context,
		url,
		request: new Request(url, { headers: { Accept: "application/activity+json" } })
	});
	response = await handleCollection(context.request, {
		context,
		name: "collection",
		identifier: "someone",
		uriGetter(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		},
		collectionCallbacks: {
			dispatcher,
			counter,
			firstCursor,
			lastCursor
		},
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/data-integrity/v1",
			{
				toot: "http://joinmastodon.org/ns#",
				misskey: "https://misskey-hub.net/ns#",
				fedibird: "http://fedibird.com/ns#",
				ChatMessage: "http://litepub.social/ns#ChatMessage",
				Emoji: "toot:Emoji",
				Hashtag: "as:Hashtag",
				sensitive: "as:sensitive",
				votersCount: "toot:votersCount",
				_misskey_quote: "misskey:_misskey_quote",
				quoteUri: "fedibird:quoteUri",
				quoteUrl: "as:quoteUrl",
				emojiReactions: {
					"@id": "fedibird:emojiReactions",
					"@type": "@id"
				}
			}
		],
		id: "https://example.com/users/someone?cursor=0",
		type: "OrderedCollectionPage",
		partOf: "https://example.com/",
		next: "https://example.com/?cursor=1",
		orderedItems: [{
			"@context": createCtx,
			id: "https://example.com/activities/1",
			type: "Create"
		}]
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	url = new URL("https://example.com/?cursor=2");
	context = createRequestContext({
		...context,
		url,
		request: new Request(url, { headers: { Accept: "application/activity+json" } })
	});
	response = await handleCollection(context.request, {
		context,
		name: "collection",
		identifier: "someone",
		uriGetter(identifier) {
			return new URL(`https://example.com/users/${identifier}`);
		},
		collectionCallbacks: {
			dispatcher,
			counter,
			firstCursor,
			lastCursor
		},
		onNotFound,
		onUnauthorized
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/data-integrity/v1",
			{
				toot: "http://joinmastodon.org/ns#",
				misskey: "https://misskey-hub.net/ns#",
				fedibird: "http://fedibird.com/ns#",
				ChatMessage: "http://litepub.social/ns#ChatMessage",
				Emoji: "toot:Emoji",
				Hashtag: "as:Hashtag",
				sensitive: "as:sensitive",
				votersCount: "toot:votersCount",
				_misskey_quote: "misskey:_misskey_quote",
				quoteUri: "fedibird:quoteUri",
				quoteUrl: "as:quoteUrl",
				emojiReactions: {
					"@id": "fedibird:emojiReactions",
					"@type": "@id"
				}
			}
		],
		id: "https://example.com/users/someone?cursor=2",
		type: "OrderedCollectionPage",
		partOf: "https://example.com/",
		prev: "https://example.com/?cursor=1",
		orderedItems: [{
			"@context": createCtx,
			id: "https://example.com/activities/3",
			type: "Create"
		}]
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
});
test("handleInbox()", async () => {
	const activity = new Create({
		id: new URL("https://example.com/activities/1"),
		actor: new URL("https://example.com/person2"),
		object: new Note({
			id: new URL("https://example.com/notes/1"),
			attribution: new URL("https://example.com/person2"),
			content: "Hello, world!"
		})
	});
	const unsignedRequest = new Request("https://example.com/", {
		method: "POST",
		body: JSON.stringify(await activity.toJsonLd())
	});
	const federation = createFederation({ kv: new MemoryKvStore() });
	const unsignedContext = createRequestContext({
		federation,
		request: unsignedRequest,
		url: new URL(unsignedRequest.url),
		data: void 0
	});
	let onNotFoundCalled = null;
	const onNotFound = (request) => {
		onNotFoundCalled = request;
		return new Response("Not found", { status: 404 });
	};
	const actorDispatcher = (_ctx, identifier) => {
		if (identifier !== "someone") return null;
		return new Person({ name: "Someone" });
	};
	const inboxOptions = {
		kv: new MemoryKvStore(),
		kvPrefixes: {
			activityIdempotence: ["_fedify", "activityIdempotence"],
			publicKey: ["_fedify", "publicKey"]
		},
		actorDispatcher,
		onNotFound,
		signatureTimeWindow: { minutes: 5 },
		skipSignatureVerification: false
	};
	let response = await handleInbox(unsignedRequest, {
		recipient: null,
		context: unsignedContext,
		inboxContextFactory(_activity) {
			return createInboxContext({
				...unsignedContext,
				clone: void 0
			});
		},
		...inboxOptions,
		actorDispatcher: void 0
	});
	assertEquals(onNotFoundCalled, unsignedRequest);
	assertEquals(response.status, 404);
	onNotFoundCalled = null;
	response = await handleInbox(unsignedRequest, {
		recipient: "nobody",
		context: unsignedContext,
		inboxContextFactory(_activity) {
			return createInboxContext({
				...unsignedContext,
				clone: void 0,
				recipient: "nobody"
			});
		},
		...inboxOptions
	});
	assertEquals(onNotFoundCalled, unsignedRequest);
	assertEquals(response.status, 404);
	onNotFoundCalled = null;
	response = await handleInbox(unsignedRequest, {
		recipient: null,
		context: unsignedContext,
		inboxContextFactory(_activity) {
			return createInboxContext({
				...unsignedContext,
				clone: void 0
			});
		},
		...inboxOptions
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(response.status, 401);
	response = await handleInbox(unsignedRequest, {
		recipient: "someone",
		context: unsignedContext,
		inboxContextFactory(_activity) {
			return createInboxContext({
				...unsignedContext,
				clone: void 0,
				recipient: "someone"
			});
		},
		...inboxOptions
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(response.status, 401);
	onNotFoundCalled = null;
	const signedRequest = await signRequest(unsignedRequest.clone(), rsaPrivateKey3, rsaPublicKey3.id);
	const signedContext = createRequestContext({
		federation,
		request: signedRequest,
		url: new URL(signedRequest.url),
		data: void 0,
		documentLoader: mockDocumentLoader
	});
	response = await handleInbox(signedRequest, {
		recipient: null,
		context: signedContext,
		inboxContextFactory(_activity) {
			return createInboxContext({
				...unsignedContext,
				clone: void 0
			});
		},
		...inboxOptions
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals([response.status, await response.text()], [202, ""]);
	response = await handleInbox(signedRequest, {
		recipient: "someone",
		context: signedContext,
		inboxContextFactory(_activity) {
			return createInboxContext({
				...unsignedContext,
				clone: void 0,
				recipient: "someone"
			});
		},
		...inboxOptions
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals([response.status, await response.text()], [202, ""]);
	response = await handleInbox(unsignedRequest, {
		recipient: null,
		context: unsignedContext,
		inboxContextFactory(_activity) {
			return createInboxContext({
				...unsignedContext,
				clone: void 0
			});
		},
		...inboxOptions,
		skipSignatureVerification: true
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(response.status, 202);
	response = await handleInbox(unsignedRequest, {
		recipient: "someone",
		context: unsignedContext,
		inboxContextFactory(_activity) {
			return createInboxContext({
				...unsignedContext,
				clone: void 0,
				recipient: "someone"
			});
		},
		...inboxOptions,
		skipSignatureVerification: true
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(response.status, 202);
	const invalidRequest = new Request("https://example.com/", {
		method: "POST",
		body: JSON.stringify({
			"@context": [
				"https://www.w3.org/ns/activitystreams",
				true,
				23
			],
			type: "Create",
			object: {
				type: "Note",
				content: "Hello, world!"
			},
			actor: "https://example.com/users/alice"
		})
	});
	const signedInvalidRequest = await signRequest(invalidRequest, rsaPrivateKey3, rsaPublicKey3.id);
	const signedInvalidContext = createRequestContext({
		federation,
		request: signedInvalidRequest,
		url: new URL(signedInvalidRequest.url),
		data: void 0,
		documentLoader: mockDocumentLoader
	});
	response = await handleInbox(signedInvalidRequest, {
		recipient: null,
		context: signedContext,
		inboxContextFactory(_activity) {
			return createInboxContext({
				...signedInvalidContext,
				clone: void 0
			});
		},
		...inboxOptions
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(response.status, 400);
});
test("respondWithObject()", async () => {
	const response = await respondWithObject(new Note({
		id: new URL("https://example.com/notes/1"),
		content: "Hello, world!"
	}), { contextLoader: mockDocumentLoader });
	assert(response.ok);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/data-integrity/v1",
			{
				Emoji: "toot:Emoji",
				Hashtag: "as:Hashtag",
				sensitive: "as:sensitive",
				toot: "http://joinmastodon.org/ns#",
				_misskey_quote: "misskey:_misskey_quote",
				fedibird: "http://fedibird.com/ns#",
				misskey: "https://misskey-hub.net/ns#",
				quoteUri: "fedibird:quoteUri",
				quoteUrl: "as:quoteUrl",
				emojiReactions: {
					"@id": "fedibird:emojiReactions",
					"@type": "@id"
				}
			}
		],
		id: "https://example.com/notes/1",
		type: "Note",
		content: "Hello, world!"
	});
});
test("handleInbox() - authentication bypass vulnerability", async () => {
	const federation = createFederation({ kv: new MemoryKvStore() });
	let processedActivity;
	const inboxListeners = new InboxListenerSet();
	inboxListeners.add(Create, (_ctx, activity) => {
		processedActivity = activity;
	});
	const maliciousActivity = new Create({
		id: new URL("https://attacker.example.com/activities/malicious"),
		actor: new URL("https://victim.example.com/users/alice"),
		object: new Note({
			id: new URL("https://attacker.example.com/notes/forged"),
			attribution: new URL("https://victim.example.com/users/alice"),
			content: "This is a forged message from the victim!"
		})
	});
	const maliciousRequest = await signRequest(new Request("https://example.com/", {
		method: "POST",
		body: JSON.stringify(await maliciousActivity.toJsonLd())
	}), rsaPrivateKey3, rsaPublicKey3.id);
	const maliciousContext = createRequestContext({
		request: maliciousRequest,
		url: new URL(maliciousRequest.url),
		data: void 0,
		documentLoader: mockDocumentLoader,
		federation
	});
	const actorDispatcher = (_ctx, identifier) => {
		if (identifier !== "someone") return null;
		return new Person({ name: "Someone" });
	};
	const response = await handleInbox(maliciousRequest, {
		recipient: "someone",
		context: maliciousContext,
		inboxContextFactory(_activity) {
			return createInboxContext({
				url: new URL(maliciousRequest.url),
				data: void 0,
				documentLoader: mockDocumentLoader,
				federation,
				recipient: "someone"
			});
		},
		kv: new MemoryKvStore(),
		kvPrefixes: {
			activityIdempotence: ["_fedify", "activityIdempotence"],
			publicKey: ["_fedify", "publicKey"]
		},
		actorDispatcher,
		inboxListeners,
		onNotFound: () => new Response("Not found", { status: 404 }),
		signatureTimeWindow: { minutes: 5 },
		skipSignatureVerification: false
	});
	assertEquals(response.status, 401);
	assertEquals(await response.text(), "The signer and the actor do not match.");
	assertEquals(processedActivity, void 0, `SECURITY VULNERABILITY: Malicious activity with mismatched signature was processed! Activity ID: ${processedActivity?.id?.href}, Claimed actor: ${processedActivity?.actorId?.href}`);
});
test("respondWithObjectIfAcceptable", async () => {
	let request = new Request("https://example.com/", { headers: { Accept: "application/activity+json" } });
	let response = await respondWithObjectIfAcceptable(new Note({
		id: new URL("https://example.com/notes/1"),
		content: "Hello, world!"
	}), request, { contextLoader: mockDocumentLoader });
	assert(response != null);
	assert(response.ok);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": [
			"https://www.w3.org/ns/activitystreams",
			"https://w3id.org/security/data-integrity/v1",
			{
				Emoji: "toot:Emoji",
				Hashtag: "as:Hashtag",
				sensitive: "as:sensitive",
				toot: "http://joinmastodon.org/ns#",
				_misskey_quote: "misskey:_misskey_quote",
				fedibird: "http://fedibird.com/ns#",
				misskey: "https://misskey-hub.net/ns#",
				quoteUri: "fedibird:quoteUri",
				quoteUrl: "as:quoteUrl",
				emojiReactions: {
					"@id": "fedibird:emojiReactions",
					"@type": "@id"
				}
			}
		],
		id: "https://example.com/notes/1",
		type: "Note",
		content: "Hello, world!"
	});
	request = new Request("https://example.com/", { headers: { Accept: "text/html" } });
	response = await respondWithObjectIfAcceptable(new Note({
		id: new URL("https://example.com/notes/1"),
		content: "Hello, world!"
	}), request, { contextLoader: mockDocumentLoader });
	assertEquals(response, null);
});
test("handleCustomCollection()", async () => {
	const federation = createFederation({ kv: new MemoryKvStore() });
	let context = createRequestContext({
		federation,
		data: void 0,
		url: new URL("https://example.com/")
	});
	const dispatcher = (_ctx, values, cursor) => {
		if (values.handle !== "someone") return null;
		const items = [
			new Create({ id: new URL("https://example.com/activities/1") }),
			new Create({ id: new URL("https://example.com/activities/2") }),
			new Create({ id: new URL("https://example.com/activities/3") })
		];
		if (cursor != null) {
			const idx = parseInt(cursor);
			return {
				items: [items[idx]],
				nextCursor: idx < items.length - 1 ? (idx + 1).toString() : null,
				prevCursor: idx > 0 ? (idx - 1).toString() : null
			};
		}
		return { items };
	};
	const counter = (_ctx, values) => values.handle === "someone" ? 3 : null;
	const firstCursor = (_ctx, values) => values.handle === "someone" ? "0" : null;
	const lastCursor = (_ctx, values) => values.handle === "someone" ? "2" : null;
	const callbacks = {
		dispatcher,
		counter,
		firstCursor,
		lastCursor
	};
	let onNotFoundCalled = null;
	const onNotFound = (request) => {
		onNotFoundCalled = request;
		return new Response("Not found", { status: 404 });
	};
	let onUnauthorizedCalled = null;
	const onUnauthorized = (request) => {
		onUnauthorizedCalled = request;
		return new Response("Unauthorized", { status: 401 });
	};
	const errorHandlers = {
		onNotFound,
		onUnauthorized
	};
	let response = await handleCustomCollection(context.request, {
		context,
		name: "custom collection",
		values: { handle: "someone" },
		...errorHandlers
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	context = createRequestContext({
		...context,
		request: new Request(context.url, { headers: { Accept: "application/activity+json" } })
	});
	response = await handleCustomCollection(context.request, {
		context,
		name: "custom collection",
		values: { handle: "no-one" },
		collectionCallbacks: { dispatcher },
		...errorHandlers
	});
	assertEquals(response.status, 404);
	assertEquals(onNotFoundCalled, context.request);
	assertEquals(onUnauthorizedCalled, null);
	onNotFoundCalled = null;
	response = await handleCustomCollection(context.request, {
		context,
		name: "custom collection",
		values: { handle: "someone" },
		collectionCallbacks: { dispatcher },
		...errorHandlers
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	const createCtx = [
		"https://w3id.org/identity/v1",
		"https://www.w3.org/ns/activitystreams",
		"https://w3id.org/security/data-integrity/v1",
		{
			toot: "http://joinmastodon.org/ns#",
			misskey: "https://misskey-hub.net/ns#",
			fedibird: "http://fedibird.com/ns#",
			ChatMessage: "http://litepub.social/ns#ChatMessage",
			Emoji: "toot:Emoji",
			Hashtag: "as:Hashtag",
			sensitive: "as:sensitive",
			votersCount: {
				"@id": "toot:votersCount",
				"@type": "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"
			},
			_misskey_quote: "misskey:_misskey_quote",
			quoteUri: "fedibird:quoteUri",
			quoteUrl: "as:quoteUrl",
			emojiReactions: {
				"@id": "fedibird:emojiReactions",
				"@type": "@id"
			}
		}
	];
	const CONTEXT = [
		"https://www.w3.org/ns/activitystreams",
		"https://w3id.org/security/data-integrity/v1",
		{
			toot: "http://joinmastodon.org/ns#",
			misskey: "https://misskey-hub.net/ns#",
			fedibird: "http://fedibird.com/ns#",
			ChatMessage: "http://litepub.social/ns#ChatMessage",
			Emoji: "toot:Emoji",
			Hashtag: "as:Hashtag",
			sensitive: "as:sensitive",
			votersCount: "toot:votersCount",
			_misskey_quote: "misskey:_misskey_quote",
			quoteUri: "fedibird:quoteUri",
			quoteUrl: "as:quoteUrl",
			emojiReactions: {
				"@id": "fedibird:emojiReactions",
				"@type": "@id"
			}
		}
	];
	assertEquals(await response.json(), {
		"@context": CONTEXT,
		id: "https://example.com/",
		type: "Collection",
		items: [
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/1"
			},
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/2"
			},
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/3"
			}
		]
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	response = await handleCustomCollection(context.request, {
		context,
		name: "custom collection",
		values: { handle: "someone" },
		collectionCallbacks: {
			dispatcher,
			authorizePredicate: (_ctx, _values, key, keyOwner) => key != null && keyOwner != null
		},
		...errorHandlers
	});
	assertEquals(response.status, 401);
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, context.request);
	onUnauthorizedCalled = null;
	context = createRequestContext({
		...context,
		getSignedKey: () => Promise.resolve(rsaPublicKey2),
		getSignedKeyOwner: () => Promise.resolve(new Person({}))
	});
	response = await handleCustomCollection(context.request, {
		context,
		name: "custom collection",
		values: { handle: "someone" },
		collectionCallbacks: {
			dispatcher,
			authorizePredicate: (_ctx, _values, key, keyOwner) => key != null && keyOwner != null
		},
		...errorHandlers
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": CONTEXT,
		id: "https://example.com/",
		type: "Collection",
		items: [
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/1"
			},
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/2"
			},
			{
				"@context": createCtx,
				type: "Create",
				id: "https://example.com/activities/3"
			}
		]
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	response = await handleCustomCollection(context.request, {
		context,
		name: "custom collection",
		values: { handle: "someone" },
		collectionCallbacks: callbacks,
		...errorHandlers
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": CONTEXT,
		id: "https://example.com/",
		type: "Collection",
		totalItems: 3,
		first: "https://example.com/?cursor=0",
		last: "https://example.com/?cursor=2"
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	let url = new URL("https://example.com/?cursor=0");
	context = createRequestContext({
		...context,
		url,
		request: new Request(url, { headers: { Accept: "application/activity+json" } })
	});
	response = await handleCustomCollection(context.request, {
		context,
		name: "custom collection",
		values: { handle: "someone" },
		collectionCallbacks: callbacks,
		...errorHandlers
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": CONTEXT,
		id: "https://example.com/?cursor=0",
		type: "CollectionPage",
		partOf: "https://example.com/",
		next: "https://example.com/?cursor=1",
		items: {
			"@context": createCtx,
			id: "https://example.com/activities/1",
			type: "Create"
		}
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
	url = new URL("https://example.com/?cursor=2");
	context = createRequestContext({
		...context,
		url,
		request: new Request(url, { headers: { Accept: "application/activity+json" } })
	});
	response = await handleCustomCollection(context.request, {
		context,
		name: "custom collection",
		values: { handle: "someone" },
		collectionCallbacks: callbacks,
		...errorHandlers
	});
	assertEquals(response.status, 200);
	assertEquals(response.headers.get("Content-Type"), "application/activity+json");
	assertEquals(await response.json(), {
		"@context": CONTEXT,
		id: "https://example.com/?cursor=2",
		type: "CollectionPage",
		partOf: "https://example.com/",
		prev: "https://example.com/?cursor=1",
		items: {
			"@context": createCtx,
			id: "https://example.com/activities/3",
			type: "Create"
		}
	});
	assertEquals(onNotFoundCalled, null);
	assertEquals(onUnauthorizedCalled, null);
});

//#endregion