mirror of
https://github.com/sdwolf4103/opencode-working-memory.git
synced 2026-06-02 06:19:36 +02:00
fix(memory): isolate test workspace cleanup
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { after } from "node:test";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
const previousXdgDataHome = process.env.XDG_DATA_HOME;
|
||||
const previousTestFlag = process.env.OPENCODE_WORKING_MEMORY_TEST;
|
||||
const testDataHome = await mkdtemp(join(tmpdir(), "opencode-working-memory-test-xdg-"));
|
||||
|
||||
process.env.XDG_DATA_HOME = testDataHome;
|
||||
process.env.OPENCODE_WORKING_MEMORY_TEST = "1";
|
||||
|
||||
after(async () => {
|
||||
if (previousXdgDataHome === undefined) delete process.env.XDG_DATA_HOME;
|
||||
else process.env.XDG_DATA_HOME = previousXdgDataHome;
|
||||
|
||||
if (previousTestFlag === undefined) delete process.env.OPENCODE_WORKING_MEMORY_TEST;
|
||||
else process.env.OPENCODE_WORKING_MEMORY_TEST = previousTestFlag;
|
||||
|
||||
await rm(testDataHome, { recursive: true, force: true });
|
||||
});
|
||||
@@ -0,0 +1,171 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import {
|
||||
classifyWorkspaceDir,
|
||||
cleanupWorkspaceResidues,
|
||||
scanWorkspaceResidues,
|
||||
} from "../src/workspace-cleanup.ts";
|
||||
|
||||
async function writeWorkspaceStore(dataHome: string, key: string, root: string): Promise<string> {
|
||||
const workspaceDir = join(dataHome, "opencode-working-memory", "workspaces", key);
|
||||
await mkdir(workspaceDir, { recursive: true });
|
||||
await writeFile(
|
||||
join(workspaceDir, "workspace-memory.json"),
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
workspace: { root, key },
|
||||
limits: { maxRenderedChars: 5200, maxEntries: 28 },
|
||||
entries: [],
|
||||
updatedAt: "2026-04-28T00:00:00.000Z",
|
||||
}, null, 2),
|
||||
"utf8",
|
||||
);
|
||||
return workspaceDir;
|
||||
}
|
||||
|
||||
test("workspace cleanup classifies missing temp test roots as definite residue", async () => {
|
||||
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
|
||||
try {
|
||||
const missingTempRoot = join(tmpdir(), "memory-plugin-test-missing-root");
|
||||
await rm(missingTempRoot, { recursive: true, force: true });
|
||||
const workspaceDir = await writeWorkspaceStore(dataHome, "definite", missingTempRoot);
|
||||
|
||||
const result = await classifyWorkspaceDir(workspaceDir);
|
||||
|
||||
assert.equal(result.classification, "test_temp_definite");
|
||||
assert.equal(result.rootExists, false);
|
||||
assert.ok(result.reasons.includes("root_missing"));
|
||||
assert.ok(result.reasons.some(reason => reason.startsWith("root_under_temp")));
|
||||
assert.ok(result.reasons.includes("test_prefix_memory-plugin-test"));
|
||||
} finally {
|
||||
await rm(dataHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("workspace cleanup keeps existing temp roots live", async () => {
|
||||
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
|
||||
const liveRoot = await mkdtemp(join(tmpdir(), "wm-quality-live-root-"));
|
||||
try {
|
||||
const workspaceDir = await writeWorkspaceStore(dataHome, "live", liveRoot);
|
||||
|
||||
const result = await classifyWorkspaceDir(workspaceDir);
|
||||
|
||||
assert.equal(result.classification, "live_or_existing");
|
||||
assert.equal(result.rootExists, true);
|
||||
} finally {
|
||||
await rm(dataHome, { recursive: true, force: true });
|
||||
await rm(liveRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("workspace cleanup reports missing non-temp roots as unknown orphans", async () => {
|
||||
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
|
||||
try {
|
||||
const missingNonTempRoot = `/definitely-not-temp-opencode-working-memory-test-${Date.now()}`;
|
||||
const workspaceDir = await writeWorkspaceStore(dataHome, "orphan", missingNonTempRoot);
|
||||
|
||||
const result = await classifyWorkspaceDir(workspaceDir);
|
||||
|
||||
assert.equal(result.classification, "orphan_unknown");
|
||||
assert.equal(result.rootExists, false);
|
||||
assert.ok(result.reasons.includes("root_missing"));
|
||||
} finally {
|
||||
await rm(dataHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("workspace cleanup reports invalid stores without moving them", async () => {
|
||||
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
|
||||
try {
|
||||
const workspaceDir = join(dataHome, "opencode-working-memory", "workspaces", "invalid");
|
||||
await mkdir(workspaceDir, { recursive: true });
|
||||
await writeFile(join(workspaceDir, "workspace-memory.json"), "{ invalid", "utf8");
|
||||
|
||||
const result = await classifyWorkspaceDir(workspaceDir);
|
||||
|
||||
assert.equal(result.classification, "invalid_or_unreadable");
|
||||
assert.ok(result.reasons.includes("invalid_json"));
|
||||
|
||||
const cleanup = await cleanupWorkspaceResidues({ dataHome, mode: "quarantine" });
|
||||
assert.equal(cleanup.quarantined.length, 0);
|
||||
assert.equal(existsSync(workspaceDir), true);
|
||||
} finally {
|
||||
await rm(dataHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("workspace cleanup dry-run scans definite residue without moving it", async () => {
|
||||
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
|
||||
try {
|
||||
const missingTempRoot = join(tmpdir(), "wm-accounting-missing-root");
|
||||
await rm(missingTempRoot, { recursive: true, force: true });
|
||||
const workspaceDir = await writeWorkspaceStore(dataHome, "dryrun", missingTempRoot);
|
||||
|
||||
const result = await cleanupWorkspaceResidues({ dataHome, minAgeMs: 0 });
|
||||
|
||||
assert.equal(result.mode, "dry-run");
|
||||
assert.equal(result.candidates.length, 1);
|
||||
assert.equal(result.quarantined.length, 0);
|
||||
assert.equal(existsSync(workspaceDir), true);
|
||||
} finally {
|
||||
await rm(dataHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("workspace cleanup quarantine moves definite residue and writes manifest", async () => {
|
||||
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
|
||||
try {
|
||||
const missingTempRoot = join(tmpdir(), "wm-redact-missing-root");
|
||||
await rm(missingTempRoot, { recursive: true, force: true });
|
||||
const definiteDir = await writeWorkspaceStore(dataHome, "definite", missingTempRoot);
|
||||
const orphanDir = await writeWorkspaceStore(dataHome, "orphan", `/definitely-not-temp-opencode-working-memory-test-${Date.now()}`);
|
||||
|
||||
const result = await cleanupWorkspaceResidues({
|
||||
dataHome,
|
||||
mode: "quarantine",
|
||||
minAgeMs: 0,
|
||||
now: new Date("2026-04-28T12:00:00.000Z"),
|
||||
});
|
||||
|
||||
assert.equal(result.quarantined.length, 1);
|
||||
assert.equal(result.quarantined[0]?.workspaceKey, "definite");
|
||||
assert.equal(existsSync(definiteDir), false);
|
||||
assert.equal(existsSync(orphanDir), true);
|
||||
assert.ok(result.quarantineDir);
|
||||
assert.equal(existsSync(join(result.quarantineDir!, "workspaces", "definite", "workspace-memory.json")), true);
|
||||
|
||||
const manifest = await readFile(join(result.quarantineDir!, "manifest.jsonl"), "utf8");
|
||||
const event = JSON.parse(manifest.trim());
|
||||
assert.equal(event.workspaceKey, "definite");
|
||||
assert.equal(event.classification, "test_temp_definite");
|
||||
assert.equal(event.root, missingTempRoot);
|
||||
} finally {
|
||||
await rm(dataHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("workspace cleanup skips recently updated definite residue", async () => {
|
||||
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
|
||||
try {
|
||||
const missingTempRoot = join(tmpdir(), "wm-extraction-missing-root");
|
||||
await rm(missingTempRoot, { recursive: true, force: true });
|
||||
const workspaceDir = await writeWorkspaceStore(dataHome, "recent", missingTempRoot);
|
||||
|
||||
const stats = await stat(workspaceDir);
|
||||
const result = await scanWorkspaceResidues({
|
||||
dataHome,
|
||||
nowMs: stats.mtimeMs + 1_000,
|
||||
minAgeMs: 10 * 60 * 1_000,
|
||||
});
|
||||
|
||||
assert.equal(result.candidates.length, 0);
|
||||
assert.equal(result.results.find(item => item.workspaceKey === "recent")?.classification, "test_temp_definite");
|
||||
assert.ok(result.results.find(item => item.workspaceKey === "recent")?.reasons.includes("recent_workspace_dir"));
|
||||
} finally {
|
||||
await rm(dataHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -14,13 +14,13 @@ import {
|
||||
normalizeWorkspaceMemoryWithAccounting,
|
||||
workspaceMemoryExactKey,
|
||||
workspaceMemoryIdentityKey,
|
||||
redactCredentials,
|
||||
runMigrationP0Cleanup,
|
||||
runMigrationQualityCleanup,
|
||||
loadWorkspaceMemory,
|
||||
saveWorkspaceMemory,
|
||||
updateWorkspaceMemoryWithAccounting,
|
||||
} from "../src/workspace-memory.ts";
|
||||
import { redactCredentials } from "../src/redaction.ts";
|
||||
import { assessMemoryQuality, isHardQualityReason, isProgressSnapshotViolation } from "../src/memory-quality.ts";
|
||||
import { reviewerCurrent28Fixture } from "./fixtures/memory-quality-current-28.ts";
|
||||
import { REAL_WORKSPACE_FIXTURES } from "./fixtures/real-workspaces-snapshot.ts";
|
||||
|
||||
Reference in New Issue
Block a user