test(memory): speed up host fixture setup

This commit is contained in:
Peter Steinberger
2026-04-25 18:41:01 +01:00
parent f56bf63b06
commit 8c93745f0f
4 changed files with 145 additions and 144 deletions

View File

@@ -1,4 +1,4 @@
import fs from "node:fs/promises";
import fsSync from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
@@ -19,21 +19,21 @@ import {
let sharedTempRoot = "";
let sharedTempId = 0;
beforeAll(async () => {
sharedTempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "memory-host-sdk-tests-"));
beforeAll(() => {
sharedTempRoot = fsSync.mkdtempSync(path.join(os.tmpdir(), "memory-host-sdk-tests-"));
});
afterAll(async () => {
afterAll(() => {
if (sharedTempRoot) {
await fs.rm(sharedTempRoot, { recursive: true, force: true });
fsSync.rmSync(sharedTempRoot, { recursive: true, force: true });
}
});
function setupTempDirLifecycle(prefix: string): () => string {
let tmpDir = "";
beforeEach(async () => {
beforeEach(() => {
tmpDir = path.join(sharedTempRoot, `${prefix}${sharedTempId++}`);
await fs.mkdir(tmpDir, { recursive: true });
fsSync.mkdirSync(tmpDir, { recursive: true });
});
return () => tmpDir;
}
@@ -63,12 +63,12 @@ describe("listMemoryFiles", () => {
it("includes files from additional paths (directory)", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const extraDir = path.join(tmpDir, "extra-notes");
await fs.mkdir(extraDir, { recursive: true });
await fs.writeFile(path.join(extraDir, "note1.md"), "# Note 1");
await fs.writeFile(path.join(extraDir, "note2.md"), "# Note 2");
await fs.writeFile(path.join(extraDir, "ignore.txt"), "Not a markdown file");
fsSync.mkdirSync(extraDir, { recursive: true });
fsSync.writeFileSync(path.join(extraDir, "note1.md"), "# Note 1");
fsSync.writeFileSync(path.join(extraDir, "note2.md"), "# Note 2");
fsSync.writeFileSync(path.join(extraDir, "ignore.txt"), "Not a markdown file");
const files = await listMemoryFiles(tmpDir, [extraDir]);
expect(files).toHaveLength(3);
@@ -80,9 +80,9 @@ describe("listMemoryFiles", () => {
it("includes files from additional paths (single file)", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const singleFile = path.join(tmpDir, "standalone.md");
await fs.writeFile(singleFile, "# Standalone");
fsSync.writeFileSync(singleFile, "# Standalone");
const files = await listMemoryFiles(tmpDir, [singleFile]);
expect(files).toHaveLength(2);
@@ -91,7 +91,7 @@ describe("listMemoryFiles", () => {
it("ignores lowercase root memory.md when canonical MEMORY.md is absent", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "memory.md"), "# Legacy memory");
fsSync.writeFileSync(path.join(tmpDir, "memory.md"), "# Legacy memory");
const files = await listMemoryFiles(tmpDir, [path.join(tmpDir, "memory.md")]);
@@ -100,8 +100,8 @@ describe("listMemoryFiles", () => {
it("prefers MEMORY.md when both root files exist", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
await fs.writeFile(path.join(tmpDir, "memory.md"), "# Legacy memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "memory.md"), "# Legacy memory");
const files = await listMemoryFiles(tmpDir, [path.join(tmpDir, "memory.md"), tmpDir]);
@@ -110,10 +110,10 @@ describe("listMemoryFiles", () => {
it("skips root-memory repair backups from extra workspace paths", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const repairDir = path.join(tmpDir, ".openclaw-repair", "root-memory", "2026-04-23");
await fs.mkdir(repairDir, { recursive: true });
await fs.writeFile(path.join(repairDir, "memory.md"), "# Archived legacy memory");
fsSync.mkdirSync(repairDir, { recursive: true });
fsSync.writeFileSync(path.join(repairDir, "memory.md"), "# Archived legacy memory");
const files = await listMemoryFiles(tmpDir, [tmpDir]);
@@ -123,10 +123,10 @@ describe("listMemoryFiles", () => {
it("handles relative paths in additional paths", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const extraDir = path.join(tmpDir, "subdir");
await fs.mkdir(extraDir, { recursive: true });
await fs.writeFile(path.join(extraDir, "nested.md"), "# Nested");
fsSync.mkdirSync(extraDir, { recursive: true });
fsSync.writeFileSync(path.join(extraDir, "nested.md"), "# Nested");
const files = await listMemoryFiles(tmpDir, ["subdir"]);
expect(files).toHaveLength(2);
@@ -135,7 +135,7 @@ describe("listMemoryFiles", () => {
it("ignores non-existent additional paths", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const files = await listMemoryFiles(tmpDir, ["/does/not/exist"]);
expect(files).toHaveLength(1);
@@ -143,24 +143,24 @@ describe("listMemoryFiles", () => {
it("ignores symlinked files and directories", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const extraDir = path.join(tmpDir, "extra");
await fs.mkdir(extraDir, { recursive: true });
await fs.writeFile(path.join(extraDir, "note.md"), "# Note");
fsSync.mkdirSync(extraDir, { recursive: true });
fsSync.writeFileSync(path.join(extraDir, "note.md"), "# Note");
const targetFile = path.join(tmpDir, "target.md");
await fs.writeFile(targetFile, "# Target");
fsSync.writeFileSync(targetFile, "# Target");
const linkFile = path.join(extraDir, "linked.md");
const targetDir = path.join(tmpDir, "target-dir");
await fs.mkdir(targetDir, { recursive: true });
await fs.writeFile(path.join(targetDir, "nested.md"), "# Nested");
fsSync.mkdirSync(targetDir, { recursive: true });
fsSync.writeFileSync(path.join(targetDir, "nested.md"), "# Nested");
const linkDir = path.join(tmpDir, "linked-dir");
let symlinksOk = true;
try {
await fs.symlink(targetFile, linkFile, "file");
await fs.symlink(targetDir, linkDir, "dir");
fsSync.symlinkSync(targetFile, linkFile, "file");
fsSync.symlinkSync(targetDir, linkDir, "dir");
} catch (err) {
const code = (err as NodeJS.ErrnoException).code;
if (code === "EPERM" || code === "EACCES") {
@@ -180,7 +180,7 @@ describe("listMemoryFiles", () => {
it("dedupes overlapping extra paths that resolve to the same file", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const files = await listMemoryFiles(tmpDir, [tmpDir, ".", path.join(tmpDir, "MEMORY.md")]);
const memoryMatches = files.filter((file) => file.endsWith("MEMORY.md"));
expect(memoryMatches).toHaveLength(1);
@@ -189,10 +189,10 @@ describe("listMemoryFiles", () => {
it("includes image and audio files from extra paths when multimodal is enabled", async () => {
const tmpDir = getTmpDir();
const extraDir = path.join(tmpDir, "media");
await fs.mkdir(extraDir, { recursive: true });
await fs.writeFile(path.join(extraDir, "diagram.png"), Buffer.from("png"));
await fs.writeFile(path.join(extraDir, "note.wav"), Buffer.from("wav"));
await fs.writeFile(path.join(extraDir, "ignore.bin"), Buffer.from("bin"));
fsSync.mkdirSync(extraDir, { recursive: true });
fsSync.writeFileSync(path.join(extraDir, "diagram.png"), Buffer.from("png"));
fsSync.writeFileSync(path.join(extraDir, "note.wav"), Buffer.from("wav"));
fsSync.writeFileSync(path.join(extraDir, "ignore.bin"), Buffer.from("bin"));
const files = await listMemoryFiles(tmpDir, [extraDir], multimodal);
expect(files.some((file) => file.endsWith("diagram.png"))).toBe(true);
@@ -218,8 +218,8 @@ describe("buildFileEntry", () => {
it("returns null when the file disappears before reading", async () => {
const tmpDir = getTmpDir();
const target = path.join(tmpDir, "ghost.md");
await fs.writeFile(target, "ghost", "utf-8");
await fs.rm(target);
fsSync.writeFileSync(target, "ghost", "utf-8");
fsSync.rmSync(target);
const entry = await buildFileEntry(target, tmpDir);
expect(entry).toBeNull();
});
@@ -227,7 +227,7 @@ describe("buildFileEntry", () => {
it("returns metadata when the file exists", async () => {
const tmpDir = getTmpDir();
const target = path.join(tmpDir, "note.md");
await fs.writeFile(target, "hello", "utf-8");
fsSync.writeFileSync(target, "hello", "utf-8");
const entry = await buildFileEntry(target, tmpDir);
expect(entry).not.toBeNull();
expect(entry?.path).toBe("note.md");
@@ -237,7 +237,7 @@ describe("buildFileEntry", () => {
it("returns multimodal metadata for eligible image files", async () => {
const tmpDir = getTmpDir();
const target = path.join(tmpDir, "diagram.png");
await fs.writeFile(target, Buffer.from("png"));
fsSync.writeFileSync(target, Buffer.from("png"));
const entry = await buildFileEntry(target, tmpDir, multimodal);
@@ -253,7 +253,7 @@ describe("buildFileEntry", () => {
it("builds a multimodal chunk lazily for indexing", async () => {
const tmpDir = getTmpDir();
const target = path.join(tmpDir, "diagram.png");
await fs.writeFile(target, Buffer.from("png"));
fsSync.writeFileSync(target, Buffer.from("png"));
const entry = await buildFileEntry(target, tmpDir, multimodal);
const built = await buildMultimodalChunkForIndexing(entry!);
@@ -269,24 +269,24 @@ describe("buildFileEntry", () => {
for (const testCase of [
{
name: "grows",
mutate: async (target: string, entrySize: number) => {
await fs.writeFile(target, Buffer.alloc(entrySize + 32, 1));
mutate: (target: string, entrySize: number) => {
fsSync.writeFileSync(target, Buffer.alloc(entrySize + 32, 1));
},
},
{
name: "bytes change",
mutate: async (target: string) => {
await fs.writeFile(target, Buffer.from("gif"));
mutate: (target: string) => {
fsSync.writeFileSync(target, Buffer.from("gif"));
},
},
] as const) {
const tmpDir = getTmpDir();
const target = path.join(tmpDir, `${testCase.name}.png`);
await fs.writeFile(target, Buffer.from("png"));
fsSync.writeFileSync(target, Buffer.from("png"));
const entry = await buildFileEntry(target, tmpDir, multimodal);
expect(entry, testCase.name).not.toBeNull();
await testCase.mutate(target, entry!.size);
testCase.mutate(target, entry!.size);
await expect(buildMultimodalChunkForIndexing(entry!), testCase.name).resolves.toBeNull();
}

View File

@@ -1,4 +1,4 @@
import fs from "node:fs/promises";
import fsSync from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@@ -9,17 +9,17 @@ let tmpDir: string;
let originalStateDir: string | undefined;
let fixtureId = 0;
beforeAll(async () => {
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "session-entry-test-"));
beforeAll(() => {
fixtureRoot = fsSync.mkdtempSync(path.join(os.tmpdir(), "session-entry-test-"));
});
afterAll(async () => {
await fs.rm(fixtureRoot, { recursive: true, force: true });
afterAll(() => {
fsSync.rmSync(fixtureRoot, { recursive: true, force: true });
});
beforeEach(async () => {
beforeEach(() => {
tmpDir = path.join(fixtureRoot, `case-${fixtureId++}`);
await fs.mkdir(tmpDir, { recursive: true });
fsSync.mkdirSync(tmpDir, { recursive: true });
originalStateDir = process.env.OPENCLAW_STATE_DIR;
process.env.OPENCLAW_STATE_DIR = tmpDir;
});
@@ -35,7 +35,7 @@ afterEach(() => {
describe("listSessionFilesForAgent", () => {
it("includes reset and deleted transcripts in session file listing", async () => {
const sessionsDir = path.join(tmpDir, "agents", "main", "sessions");
await fs.mkdir(path.join(sessionsDir, "archive"), { recursive: true });
fsSync.mkdirSync(path.join(sessionsDir, "archive"), { recursive: true });
const included = [
"active.jsonl",
@@ -45,9 +45,9 @@ describe("listSessionFilesForAgent", () => {
const excluded = ["active.jsonl.bak.2026-02-16T22-28-33.000Z", "sessions.json", "notes.md"];
for (const fileName of [...included, ...excluded]) {
await fs.writeFile(path.join(sessionsDir, fileName), "");
fsSync.writeFileSync(path.join(sessionsDir, fileName), "");
}
await fs.writeFile(
fsSync.writeFileSync(
path.join(sessionsDir, "archive", "nested.jsonl.deleted.2026-02-16T22-29-33.000Z"),
"",
);
@@ -81,7 +81,7 @@ describe("buildSessionEntry", () => {
JSON.stringify({ type: "message", message: { role: "user", content: "Tell me a joke" } }),
];
const filePath = path.join(tmpDir, "session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -107,7 +107,7 @@ describe("buildSessionEntry", () => {
JSON.stringify({ type: "session-meta", agentId: "test" }),
];
const filePath = path.join(tmpDir, "empty-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -124,7 +124,7 @@ describe("buildSessionEntry", () => {
JSON.stringify({ type: "message", message: { role: "assistant", content: "Second" } }),
];
const filePath = path.join(tmpDir, "gaps.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -154,7 +154,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "enveloped-session-array.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();

View File

@@ -1,3 +1,4 @@
import fsSync from "node:fs";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
@@ -19,21 +20,21 @@ import {
let sharedTempRoot = "";
let sharedTempId = 0;
beforeAll(async () => {
sharedTempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "memory-host-sdk-tests-"));
beforeAll(() => {
sharedTempRoot = fsSync.mkdtempSync(path.join(os.tmpdir(), "memory-host-sdk-tests-"));
});
afterAll(async () => {
afterAll(() => {
if (sharedTempRoot) {
await fs.rm(sharedTempRoot, { recursive: true, force: true });
fsSync.rmSync(sharedTempRoot, { recursive: true, force: true });
}
});
function setupTempDirLifecycle(prefix: string): () => string {
let tmpDir = "";
beforeEach(async () => {
beforeEach(() => {
tmpDir = path.join(sharedTempRoot, `${prefix}${sharedTempId++}`);
await fs.mkdir(tmpDir, { recursive: true });
fsSync.mkdirSync(tmpDir, { recursive: true });
});
return () => tmpDir;
}
@@ -63,12 +64,12 @@ describe("listMemoryFiles", () => {
it("includes files from additional paths (directory)", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const extraDir = path.join(tmpDir, "extra-notes");
await fs.mkdir(extraDir, { recursive: true });
await fs.writeFile(path.join(extraDir, "note1.md"), "# Note 1");
await fs.writeFile(path.join(extraDir, "note2.md"), "# Note 2");
await fs.writeFile(path.join(extraDir, "ignore.txt"), "Not a markdown file");
fsSync.mkdirSync(extraDir, { recursive: true });
fsSync.writeFileSync(path.join(extraDir, "note1.md"), "# Note 1");
fsSync.writeFileSync(path.join(extraDir, "note2.md"), "# Note 2");
fsSync.writeFileSync(path.join(extraDir, "ignore.txt"), "Not a markdown file");
const files = await listMemoryFiles(tmpDir, [extraDir]);
expect(files).toHaveLength(3);
@@ -80,9 +81,9 @@ describe("listMemoryFiles", () => {
it("includes files from additional paths (single file)", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const singleFile = path.join(tmpDir, "standalone.md");
await fs.writeFile(singleFile, "# Standalone");
fsSync.writeFileSync(singleFile, "# Standalone");
const files = await listMemoryFiles(tmpDir, [singleFile]);
expect(files).toHaveLength(2);
@@ -91,7 +92,7 @@ describe("listMemoryFiles", () => {
it("ignores lowercase root memory.md when canonical MEMORY.md is absent", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "memory.md"), "# Legacy memory");
fsSync.writeFileSync(path.join(tmpDir, "memory.md"), "# Legacy memory");
const files = await listMemoryFiles(tmpDir, [path.join(tmpDir, "memory.md")]);
@@ -164,10 +165,10 @@ describe("listMemoryFiles", () => {
},
] as const) {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const repairDir = path.join(tmpDir, ".openclaw-repair", "root-memory", "2026-04-23");
await fs.mkdir(repairDir, { recursive: true });
await fs.writeFile(path.join(repairDir, "memory.md"), "# Archived legacy memory");
fsSync.mkdirSync(repairDir, { recursive: true });
fsSync.writeFileSync(path.join(repairDir, "memory.md"), "# Archived legacy memory");
const files = await listMemoryFiles(tmpDir, testCase.extraPaths(tmpDir));
@@ -178,10 +179,10 @@ describe("listMemoryFiles", () => {
it("handles relative paths in additional paths", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const extraDir = path.join(tmpDir, "subdir");
await fs.mkdir(extraDir, { recursive: true });
await fs.writeFile(path.join(extraDir, "nested.md"), "# Nested");
fsSync.mkdirSync(extraDir, { recursive: true });
fsSync.writeFileSync(path.join(extraDir, "nested.md"), "# Nested");
const files = await listMemoryFiles(tmpDir, ["subdir"]);
expect(files).toHaveLength(2);
@@ -190,7 +191,7 @@ describe("listMemoryFiles", () => {
it("ignores non-existent additional paths", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const files = await listMemoryFiles(tmpDir, ["/does/not/exist"]);
expect(files).toHaveLength(1);
@@ -198,24 +199,24 @@ describe("listMemoryFiles", () => {
it("ignores symlinked files and directories", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const extraDir = path.join(tmpDir, "extra");
await fs.mkdir(extraDir, { recursive: true });
await fs.writeFile(path.join(extraDir, "note.md"), "# Note");
fsSync.mkdirSync(extraDir, { recursive: true });
fsSync.writeFileSync(path.join(extraDir, "note.md"), "# Note");
const targetFile = path.join(tmpDir, "target.md");
await fs.writeFile(targetFile, "# Target");
fsSync.writeFileSync(targetFile, "# Target");
const linkFile = path.join(extraDir, "linked.md");
const targetDir = path.join(tmpDir, "target-dir");
await fs.mkdir(targetDir, { recursive: true });
await fs.writeFile(path.join(targetDir, "nested.md"), "# Nested");
fsSync.mkdirSync(targetDir, { recursive: true });
fsSync.writeFileSync(path.join(targetDir, "nested.md"), "# Nested");
const linkDir = path.join(tmpDir, "linked-dir");
let symlinksOk = true;
try {
await fs.symlink(targetFile, linkFile, "file");
await fs.symlink(targetDir, linkDir, "dir");
fsSync.symlinkSync(targetFile, linkFile, "file");
fsSync.symlinkSync(targetDir, linkDir, "dir");
} catch (err) {
const code = (err as NodeJS.ErrnoException).code;
if (code === "EPERM" || code === "EACCES") {
@@ -235,7 +236,7 @@ describe("listMemoryFiles", () => {
it("dedupes overlapping extra paths that resolve to the same file", async () => {
const tmpDir = getTmpDir();
await fs.writeFile(path.join(tmpDir, "MEMORY.md"), "# Default memory");
fsSync.writeFileSync(path.join(tmpDir, "MEMORY.md"), "# Default memory");
const files = await listMemoryFiles(tmpDir, [tmpDir, ".", path.join(tmpDir, "MEMORY.md")]);
const memoryMatches = files.filter((file) => file.endsWith("MEMORY.md"));
expect(memoryMatches).toHaveLength(1);
@@ -244,10 +245,10 @@ describe("listMemoryFiles", () => {
it("includes image and audio files from extra paths when multimodal is enabled", async () => {
const tmpDir = getTmpDir();
const extraDir = path.join(tmpDir, "media");
await fs.mkdir(extraDir, { recursive: true });
await fs.writeFile(path.join(extraDir, "diagram.png"), Buffer.from("png"));
await fs.writeFile(path.join(extraDir, "note.wav"), Buffer.from("wav"));
await fs.writeFile(path.join(extraDir, "ignore.bin"), Buffer.from("bin"));
fsSync.mkdirSync(extraDir, { recursive: true });
fsSync.writeFileSync(path.join(extraDir, "diagram.png"), Buffer.from("png"));
fsSync.writeFileSync(path.join(extraDir, "note.wav"), Buffer.from("wav"));
fsSync.writeFileSync(path.join(extraDir, "ignore.bin"), Buffer.from("bin"));
const files = await listMemoryFiles(tmpDir, [extraDir], multimodal);
expect(files.some((file) => file.endsWith("diagram.png"))).toBe(true);
@@ -273,8 +274,8 @@ describe("buildFileEntry", () => {
it("returns null when the file disappears before reading", async () => {
const tmpDir = getTmpDir();
const target = path.join(tmpDir, "ghost.md");
await fs.writeFile(target, "ghost", "utf-8");
await fs.rm(target);
fsSync.writeFileSync(target, "ghost", "utf-8");
fsSync.rmSync(target);
const entry = await buildFileEntry(target, tmpDir);
expect(entry).toBeNull();
});
@@ -282,7 +283,7 @@ describe("buildFileEntry", () => {
it("returns metadata when the file exists", async () => {
const tmpDir = getTmpDir();
const target = path.join(tmpDir, "note.md");
await fs.writeFile(target, "hello", "utf-8");
fsSync.writeFileSync(target, "hello", "utf-8");
const entry = await buildFileEntry(target, tmpDir);
expect(entry).not.toBeNull();
expect(entry?.path).toBe("note.md");
@@ -292,7 +293,7 @@ describe("buildFileEntry", () => {
it("returns multimodal metadata for eligible image files", async () => {
const tmpDir = getTmpDir();
const target = path.join(tmpDir, "diagram.png");
await fs.writeFile(target, Buffer.from("png"));
fsSync.writeFileSync(target, Buffer.from("png"));
const entry = await buildFileEntry(target, tmpDir, multimodal);
@@ -308,7 +309,7 @@ describe("buildFileEntry", () => {
it("builds a multimodal chunk lazily for indexing", async () => {
const tmpDir = getTmpDir();
const target = path.join(tmpDir, "diagram.png");
await fs.writeFile(target, Buffer.from("png"));
fsSync.writeFileSync(target, Buffer.from("png"));
const entry = await buildFileEntry(target, tmpDir, multimodal);
const built = await buildMultimodalChunkForIndexing(entry!);
@@ -324,30 +325,30 @@ describe("buildFileEntry", () => {
for (const testCase of [
{
name: "grows",
mutate: async (target: string, entrySize: number) => {
await fs.writeFile(target, Buffer.alloc(entrySize + 32, 1));
mutate: (target: string, entrySize: number) => {
fsSync.writeFileSync(target, Buffer.alloc(entrySize + 32, 1));
},
},
{
name: "bytes change",
mutate: async (target: string) => {
await fs.writeFile(target, Buffer.from("gif"));
mutate: (target: string) => {
fsSync.writeFileSync(target, Buffer.from("gif"));
},
},
{
name: "disappears",
mutate: async (target: string) => {
await fs.rm(target);
mutate: (target: string) => {
fsSync.rmSync(target);
},
},
] as const) {
const tmpDir = getTmpDir();
const target = path.join(tmpDir, `${testCase.name}.png`);
await fs.writeFile(target, Buffer.from("png"));
fsSync.writeFileSync(target, Buffer.from("png"));
const entry = await buildFileEntry(target, tmpDir, multimodal);
expect(entry, testCase.name).not.toBeNull();
await testCase.mutate(target, entry!.size);
testCase.mutate(target, entry!.size);
await expect(buildMultimodalChunkForIndexing(entry!), testCase.name).resolves.toBeNull();
}

View File

@@ -1,4 +1,4 @@
import fs from "node:fs/promises";
import fsSync from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@@ -9,17 +9,17 @@ let tmpDir: string;
let originalStateDir: string | undefined;
let fixtureId = 0;
beforeAll(async () => {
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "session-entry-test-"));
beforeAll(() => {
fixtureRoot = fsSync.mkdtempSync(path.join(os.tmpdir(), "session-entry-test-"));
});
afterAll(async () => {
await fs.rm(fixtureRoot, { recursive: true, force: true });
afterAll(() => {
fsSync.rmSync(fixtureRoot, { recursive: true, force: true });
});
beforeEach(async () => {
beforeEach(() => {
tmpDir = path.join(fixtureRoot, `case-${fixtureId++}`);
await fs.mkdir(tmpDir, { recursive: true });
fsSync.mkdirSync(tmpDir, { recursive: true });
originalStateDir = process.env.OPENCLAW_STATE_DIR;
process.env.OPENCLAW_STATE_DIR = tmpDir;
});
@@ -47,16 +47,16 @@ function expectNoUnpairedSurrogates(value: string): void {
}
}
async function writeSessionJsonl(fileName: string, records: readonly unknown[]): Promise<string> {
function writeSessionJsonl(fileName: string, records: readonly unknown[]): string {
const filePath = path.join(tmpDir, fileName);
await fs.writeFile(filePath, records.map((record) => JSON.stringify(record)).join("\n"));
fsSync.writeFileSync(filePath, records.map((record) => JSON.stringify(record)).join("\n"));
return filePath;
}
describe("listSessionFilesForAgent", () => {
it("includes reset and deleted transcripts in session file listing", async () => {
const sessionsDir = path.join(tmpDir, "agents", "main", "sessions");
await fs.mkdir(path.join(sessionsDir, "archive"), { recursive: true });
fsSync.mkdirSync(path.join(sessionsDir, "archive"), { recursive: true });
const included = [
"active.jsonl",
@@ -66,9 +66,9 @@ describe("listSessionFilesForAgent", () => {
const excluded = ["active.jsonl.bak.2026-02-16T22-28-33.000Z", "sessions.json", "notes.md"];
for (const fileName of [...included, ...excluded]) {
await fs.writeFile(path.join(sessionsDir, fileName), "");
fsSync.writeFileSync(path.join(sessionsDir, fileName), "");
}
await fs.writeFile(
fsSync.writeFileSync(
path.join(sessionsDir, "archive", "nested.jsonl.deleted.2026-02-16T22-29-33.000Z"),
"",
);
@@ -102,7 +102,7 @@ describe("buildSessionEntry", () => {
JSON.stringify({ type: "message", message: { role: "user", content: "Tell me a joke" } }),
];
const filePath = path.join(tmpDir, "session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -129,7 +129,7 @@ describe("buildSessionEntry", () => {
JSON.stringify({ type: "session-meta", agentId: "test" }),
];
const filePath = path.join(tmpDir, "empty-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -147,7 +147,7 @@ describe("buildSessionEntry", () => {
JSON.stringify({ type: "message", message: { role: "assistant", content: "Second" } }),
];
const filePath = path.join(tmpDir, "gaps.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -172,7 +172,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "timestamps.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -213,7 +213,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "enveloped-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -251,7 +251,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "enveloped-session-array.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -269,7 +269,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "wrapped-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -291,7 +291,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "hard-wrapped-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -315,7 +315,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "surrogate-safe-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -342,7 +342,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "assistant-sentinel.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
@@ -365,7 +365,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "dreaming-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
@@ -375,9 +375,9 @@ describe("buildSessionEntry", () => {
it("flags cron run transcripts from the sibling session store and skips their content", async () => {
const sessionsDir = path.join(tmpDir, "agents", "main", "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
fsSync.mkdirSync(sessionsDir, { recursive: true });
const filePath = path.join(sessionsDir, "cron-run-session.jsonl");
await fs.writeFile(
fsSync.writeFileSync(
filePath,
[
JSON.stringify({
@@ -396,7 +396,7 @@ describe("buildSessionEntry", () => {
}),
].join("\n"),
);
await fs.writeFile(
fsSync.writeFileSync(
path.join(sessionsDir, "sessions.json"),
JSON.stringify({
"agent:main:cron:job-1:run:run-1": {
@@ -418,9 +418,9 @@ describe("buildSessionEntry", () => {
it("flags dreaming narrative transcripts from the sibling session store before bootstrap lands", async () => {
const sessionsDir = path.join(tmpDir, "agents", "main", "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
fsSync.mkdirSync(sessionsDir, { recursive: true });
const filePath = path.join(sessionsDir, "dreaming-session.jsonl");
await fs.writeFile(
fsSync.writeFileSync(
filePath,
[
JSON.stringify({
@@ -440,7 +440,7 @@ describe("buildSessionEntry", () => {
}),
].join("\n"),
);
await fs.writeFile(
fsSync.writeFileSync(
path.join(sessionsDir, "sessions.json"),
JSON.stringify({
"agent:main:dreaming-narrative-light-1775894400455": {
@@ -476,7 +476,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "dreaming-prompt-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
@@ -594,7 +594,7 @@ describe("buildSessionEntry", () => {
] as const;
for (const testCase of cases) {
const filePath = await writeSessionJsonl(testCase.fileName, testCase.records);
const filePath = writeSessionJsonl(testCase.fileName, testCase.records);
const entry = await buildSessionEntry(filePath);
expect(entry, testCase.name).not.toBeNull();
@@ -624,7 +624,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "spoof-attempt-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
@@ -641,8 +641,8 @@ describe("buildSessionEntry", () => {
type: "message",
message: { role: "user", content: "This should never reach the dreaming corpus." },
});
await fs.writeFile(deletedPath, content);
await fs.writeFile(checkpointPath, content);
fsSync.writeFileSync(deletedPath, content);
fsSync.writeFileSync(checkpointPath, content);
const deletedEntry = await buildSessionEntry(deletedPath);
const checkpointEntry = await buildSessionEntry(checkpointPath);
@@ -670,7 +670,7 @@ describe("buildSessionEntry", () => {
}),
];
const filePath = path.join(tmpDir, "substring-marker-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
fsSync.writeFileSync(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);