openclaw-cortex/test/storage.test.ts
Claudia d41a13f914 feat: openclaw-cortex v0.1.0 — conversation intelligence plugin
Thread tracking, decision extraction, boot context generation,
pre-compaction snapshots, structured narratives.

- 10 source files, 1983 LOC TypeScript
- 9 test files, 270 tests passing
- Zero runtime dependencies
- Cerberus approved + all findings fixed
- EN/DE pattern matching, atomic file writes
- Graceful degradation (read-only workspace, corrupt JSON)
2026-02-17 12:16:49 +01:00

184 lines
5 KiB
TypeScript

import { describe, it, expect, beforeEach } from "vitest";
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import {
loadJson,
saveJson,
loadText,
saveText,
rebootDir,
ensureRebootDir,
isWritable,
getFileMtime,
isFileOlderThan,
} from "../src/storage.js";
const logger = {
info: () => {},
warn: () => {},
error: () => {},
debug: () => {},
};
function makeTmp(): string {
return mkdtempSync(join(tmpdir(), "cortex-storage-"));
}
describe("rebootDir", () => {
it("returns memory/reboot path", () => {
expect(rebootDir("/workspace")).toBe(join("/workspace", "memory", "reboot"));
});
});
describe("ensureRebootDir", () => {
it("creates the directory", () => {
const ws = makeTmp();
const ok = ensureRebootDir(ws, logger);
expect(ok).toBe(true);
const stat = require("node:fs").statSync(rebootDir(ws));
expect(stat.isDirectory()).toBe(true);
});
it("returns true if directory already exists", () => {
const ws = makeTmp();
mkdirSync(join(ws, "memory", "reboot"), { recursive: true });
expect(ensureRebootDir(ws, logger)).toBe(true);
});
});
describe("isWritable", () => {
it("returns true for writable workspace", () => {
const ws = makeTmp();
expect(isWritable(ws)).toBe(true);
});
it("returns true when memory/ dir exists and is writable", () => {
const ws = makeTmp();
mkdirSync(join(ws, "memory"), { recursive: true });
expect(isWritable(ws)).toBe(true);
});
});
describe("loadJson", () => {
it("loads valid JSON", () => {
const ws = makeTmp();
const f = join(ws, "test.json");
writeFileSync(f, '{"a":1}');
const result = loadJson<{ a: number }>(f);
expect(result.a).toBe(1);
});
it("returns empty object for missing file", () => {
const result = loadJson("/nonexistent/path.json");
expect(result).toEqual({});
});
it("returns empty object for corrupt JSON", () => {
const ws = makeTmp();
const f = join(ws, "bad.json");
writeFileSync(f, "not json {{{");
expect(loadJson(f)).toEqual({});
});
it("returns empty object for empty file", () => {
const ws = makeTmp();
const f = join(ws, "empty.json");
writeFileSync(f, "");
expect(loadJson(f)).toEqual({});
});
});
describe("saveJson", () => {
it("writes valid JSON atomically", () => {
const ws = makeTmp();
const f = join(ws, "out.json");
const ok = saveJson(f, { hello: "world" }, logger);
expect(ok).toBe(true);
const content = JSON.parse(readFileSync(f, "utf-8"));
expect(content.hello).toBe("world");
});
it("creates parent directories", () => {
const ws = makeTmp();
const f = join(ws, "sub", "deep", "out.json");
const ok = saveJson(f, { nested: true }, logger);
expect(ok).toBe(true);
expect(JSON.parse(readFileSync(f, "utf-8")).nested).toBe(true);
});
it("no .tmp file left after successful write", () => {
const ws = makeTmp();
const f = join(ws, "clean.json");
saveJson(f, { clean: true }, logger);
const fs = require("node:fs");
expect(fs.existsSync(f + ".tmp")).toBe(false);
});
it("pretty-prints with 2-space indent", () => {
const ws = makeTmp();
const f = join(ws, "pretty.json");
saveJson(f, { a: 1 }, logger);
const raw = readFileSync(f, "utf-8");
expect(raw).toContain(" ");
expect(raw.endsWith("\n")).toBe(true);
});
});
describe("loadText", () => {
it("loads text file content", () => {
const ws = makeTmp();
const f = join(ws, "note.md");
writeFileSync(f, "# Hello\nWorld");
expect(loadText(f)).toBe("# Hello\nWorld");
});
it("returns empty string for missing file", () => {
expect(loadText("/nonexistent/file.md")).toBe("");
});
});
describe("saveText", () => {
it("writes text file atomically", () => {
const ws = makeTmp();
const f = join(ws, "out.md");
const ok = saveText(f, "# Test", logger);
expect(ok).toBe(true);
expect(readFileSync(f, "utf-8")).toBe("# Test");
});
it("creates parent directories", () => {
const ws = makeTmp();
const f = join(ws, "a", "b", "out.md");
saveText(f, "deep", logger);
expect(readFileSync(f, "utf-8")).toBe("deep");
});
});
describe("getFileMtime", () => {
it("returns ISO string for existing file", () => {
const ws = makeTmp();
const f = join(ws, "file.txt");
writeFileSync(f, "x");
const mtime = getFileMtime(f);
expect(mtime).toBeTruthy();
expect(new Date(mtime!).getTime()).toBeGreaterThan(0);
});
it("returns null for missing file", () => {
expect(getFileMtime("/nonexistent")).toBeNull();
});
});
describe("isFileOlderThan", () => {
it("returns true for missing file", () => {
expect(isFileOlderThan("/nonexistent", 1)).toBe(true);
});
it("returns false for fresh file", () => {
const ws = makeTmp();
const f = join(ws, "fresh.txt");
writeFileSync(f, "new");
expect(isFileOlderThan(f, 1)).toBe(false);
});
});