@fedify/fedify
Version:
An ActivityPub server framework
262 lines (261 loc) • 8.72 kB
JavaScript
import { Temporal } from "@js-temporal/polyfill";
import { URLPattern } from "urlpattern-polyfill";
globalThis.addEventListener = () => {};
import { n as kvCache, t as MockKvStore } from "../kv-cache-Ceg0c1wh.mjs";
import { deepStrictEqual, throws } from "node:assert";
import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
import { preloadedContexts } from "@fedify/vocab-runtime";
//#region src/utils/kv-cache.test.ts
test("kvCache()", async (t) => {
const kv = new MockKvStore();
await t.step("cached", async () => {
const loader = kvCache({
kv,
loader: mockDocumentLoader,
rules: [
["https://example.org/", Temporal.Duration.from({ days: 1 })],
[new URL("https://example.net/"), Temporal.Duration.from({ days: 1 })],
[new URLPattern("https://example.com/*"), Temporal.Duration.from({ days: 30 })]
],
prefix: ["_test", "cached"]
});
const result = await loader("https://example.com/object");
deepStrictEqual(result, {
contextUrl: null,
documentUrl: "https://example.com/object",
document: {
"@context": "https://www.w3.org/ns/activitystreams",
id: "https://example.com/object",
name: "Fetched object",
type: "Object"
}
});
deepStrictEqual(await kv.get([
"_test",
"cached",
"https://example.com/object"
]), result);
await kv.set([
"_test",
"cached",
"https://example.org/"
], {
contextUrl: null,
documentUrl: "https://example.org/",
document: { "id": "https://example.org/" }
});
deepStrictEqual(await loader("https://example.org/"), {
contextUrl: null,
documentUrl: "https://example.org/",
document: { "id": "https://example.org/" }
});
await kv.set([
"_test",
"cached",
"https://example.net/"
], {
contextUrl: null,
documentUrl: "https://example.net/",
document: { "id": "https://example.net/" }
});
deepStrictEqual(await loader("https://example.net/"), {
contextUrl: null,
documentUrl: "https://example.net/",
document: { "id": "https://example.net/" }
});
});
await t.step("not cached", async () => {
deepStrictEqual(await kvCache({
kv,
loader: mockDocumentLoader,
rules: [],
prefix: ["_test", "not cached"]
})("https://example.com/object"), {
contextUrl: null,
documentUrl: "https://example.com/object",
document: {
"@context": "https://www.w3.org/ns/activitystreams",
id: "https://example.com/object",
name: "Fetched object",
type: "Object"
}
});
deepStrictEqual(await kv.get([
"test2",
"not cached",
"https://example.com/object"
]), void 0);
});
await t.step("maximum cache duration", () => {
throws(() => kvCache({
kv,
loader: mockDocumentLoader,
rules: [["https://example.com/", Temporal.Duration.from({
days: 30,
seconds: 1
})]]
}), TypeError, "The maximum cache duration is 30 days");
throws(() => kvCache({
kv,
loader: mockDocumentLoader,
rules: [[new URLPattern("https://example.com/*"), Temporal.Duration.from({
days: 30,
seconds: 1
})]]
}), TypeError, "The maximum cache duration is 30 days");
});
await t.step("on kv store exception", async () => {
class KvStoreThrowsException {
get(_key) {
throw new Error("Failed to get key");
}
set(_key, _value, _options) {
throw new Error("Failed to set key");
}
delete(_key) {
throw new Error("Failed to delete key");
}
async *list(_prefix) {
throw new Error("Failed to list keys");
}
}
deepStrictEqual(await kvCache({
kv: new KvStoreThrowsException(),
loader: mockDocumentLoader,
rules: [
["https://example.org/", Temporal.Duration.from({ days: 1 })],
[new URL("https://example.net/"), Temporal.Duration.from({ days: 1 })],
[new URLPattern("https://example.com/*"), Temporal.Duration.from({ days: 30 })]
],
prefix: ["_test", "not cached"]
})("https://example.com/object"), {
contextUrl: null,
documentUrl: "https://example.com/object",
document: {
"@context": "https://www.w3.org/ns/activitystreams",
id: "https://example.com/object",
name: "Fetched object",
type: "Object"
}
});
});
await t.step("records activitypub.document.cache with miss then hit", async () => {
const kv = new MockKvStore();
const [meterProvider, recorder] = createTestMeterProvider();
const loader = kvCache({
kv,
loader: mockDocumentLoader,
rules: [["https://example.com/object", { days: 1 }]],
prefix: ["_test", "doc-cache"],
meterProvider,
kind: "object"
});
await loader("https://example.com/object");
const afterMiss = recorder.getMeasurements("activitypub.document.cache");
deepStrictEqual(afterMiss.length, 1);
deepStrictEqual(afterMiss[0].type, "counter");
deepStrictEqual(afterMiss[0].value, 1);
deepStrictEqual(afterMiss[0].attributes["activitypub.lookup.kind"], "object");
deepStrictEqual(afterMiss[0].attributes["activitypub.lookup.result"], "miss");
deepStrictEqual(afterMiss[0].attributes["activitypub.remote.host"], "example.com");
await loader("https://example.com/object");
const all = recorder.getMeasurements("activitypub.document.cache");
deepStrictEqual(all.length, 2);
deepStrictEqual(all[1].attributes["activitypub.lookup.result"], "hit");
deepStrictEqual(all[1].attributes["activitypub.lookup.kind"], "object");
deepStrictEqual(all[1].attributes["activitypub.remote.host"], "example.com");
});
await t.step("preloaded contexts emit no activitypub.document.cache", async () => {
const kv = new MockKvStore();
const [meterProvider, recorder] = createTestMeterProvider();
await kvCache({
kv,
loader: mockDocumentLoader,
prefix: ["_test", "doc-cache-preloaded"],
meterProvider,
kind: "context"
})("https://www.w3.org/ns/activitystreams");
deepStrictEqual(recorder.getMeasurements("activitypub.document.cache").length, 0, "preloaded contexts must bypass the KV cache and the cache metric");
});
await t.step("no cache rule match emits no activitypub.document.cache", async () => {
const kv = new MockKvStore();
const [meterProvider, recorder] = createTestMeterProvider();
await kvCache({
kv,
loader: mockDocumentLoader,
rules: [],
prefix: ["_test", "doc-cache-no-rule"],
meterProvider,
kind: "object"
})("https://example.com/object");
deepStrictEqual(recorder.getMeasurements("activitypub.document.cache").length, 0);
});
await t.step("omitting meterProvider records no cache measurements", async () => {
const kv = new MockKvStore();
const [meterProvider, recorder] = createTestMeterProvider();
const loader = kvCache({
kv,
loader: mockDocumentLoader,
rules: [["https://example.com/object", { days: 1 }]],
prefix: ["_test", "doc-cache-no-mp"]
});
await loader("https://example.com/object");
await loader("https://example.com/object");
deepStrictEqual(recorder.getMeasurements("activitypub.document.cache").length, 0);
deepStrictEqual(typeof meterProvider.getMeter, "function");
});
await t.step("preloaded contexts bypass cache", async () => {
const kv = new MockKvStore();
let loaderCalled = false;
const mockLoader = (url) => {
loaderCalled = true;
return Promise.resolve({
contextUrl: null,
documentUrl: url,
document: { "mock": "document" }
});
};
const loader = kvCache({
kv,
loader: mockLoader,
prefix: ["_test", "preloaded"]
});
const activityStreamsUrl = "https://www.w3.org/ns/activitystreams";
loaderCalled = false;
deepStrictEqual(await loader(activityStreamsUrl), {
contextUrl: null,
documentUrl: activityStreamsUrl,
document: preloadedContexts[activityStreamsUrl]
});
deepStrictEqual(loaderCalled, false, "Loader should not be called for preloaded contexts");
deepStrictEqual(await kv.get([
"_test",
"preloaded",
activityStreamsUrl
]), void 0, "Preloaded contexts should not be cached in KV store");
const securityUrl = "https://w3id.org/security/v1";
loaderCalled = false;
deepStrictEqual(await loader(securityUrl), {
contextUrl: null,
documentUrl: securityUrl,
document: preloadedContexts[securityUrl]
});
deepStrictEqual(loaderCalled, false, "Loader should not be called for preloaded contexts");
const nonPreloadedUrl = "https://example.com/not-preloaded";
loaderCalled = false;
const result3 = await loader(nonPreloadedUrl);
deepStrictEqual(result3, {
contextUrl: null,
documentUrl: nonPreloadedUrl,
document: { "mock": "document" }
});
deepStrictEqual(loaderCalled, true, "Loader should be called for non-preloaded URLs");
deepStrictEqual(await kv.get([
"_test",
"preloaded",
nonPreloadedUrl
]), result3, "Non-preloaded URLs should be cached");
});
});
//#endregion
export {};