




























@@ -2,9 +2,10 @@
22import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
3344const hoisted = vi.hoisted(() => {
5-const updateSessionStore = vi.fn();
5+const listSessionEntries = vi.fn();
6+const patchSessionEntry = vi.fn();
67const resolveStorePath = vi.fn(() => "/tmp/openclaw-sessions.json");
7-return { updateSessionStore, resolveStorePath };
8+return { listSessionEntries, patchSessionEntry, resolveStorePath };
89});
9101011vi.mock("openclaw/plugin-sdk/session-store-runtime", async () => {
@@ -13,16 +14,37 @@ vi.mock("openclaw/plugin-sdk/session-store-runtime", async () => {
1314);
1415return {
1516 ...actual,
16-updateSessionStore: hoisted.updateSessionStore,
17+listSessionEntries: hoisted.listSessionEntries,
18+patchSessionEntry: hoisted.patchSessionEntry,
1719resolveStorePath: hoisted.resolveStorePath,
1820};
1921});
20222123let closeDiscordThreadSessions: typeof import("./thread-session-close.js").closeDiscordThreadSessions;
222423-function setupStore(store: Record<string, { updatedAt: number }>) {
24-hoisted.updateSessionStore.mockImplementation(
25-async (_storePath: string, mutator: (s: typeof store) => unknown) => mutator(store),
25+function setupStore(store: Record<string, { sessionId?: string; updatedAt: number }>) {
26+hoisted.listSessionEntries.mockImplementation(() =>
27+Object.entries(store).map(([sessionKey, entry]) => ({ sessionKey, entry })),
28+);
29+hoisted.patchSessionEntry.mockImplementation(
30+async (params: {
31+sessionKey: string;
32+update: (entry: {
33+sessionId?: string;
34+updatedAt: number;
35+}) => { sessionId?: string; updatedAt: number } | null;
36+}) => {
37+const entry = store[params.sessionKey];
38+if (!entry) {
39+return null;
40+}
41+const next = params.update({ ...entry });
42+if (!next) {
43+return entry;
44+}
45+store[params.sessionKey] = next;
46+return next;
47+},
2648);
2749}
2850@@ -38,7 +60,8 @@ describe("closeDiscordThreadSessions", () => {
3860});
39614062beforeEach(() => {
41-hoisted.updateSessionStore.mockClear();
63+hoisted.listSessionEntries.mockReset();
64+hoisted.patchSessionEntry.mockReset();
4265hoisted.resolveStorePath.mockClear();
4366hoisted.resolveStorePath.mockReturnValue("/tmp/openclaw-sessions.json");
4467});
@@ -143,7 +166,8 @@ describe("closeDiscordThreadSessions", () => {
143166});
144167145168expect(count).toBe(0);
146-expect(hoisted.updateSessionStore).not.toHaveBeenCalled();
169+expect(hoisted.listSessionEntries).not.toHaveBeenCalled();
170+expect(hoisted.patchSessionEntry).not.toHaveBeenCalled();
147171});
148172149173it("does not recount sessions that were already reset", async () => {
@@ -164,6 +188,35 @@ describe("closeDiscordThreadSessions", () => {
164188expect(store[UNMATCHED_KEY].updatedAt).toBe(1_700_000_000_001);
165189});
166190191+it("does not reset a matching session that changed after the list snapshot", async () => {
192+const store = {
193+[MATCHED_KEY]: {
194+sessionId: "fresh-session",
195+updatedAt: 2_000,
196+},
197+};
198+setupStore(store);
199+hoisted.listSessionEntries.mockReturnValue([
200+{
201+sessionKey: MATCHED_KEY,
202+entry: {
203+sessionId: "old-session",
204+updatedAt: 1_000,
205+},
206+},
207+]);
208+209+const count = await closeDiscordThreadSessions({
210+cfg: {},
211+accountId: "default",
212+threadId: THREAD_ID,
213+});
214+215+expect(count).toBe(0);
216+expect(store[MATCHED_KEY].updatedAt).toBe(2_000);
217+expect(store[MATCHED_KEY].sessionId).toBe("fresh-session");
218+});
219+167220it("resolves the store path using cfg.session.store and accountId", async () => {
168221const store = {};
169222setupStore(store);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。