Files
wehub-resource-sync 979fb22d7c
CI / test (20, macos-latest) (push) Waiting to run
CI / test (20, ubuntu-latest) (push) Waiting to run
CI / test (22, macos-latest) (push) Waiting to run
CI / test (22, ubuntu-latest) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:01:18 +08:00

248 lines
8.5 KiB
TypeScript

import { describe, it, expect, vi } from "vitest";
vi.mock("../src/logger.js", () => ({
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
function mockKV() {
const store = new Map<string, Map<string, unknown>>();
return {
store,
get: async <T>(scope: string, key: string): Promise<T | null> =>
(store.get(scope)?.get(key) as T) ?? null,
set: async <T>(scope: string, key: string, data: T): Promise<T> => {
if (!store.has(scope)) store.set(scope, new Map());
store.get(scope)!.set(key, data);
return data;
},
delete: async (scope: string, key: string) => {
store.get(scope)?.delete(key);
},
list: async <T>(scope: string): Promise<T[]> => {
const m = store.get(scope);
return m ? (Array.from(m.values()) as T[]) : [];
},
};
}
describe("access-tracker", () => {
it("getAccessLog returns empty log for unknown id", async () => {
const { getAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
const log = await getAccessLog(kv as never, "mem_xyz");
expect(log).toEqual({ memoryId: "mem_xyz", count: 0, lastAt: "", recent: [] });
});
it("recordAccess increments count and lastAt", async () => {
const { recordAccess, getAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
await recordAccess(kv as never, "mem_a", 1_000_000);
await recordAccess(kv as never, "mem_a", 2_000_000);
await recordAccess(kv as never, "mem_a", 3_000_000);
const log = await getAccessLog(kv as never, "mem_a");
expect(log.count).toBe(3);
expect(log.recent).toEqual([1_000_000, 2_000_000, 3_000_000]);
expect(log.lastAt).toBe(new Date(3_000_000).toISOString());
});
it("recent[] is bounded to last 20 entries", async () => {
const { recordAccess, getAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
for (let i = 1; i <= 50; i++) {
await recordAccess(kv as never, "mem_b", i * 1000);
}
const log = await getAccessLog(kv as never, "mem_b");
expect(log.count).toBe(50);
expect(log.recent.length).toBe(20);
// Should be the LAST 20: 31_000..50_000
expect(log.recent[0]).toBe(31_000);
expect(log.recent[19]).toBe(50_000);
});
it("recordAccessBatch deduplicates and writes once per id", async () => {
const { recordAccessBatch, getAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
await recordAccessBatch(
kv as never,
["mem_a", "mem_b", "mem_a", "mem_b", "mem_c"],
5_000_000,
);
expect((await getAccessLog(kv as never, "mem_a")).count).toBe(1);
expect((await getAccessLog(kv as never, "mem_b")).count).toBe(1);
expect((await getAccessLog(kv as never, "mem_c")).count).toBe(1);
});
it("recordAccess swallows kv.set errors (must not break reads)", async () => {
const { recordAccess } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
kv.set = (async () => {
throw new Error("boom");
}) as never;
await expect(recordAccess(kv as never, "mem_a")).resolves.toBeUndefined();
});
it("concurrent recordAccess calls do not lose increments (keyed mutex)", async () => {
const { recordAccess, getAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
await Promise.all(
Array.from({ length: 25 }, (_, i) =>
recordAccess(kv as never, "mem_race", i * 100),
),
);
const log = await getAccessLog(kv as never, "mem_race");
expect(log.count).toBe(25);
});
it("recordAccessBatch: a single failing id does not block siblings", async () => {
const { recordAccessBatch, getAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
const realSet = kv.set.bind(kv);
kv.set = (async (scope: string, key: string, val: unknown) => {
if (key === "mem_slow") throw new Error("write failed");
return realSet(scope, key, val);
}) as never;
await recordAccessBatch(
kv as never,
["mem_slow", "mem_fast_a", "mem_fast_b"],
1_000_000,
);
expect((await getAccessLog(kv as never, "mem_fast_a")).count).toBe(1);
expect((await getAccessLog(kv as never, "mem_fast_b")).count).toBe(1);
expect((await getAccessLog(kv as never, "mem_slow")).count).toBe(0);
});
it("ignores empty / falsy memory ids", async () => {
const { recordAccess, recordAccessBatch } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
await recordAccess(kv as never, "");
await recordAccessBatch(kv as never, ["", "mem_x", ""]);
expect(kv.store.get("mem:access")?.has("")).toBeFalsy();
expect(kv.store.get("mem:access")?.get("mem_x")).toBeTruthy();
});
it("deleteAccessLog removes the target entry and leaves siblings intact", async () => {
const { recordAccess, deleteAccessLog, getAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
await recordAccess(kv as never, "mem_a");
await recordAccess(kv as never, "mem_b");
await deleteAccessLog(kv as never, "mem_a");
expect(kv.store.get("mem:access")?.has("mem_a")).toBe(false);
expect((await getAccessLog(kv as never, "mem_b")).count).toBe(1);
});
it("deleteAccessLog is a no-op for unknown ids and empty ids", async () => {
const { deleteAccessLog, recordAccess } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
await recordAccess(kv as never, "mem_keep");
await deleteAccessLog(kv as never, "");
await deleteAccessLog(kv as never, "mem_unknown");
expect(kv.store.get("mem:access")?.has("mem_keep")).toBe(true);
});
it("deleteAccessLog swallows kv.delete errors", async () => {
const { deleteAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const kv = mockKV();
kv.delete = (async () => {
throw new Error("boom");
}) as never;
await expect(
deleteAccessLog(kv as never, "mem_x"),
).resolves.toBeUndefined();
});
});
describe("normalizeAccessLog", () => {
it("returns a well-formed empty log for nullish / non-object input", async () => {
const { normalizeAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const log = normalizeAccessLog(null);
expect(log).toEqual({
memoryId: "",
count: 0,
lastAt: "",
recent: [],
});
expect(normalizeAccessLog(undefined).count).toBe(0);
expect(normalizeAccessLog("garbage").count).toBe(0);
});
it("coerces count to a non-negative integer", async () => {
const { normalizeAccessLog } = await import(
"../src/functions/access-tracker.js"
);
expect(normalizeAccessLog({ count: -5 }).count).toBe(0);
expect(normalizeAccessLog({ count: 3.7 }).count).toBe(3);
expect(normalizeAccessLog({ count: NaN }).count).toBe(0);
expect(normalizeAccessLog({ count: "123" }).count).toBe(0);
});
it("preserves large lifetime counts (NOT capped at ring buffer size)", async () => {
const { normalizeAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const log = normalizeAccessLog({ memoryId: "m", count: 500, recent: [1] });
expect(log.count).toBe(500);
});
it("truncates recent[] to the last 20 entries and drops non-finite values", async () => {
const { normalizeAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const input = Array.from({ length: 40 }, (_, i) => i * 1000);
const withGarbage = [...input, NaN, Infinity, "bad" as unknown as number];
const log = normalizeAccessLog({ recent: withGarbage });
expect(log.recent.length).toBe(20);
expect(log.recent[0]).toBe(20_000);
expect(log.recent[19]).toBe(39_000);
});
it("count is at least recent.length when count < recent.length", async () => {
const { normalizeAccessLog } = await import(
"../src/functions/access-tracker.js"
);
const log = normalizeAccessLog({
count: 2,
recent: [1, 2, 3, 4, 5],
});
expect(log.count).toBeGreaterThanOrEqual(5);
});
it("fills in memoryId only when field is a string", async () => {
const { normalizeAccessLog } = await import(
"../src/functions/access-tracker.js"
);
expect(normalizeAccessLog({ memoryId: "mem_x" }).memoryId).toBe("mem_x");
expect(normalizeAccessLog({ memoryId: 42 }).memoryId).toBe("");
});
});