






















11import fs from "node:fs";
22import path from "node:path";
33import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
4+import * as jsonFiles from "../infra/json-files.js";
45import { createSuiteTempRootTracker } from "../test-helpers/temp-dir.js";
56import {
67getSerializedSessionStore,
@@ -12,6 +13,7 @@ import {
1213writeSessionStoreCache,
1314} from "./sessions/store-cache.js";
1415import {
16+applySessionStoreEntryPatch,
1517clearSessionStoreCacheForTest,
1618loadSessionStore,
1719readSessionEntries,
@@ -21,6 +23,7 @@ import {
2123saveSessionStore,
2224updateSessionStore,
2325updateSessionStoreEntry,
26+updateLastRoute,
2427} from "./sessions/store.js";
2528import type { SessionEntry } from "./sessions/types.js";
2629@@ -627,6 +630,91 @@ describe("Session Store Cache", () => {
627630expect(cached["session:1"].displayName).toBe("Entry writer owned");
628631});
629632633+it("publishes high-level entry patches without cloning the whole object cache", async () => {
634+await saveSessionStore(storePath, {
635+"session:1": createSessionEntry({ sessionId: "id-1" }),
636+"session:2": createSessionEntry({ sessionId: "id-2" }),
637+});
638+const before = loadSessionStore(storePath, { clone: false });
639+const untouched = before["session:2"];
640+641+const persisted = await updateSessionStoreEntry({
642+ storePath,
643+sessionKey: "session:1",
644+update: async () => ({
645+displayName: "Entry writer owned by default",
646+updatedAt: Date.now() + 1,
647+}),
648+});
649+650+const cached = loadSessionStore(storePath, { clone: false });
651+expect(cached["session:2"]).toBe(untouched);
652+expect(cached["session:1"]).not.toBe(persisted);
653+persisted!.displayName = "Mutated returned entry";
654+expect(cached["session:1"].displayName).toBe("Entry writer owned by default");
655+});
656+657+it("publishes route updates without cloning the whole object cache", async () => {
658+await saveSessionStore(storePath, {
659+"session:1": createSessionEntry({ sessionId: "id-1" }),
660+"session:2": createSessionEntry({ sessionId: "id-2" }),
661+});
662+const before = loadSessionStore(storePath, { clone: false });
663+const untouched = before["session:2"];
664+665+const persisted = await updateLastRoute({
666+ storePath,
667+sessionKey: "session:1",
668+channel: "telegram",
669+to: "chat-1",
670+});
671+672+const cached = loadSessionStore(storePath, { clone: false });
673+expect(cached["session:2"]).toBe(untouched);
674+expect(cached["session:1"]).not.toBe(persisted);
675+persisted!.lastTo = "mutated-return";
676+expect(cached["session:1"].lastTo).toBe("chat-1");
677+});
678+679+it("detaches caller-owned patch objects before publishing writer-owned caches", async () => {
680+await saveSessionStore(storePath, {
681+"session:1": createSessionEntry({ sessionId: "id-1" }),
682+"session:2": createSessionEntry({ sessionId: "id-2" }),
683+});
684+const before = loadSessionStore(storePath, { clone: false });
685+const untouched = before["session:2"];
686+const deliveryContext = { channel: "telegram", to: "chat-1" };
687+688+await applySessionStoreEntryPatch({
689+ storePath,
690+sessionKey: "session:1",
691+patch: { deliveryContext },
692+});
693+deliveryContext.to = "mutated-after-persist";
694+695+const cached = loadSessionStore(storePath, { clone: false });
696+expect(cached["session:2"]).toBe(untouched);
697+expect(cached["session:1"].deliveryContext?.to).toBe("chat-1");
698+});
699+700+it("restores the writer-owned cache when update result proves the store unchanged", async () => {
701+await saveSessionStore(storePath, {
702+"session:1": createSessionEntry({ sessionId: "id-1" }),
703+"session:2": createSessionEntry({ sessionId: "id-2" }),
704+});
705+const before = loadSessionStore(storePath, { clone: false });
706+const writeSpy = vi.spyOn(jsonFiles, "writeTextAtomic");
707+708+const result = await updateSessionStore(storePath, () => 0, {
709+skipSaveWhenResult: (cleared) => cleared === 0,
710+});
711+712+const after = loadSessionStore(storePath, { clone: false });
713+expect(result).toBe(0);
714+expect(writeSpy).not.toHaveBeenCalled();
715+expect(after).toBe(before);
716+});
717+630718it("builds immutable session snapshots lazily after writes", async () => {
631719await saveSessionStore(storePath, createSingleSessionStore());
632720此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。