|
| 1 | +import fsSync from "node:fs"; |
1 | 2 | import fs from "node:fs/promises"; |
2 | 3 | import os from "node:os"; |
3 | 4 | import path from "node:path"; |
4 | | -import { afterAll, beforeAll, describe, expect, it } from "vitest"; |
| 5 | +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; |
5 | 6 | import { withEnv } from "../test-utils/env.js"; |
6 | 7 | import { |
7 | 8 | buildGroupDisplayName, |
@@ -798,7 +799,7 @@ describe("sessions", () => { |
798 | 799 | await expect(fs.stat(`${storePath}.lock`)).rejects.toThrow(); |
799 | 800 | }); |
800 | 801 |
|
801 | | - it("updateSessionStoreEntry re-reads disk inside lock instead of using stale cache", async () => { |
| 802 | + it("updateSessionStoreEntry re-reads disk inside the writer slot instead of using stale cache", async () => { |
802 | 803 | const mainSessionKey = "agent:main:main"; |
803 | 804 | const { storePath } = await createSessionStoreFixture({ |
804 | 805 | prefix: "updateSessionStoreEntry-cache-bypass", |
@@ -838,4 +839,91 @@ describe("sessions", () => { |
838 | 839 | expect(store[mainSessionKey]?.providerOverride).toBe("anthropic"); |
839 | 840 | expect(store[mainSessionKey]?.thinkingLevel).toBe("high"); |
840 | 841 | }); |
| 842 | + |
| 843 | + it("updateSessionStore uses the writer-owned mutable cache without disk read or parse", async () => { |
| 844 | + const mainSessionKey = "agent:main:main"; |
| 845 | + const { storePath } = await createSessionStoreFixture({ |
| 846 | + prefix: "updateSessionStore-mutable-cache", |
| 847 | + entries: { |
| 848 | + [mainSessionKey]: { |
| 849 | + sessionId: "sess-1", |
| 850 | + updatedAt: 123, |
| 851 | + thinkingLevel: "low", |
| 852 | + }, |
| 853 | + }, |
| 854 | + }); |
| 855 | + |
| 856 | + expect(loadSessionStore(storePath)[mainSessionKey]?.thinkingLevel).toBe("low"); |
| 857 | + |
| 858 | + const readSpy = vi.spyOn(fsSync, "readFileSync"); |
| 859 | + const parseSpy = vi.spyOn(JSON, "parse"); |
| 860 | + try { |
| 861 | + await updateSessionStore( |
| 862 | + storePath, |
| 863 | + (store) => { |
| 864 | + const existing = store[mainSessionKey]; |
| 865 | + if (!existing) { |
| 866 | + throw new Error("missing session entry"); |
| 867 | + } |
| 868 | + store[mainSessionKey] = { |
| 869 | + ...existing, |
| 870 | + thinkingLevel: "high", |
| 871 | + }; |
| 872 | + }, |
| 873 | + { skipMaintenance: true }, |
| 874 | + ); |
| 875 | + |
| 876 | + expect(readSpy).not.toHaveBeenCalled(); |
| 877 | + expect(parseSpy).not.toHaveBeenCalled(); |
| 878 | + } finally { |
| 879 | + readSpy.mockRestore(); |
| 880 | + parseSpy.mockRestore(); |
| 881 | + } |
| 882 | + |
| 883 | + const store = loadSessionStore(storePath, { skipCache: true }); |
| 884 | + expect(store[mainSessionKey]?.thinkingLevel).toBe("high"); |
| 885 | + }); |
| 886 | + |
| 887 | + it("updateSessionStore drops a borrowed cache entry when a mutator throws", async () => { |
| 888 | + const mainSessionKey = "agent:main:main"; |
| 889 | + const { storePath } = await createSessionStoreFixture({ |
| 890 | + prefix: "updateSessionStore-mutable-cache-throw", |
| 891 | + entries: { |
| 892 | + [mainSessionKey]: { |
| 893 | + sessionId: "sess-1", |
| 894 | + updatedAt: 123, |
| 895 | + thinkingLevel: "low", |
| 896 | + }, |
| 897 | + }, |
| 898 | + }); |
| 899 | + |
| 900 | + expect(loadSessionStore(storePath)[mainSessionKey]?.thinkingLevel).toBe("low"); |
| 901 | + |
| 902 | + await expect( |
| 903 | + updateSessionStore( |
| 904 | + storePath, |
| 905 | + (store) => { |
| 906 | + const existing = store[mainSessionKey]; |
| 907 | + if (!existing) { |
| 908 | + throw new Error("missing session entry"); |
| 909 | + } |
| 910 | + store[mainSessionKey] = { |
| 911 | + ...existing, |
| 912 | + thinkingLevel: "mutated-before-throw", |
| 913 | + }; |
| 914 | + throw new Error("boom"); |
| 915 | + }, |
| 916 | + { skipMaintenance: true }, |
| 917 | + ), |
| 918 | + ).rejects.toThrow("boom"); |
| 919 | + |
| 920 | + const readSpy = vi.spyOn(fsSync, "readFileSync"); |
| 921 | + try { |
| 922 | + const store = loadSessionStore(storePath); |
| 923 | + expect(readSpy).toHaveBeenCalled(); |
| 924 | + expect(store[mainSessionKey]?.thinkingLevel).toBe("low"); |
| 925 | + } finally { |
| 926 | + readSpy.mockRestore(); |
| 927 | + } |
| 928 | + }); |
841 | 929 | }); |
0 commit comments