
























1-import { describe, expect, it } from "vitest";
2-import {
3-listSessionEntries as listAccessorSessionEntries,
4-loadSessionEntry,
5-readSessionUpdatedAt as readAccessorSessionUpdatedAt,
6-} from "../config/sessions/session-accessor.js";
1+import fs from "node:fs";
2+import os from "node:os";
3+import path from "node:path";
4+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5+import * as jsonFiles from "../infra/json-files.js";
76import {
87getSessionEntry,
98listSessionEntries,
9+patchSessionEntry,
1010readSessionUpdatedAt,
11+saveSessionStore,
12+updateSessionStore,
13+updateSessionStoreEntry,
14+upsertSessionEntry,
1115} from "./session-store-runtime.js";
121613-describe("session-store-runtime", () => {
14-it("routes read helpers through the session accessor seam", () => {
15-expect(getSessionEntry).toBe(loadSessionEntry);
16-expect(listSessionEntries).toBe(listAccessorSessionEntries);
17-expect(readSessionUpdatedAt).toBe(readAccessorSessionUpdatedAt);
17+const DAY_MS = 24 * 60 * 60 * 1000;
18+19+describe("session-store-runtime compatibility surface", () => {
20+let tempDir: string;
21+let storePath: string;
22+23+beforeEach(() => {
24+tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-session-store-"));
25+storePath = path.join(tempDir, "sessions.json");
26+});
27+28+afterEach(() => {
29+fs.rmSync(tempDir, { recursive: true, force: true });
30+});
31+32+it("keeps the public session read shape while using accessor-backed exports", async () => {
33+const sessionKey = "agent:main:main";
34+await upsertSessionEntry({
35+ sessionKey,
36+ storePath,
37+entry: {
38+model: "gpt-5.5",
39+sessionId: "session-1",
40+updatedAt: 10,
41+},
42+});
43+44+expect(getSessionEntry({ sessionKey, storePath })).toMatchObject({
45+model: "gpt-5.5",
46+sessionId: "session-1",
47+updatedAt: 10,
48+});
49+expect(readSessionUpdatedAt({ sessionKey, storePath })).toEqual(expect.any(Number));
50+expect(listSessionEntries({ storePath })).toEqual([
51+{
52+ sessionKey,
53+entry: expect.objectContaining({
54+model: "gpt-5.5",
55+sessionId: "session-1",
56+updatedAt: 10,
57+}),
58+},
59+]);
60+61+await upsertSessionEntry({
62+ sessionKey,
63+ storePath,
64+entry: {
65+sessionId: "session-1",
66+updatedAt: 20,
67+},
68+});
69+expect(getSessionEntry({ sessionKey, storePath })?.model).toBeUndefined();
70+});
71+72+it("keeps the public entry mutation signature while delegating to the seam", async () => {
73+const sessionKey = "agent:main:main";
74+75+await expect(
76+updateSessionStoreEntry({
77+ sessionKey,
78+ storePath,
79+update: () => ({ model: "gpt-5.5" }),
80+}),
81+).resolves.toBeNull();
82+83+await upsertSessionEntry({
84+ sessionKey,
85+ storePath,
86+entry: {
87+sessionId: "session-1",
88+updatedAt: 10,
89+},
90+});
91+92+const beforePatch = getSessionEntry({ sessionKey, storePath });
93+await expect(
94+patchSessionEntry({
95+ sessionKey,
96+ storePath,
97+preserveActivity: true,
98+update: (_entry, context) => ({
99+providerOverride: context.existingEntry ? "openai" : "missing",
100+updatedAt: 20,
101+}),
102+}),
103+).resolves.toMatchObject({
104+providerOverride: "openai",
105+sessionId: "session-1",
106+updatedAt: beforePatch?.updatedAt,
107+});
108+109+await expect(
110+updateSessionStoreEntry({
111+ sessionKey,
112+ storePath,
113+update: () => ({ model: "gpt-5.5" }),
114+}),
115+).resolves.toMatchObject({
116+model: "gpt-5.5",
117+providerOverride: "openai",
118+sessionId: "session-1",
119+});
120+});
121+122+it("preserves resolved maintenance settings through entry patches", async () => {
123+const staleSessionKey = "agent:main:stale";
124+const activeSessionKey = "agent:main:active";
125+await saveSessionStore(
126+storePath,
127+{
128+[staleSessionKey]: {
129+sessionId: "session-stale",
130+updatedAt: 10,
131+},
132+[activeSessionKey]: {
133+sessionId: "session-active",
134+updatedAt: 20,
135+},
136+},
137+{ skipMaintenance: true },
138+);
139+140+await expect(
141+patchSessionEntry({
142+sessionKey: activeSessionKey,
143+ storePath,
144+maintenanceConfig: {
145+mode: "enforce",
146+pruneAfterMs: 7 * DAY_MS,
147+maxEntries: 1,
148+resetArchiveRetentionMs: 7 * DAY_MS,
149+maxDiskBytes: null,
150+highWaterBytes: null,
151+},
152+update: () => ({ model: "gpt-5.5" }),
153+}),
154+).resolves.toMatchObject({
155+model: "gpt-5.5",
156+sessionId: "session-active",
157+});
158+159+expect(getSessionEntry({ sessionKey: activeSessionKey, storePath })).toMatchObject({
160+model: "gpt-5.5",
161+sessionId: "session-active",
162+});
163+expect(getSessionEntry({ sessionKey: staleSessionKey, storePath })).toBeUndefined();
164+});
165+166+it("keeps deprecated whole-store mutations grouped as one compatibility operation", async () => {
167+const firstSessionKey = "agent:main:first";
168+const secondSessionKey = "agent:main:second";
169+const deletedSessionKey = "agent:main:deleted";
170+await saveSessionStore(
171+storePath,
172+{
173+[firstSessionKey]: {
174+sessionId: "session-1",
175+updatedAt: 10,
176+},
177+[secondSessionKey]: {
178+sessionId: "session-2",
179+updatedAt: 10,
180+},
181+[deletedSessionKey]: {
182+sessionId: "session-3",
183+updatedAt: 10,
184+},
185+},
186+{ skipMaintenance: true },
187+);
188+189+await expect(
190+updateSessionStore(
191+storePath,
192+(store) => {
193+const first = store[firstSessionKey];
194+const second = store[secondSessionKey];
195+if (!first || !second) {
196+throw new Error("seed session entries missing");
197+}
198+store[firstSessionKey] = {
199+ ...first,
200+model: "gpt-5.5",
201+updatedAt: 20,
202+};
203+store[secondSessionKey] = {
204+ ...second,
205+providerOverride: "openai",
206+updatedAt: 30,
207+};
208+delete store[deletedSessionKey];
209+return "whole-store-updated";
210+},
211+{ skipMaintenance: true },
212+),
213+).resolves.toBe("whole-store-updated");
214+215+expect(getSessionEntry({ sessionKey: firstSessionKey, storePath })).toMatchObject({
216+model: "gpt-5.5",
217+sessionId: "session-1",
218+updatedAt: 20,
219+});
220+expect(getSessionEntry({ sessionKey: secondSessionKey, storePath })).toMatchObject({
221+providerOverride: "openai",
222+sessionId: "session-2",
223+updatedAt: 30,
224+});
225+expect(getSessionEntry({ sessionKey: deletedSessionKey, storePath })).toBeUndefined();
226+});
227+228+it("preserves requireWriteSuccess for critical session entry updates", async () => {
229+const sessionKey = "agent:main:main";
230+await upsertSessionEntry({
231+ sessionKey,
232+ storePath,
233+entry: {
234+sessionId: "session-1",
235+updatedAt: 10,
236+},
237+});
238+const writeError = Object.assign(new Error("write failed"), { code: "ENOENT" });
239+const writeSpy = vi.spyOn(jsonFiles, "writeTextAtomic").mockRejectedValue(writeError);
240+241+try {
242+await expect(
243+updateSessionStoreEntry({
244+ sessionKey,
245+ storePath,
246+requireWriteSuccess: true,
247+update: () => ({ model: "gpt-5.5" }),
248+}),
249+).rejects.toBe(writeError);
250+} finally {
251+writeSpy.mockRestore();
252+}
18253});
19254});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。