























1+// Gateway tests cover archived-transcript retention cleanup: every retention
2+// rule shares one directory listing per cleanup call. Store maintenance runs
3+// this on each save, so per-rule listings would multiply READDIR load.
4+import fsPromises from "node:fs/promises";
5+import os from "node:os";
6+import path from "node:path";
7+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8+import { cleanupArchivedSessionTranscripts } from "./session-transcript-files.fs.js";
9+10+const DAY_MS = 24 * 60 * 60 * 1000;
11+const NOW_MS = Date.parse("2026-06-02T00:00:00.000Z");
12+const OLD_STAMP = "2026-01-01T00-00-00.000Z";
13+const FRESH_STAMP = "2026-06-01T00-00-00.000Z";
14+15+describe("cleanupArchivedSessionTranscripts", () => {
16+let dir = "";
17+18+beforeEach(async () => {
19+dir = await fsPromises.mkdtemp(path.join(os.tmpdir(), "openclaw-archive-cleanup-"));
20+});
21+22+afterEach(async () => {
23+vi.restoreAllMocks();
24+await fsPromises.rm(dir, { recursive: true, force: true });
25+});
26+27+async function seed(names: string[]): Promise<void> {
28+for (const name of names) {
29+await fsPromises.writeFile(path.join(dir, name), "");
30+}
31+}
32+33+async function remaining(): Promise<string[]> {
34+return (await fsPromises.readdir(dir)).toSorted();
35+}
36+37+it("applies every retention rule from a single directory listing", async () => {
38+await seed([
39+`a.jsonl.deleted.${OLD_STAMP}`,
40+`b.jsonl.reset.${OLD_STAMP}`,
41+`c.jsonl.reset.${FRESH_STAMP}`,
42+"live.jsonl",
43+]);
44+const readdirSpy = vi.spyOn(fsPromises, "readdir");
45+46+const result = await cleanupArchivedSessionTranscripts({
47+directories: [dir],
48+rules: [
49+{ reason: "deleted", olderThanMs: 30 * DAY_MS },
50+{ reason: "reset", olderThanMs: 30 * DAY_MS },
51+],
52+nowMs: NOW_MS,
53+});
54+55+expect(readdirSpy).toHaveBeenCalledTimes(1);
56+expect(result).toEqual({ removed: 2, scanned: 3 });
57+expect(await remaining()).toEqual([`c.jsonl.reset.${FRESH_STAMP}`, "live.jsonl"]);
58+});
59+60+it("applies each rule's age threshold independently", async () => {
61+await seed([`a.jsonl.deleted.${OLD_STAMP}`, `b.jsonl.reset.${OLD_STAMP}`]);
62+63+const result = await cleanupArchivedSessionTranscripts({
64+directories: [dir],
65+rules: [
66+{ reason: "deleted", olderThanMs: 30 * DAY_MS },
67+{ reason: "reset", olderThanMs: 365 * DAY_MS },
68+],
69+nowMs: NOW_MS,
70+});
71+72+expect(result).toEqual({ removed: 1, scanned: 2 });
73+expect(await remaining()).toEqual([`b.jsonl.reset.${OLD_STAMP}`]);
74+});
75+76+it("keeps archives whose reason has no rule", async () => {
77+await seed([`a.jsonl.reset.${OLD_STAMP}`]);
78+79+const result = await cleanupArchivedSessionTranscripts({
80+directories: [dir],
81+rules: [{ reason: "deleted", olderThanMs: 0 }],
82+nowMs: NOW_MS,
83+});
84+85+expect(result).toEqual({ removed: 0, scanned: 0 });
86+expect(await remaining()).toEqual([`a.jsonl.reset.${OLD_STAMP}`]);
87+});
88+89+it("drops invalid rules and never lists when none remain", async () => {
90+const readdirSpy = vi.spyOn(fsPromises, "readdir");
91+92+const result = await cleanupArchivedSessionTranscripts({
93+directories: [dir],
94+rules: [
95+{ reason: "deleted", olderThanMs: Number.NaN },
96+{ reason: "reset", olderThanMs: -1 },
97+],
98+nowMs: NOW_MS,
99+});
100+101+expect(result).toEqual({ removed: 0, scanned: 0 });
102+expect(readdirSpy).not.toHaveBeenCalled();
103+});
104+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。