diff --git a/src/hooks/rules-injector/cache.test.ts b/src/hooks/rules-injector/cache.test.ts new file mode 100644 index 000000000..39a5bb472 --- /dev/null +++ b/src/hooks/rules-injector/cache.test.ts @@ -0,0 +1,74 @@ +import { afterEach, describe, expect, it } from "bun:test"; +import { randomUUID } from "node:crypto"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import { createSessionCacheStore } from "./cache"; +import { RULES_INJECTOR_STORAGE } from "./constants"; +import { clearInjectedRules, saveInjectedRules } from "./storage"; + +const trackedSessionIDs: string[] = []; + +function createSessionID(prefix: string): string { + const sessionID = `${prefix}-${randomUUID()}`; + trackedSessionIDs.push(sessionID); + return sessionID; +} + +function getStoragePath(sessionID: string): string { + return join(RULES_INJECTOR_STORAGE, `${sessionID}.json`); +} + +afterEach(() => { + for (const sessionID of trackedSessionIDs.splice(0)) { + clearInjectedRules(sessionID); + } +}); + +describe("createSessionCacheStore", () => { + it("keeps factory instances isolated for the same session", () => { + // given + const sessionID = createSessionID("cache-isolation"); + const firstStore = createSessionCacheStore(); + const secondStore = createSessionCacheStore(); + const firstCache = firstStore.getSessionCache(sessionID); + + // when + firstCache.contentHashes.add("hash:first"); + firstCache.realPaths.add("/tmp/first-rule.md"); + const secondCache = secondStore.getSessionCache(sessionID); + + // then + expect([...secondCache.contentHashes]).toEqual([]); + expect([...secondCache.realPaths]).toEqual([]); + }); + + it("clears only the targeted session cache and persisted state", () => { + // given + const deletedSessionID = createSessionID("deleted-session"); + const retainedSessionID = createSessionID("retained-session"); + + saveInjectedRules(deletedSessionID, { + contentHashes: new Set(["hash:deleted"]), + realPaths: new Set(["/tmp/deleted-rule.md"]), + }); + saveInjectedRules(retainedSessionID, { + contentHashes: new Set(["hash:retained"]), + realPaths: new Set(["/tmp/retained-rule.md"]), + }); + + const store = createSessionCacheStore(); + store.getSessionCache(deletedSessionID); + const retainedCache = store.getSessionCache(retainedSessionID); + + // when + store.clearSessionCache(deletedSessionID); + const reloadedRetainedCache = store.getSessionCache(retainedSessionID); + + // then + expect(existsSync(getStoragePath(deletedSessionID))).toBe(false); + expect(existsSync(getStoragePath(retainedSessionID))).toBe(true); + expect(reloadedRetainedCache).toBe(retainedCache); + expect([...reloadedRetainedCache.contentHashes]).toEqual(["hash:retained"]); + expect([...reloadedRetainedCache.realPaths]).toEqual(["/tmp/retained-rule.md"]); + }); +}); diff --git a/src/hooks/rules-injector/storage.test.ts b/src/hooks/rules-injector/storage.test.ts new file mode 100644 index 000000000..e12c4a45e --- /dev/null +++ b/src/hooks/rules-injector/storage.test.ts @@ -0,0 +1,57 @@ +import { afterEach, describe, expect, it } from "bun:test"; +import { randomUUID } from "node:crypto"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import { RULES_INJECTOR_STORAGE } from "./constants"; +import { + clearInjectedRules, + loadInjectedRules, + saveInjectedRules, +} from "./storage"; + +const trackedSessionIDs: string[] = []; + +function createSessionID(prefix: string): string { + const sessionID = `${prefix}-${randomUUID()}`; + trackedSessionIDs.push(sessionID); + return sessionID; +} + +function getStoragePath(sessionID: string): string { + return join(RULES_INJECTOR_STORAGE, `${sessionID}.json`); +} + +afterEach(() => { + for (const sessionID of trackedSessionIDs.splice(0)) { + clearInjectedRules(sessionID); + } +}); + +describe("storage", () => { + it("reads back only the requested session data from session-scoped files", () => { + // given + const firstSessionID = createSessionID("storage-first"); + const secondSessionID = createSessionID("storage-second"); + + saveInjectedRules(firstSessionID, { + contentHashes: new Set(["hash:first"]), + realPaths: new Set(["/tmp/first-rule.md"]), + }); + saveInjectedRules(secondSessionID, { + contentHashes: new Set(["hash:second"]), + realPaths: new Set(["/tmp/second-rule.md"]), + }); + + // when + const firstLoaded = loadInjectedRules(firstSessionID); + const secondLoaded = loadInjectedRules(secondSessionID); + + // then + expect(existsSync(getStoragePath(firstSessionID))).toBe(true); + expect(existsSync(getStoragePath(secondSessionID))).toBe(true); + expect([...firstLoaded.contentHashes]).toEqual(["hash:first"]); + expect([...firstLoaded.realPaths]).toEqual(["/tmp/first-rule.md"]); + expect([...secondLoaded.contentHashes]).toEqual(["hash:second"]); + expect([...secondLoaded.realPaths]).toEqual(["/tmp/second-rule.md"]); + }); +});