UNPKG

@fedify/fedify

Version:

An ActivityPub server framework

262 lines (261 loc) • 8.72 kB
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 {};