feat(M4): Reusable code abstractions and dead-code removal
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, mkdir, writeFile, readFile, readdir, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
import { safeReplaceDir } from "../lib/safe-replace-dir.mjs";
|
||||
|
||||
// ── Happy path ────────────────────────────────────────────────────────────
|
||||
|
||||
test("safeReplaceDir copies source content into the target", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-copy-"));
|
||||
try {
|
||||
const safetyRoot = path.join(dir, "root");
|
||||
const source = path.join(dir, "source");
|
||||
const target = path.join(safetyRoot, "target");
|
||||
|
||||
await mkdir(source, { recursive: true });
|
||||
await writeFile(path.join(source, "file.txt"), "hello");
|
||||
await mkdir(safetyRoot, { recursive: true });
|
||||
|
||||
await safeReplaceDir(source, target, safetyRoot);
|
||||
|
||||
const content = await readFile(path.join(target, "file.txt"), "utf8");
|
||||
assert.equal(content, "hello");
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("safeReplaceDir removes existing content before replacing", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-stale-"));
|
||||
try {
|
||||
const safetyRoot = path.join(dir, "root");
|
||||
const source = path.join(dir, "source");
|
||||
const target = path.join(safetyRoot, "target");
|
||||
|
||||
await mkdir(target, { recursive: true });
|
||||
await writeFile(path.join(target, "old.txt"), "stale");
|
||||
await mkdir(source, { recursive: true });
|
||||
await writeFile(path.join(source, "new.txt"), "fresh");
|
||||
|
||||
await safeReplaceDir(source, target, safetyRoot);
|
||||
|
||||
const files = await readdir(target);
|
||||
assert.deepEqual(files.sort(), ["new.txt"]);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("safeReplaceDir creates target parent directories if they do not exist", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-mkdir-"));
|
||||
try {
|
||||
const safetyRoot = path.join(dir, "root");
|
||||
const source = path.join(dir, "source");
|
||||
const target = path.join(safetyRoot, "nested", "target");
|
||||
|
||||
await mkdir(source, { recursive: true });
|
||||
await writeFile(path.join(source, "data.txt"), "data");
|
||||
await mkdir(safetyRoot, { recursive: true });
|
||||
// nested parent does NOT exist yet
|
||||
|
||||
await safeReplaceDir(source, target, safetyRoot);
|
||||
|
||||
const content = await readFile(path.join(target, "data.txt"), "utf8");
|
||||
assert.equal(content, "data");
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("safeReplaceDir creates deeply nested parent directories (2+ levels missing)", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-deep-"));
|
||||
try {
|
||||
const safetyRoot = path.join(dir, "root");
|
||||
const source = path.join(dir, "source");
|
||||
// two parent levels (a/b) do NOT exist under safetyRoot
|
||||
const target = path.join(safetyRoot, "a", "b", "target");
|
||||
|
||||
await mkdir(source, { recursive: true });
|
||||
await writeFile(path.join(source, "deep.txt"), "deep");
|
||||
await mkdir(safetyRoot, { recursive: true });
|
||||
// a/ and a/b/ intentionally NOT created
|
||||
|
||||
await safeReplaceDir(source, target, safetyRoot);
|
||||
|
||||
const content = await readFile(path.join(target, "deep.txt"), "utf8");
|
||||
assert.equal(content, "deep");
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// ── Safety checks ─────────────────────────────────────────────────────────
|
||||
|
||||
test("safeReplaceDir refuses when target is outside the safety root", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-outside-"));
|
||||
try {
|
||||
const safetyRoot = path.join(dir, "root");
|
||||
const source = path.join(dir, "source");
|
||||
const outside = path.join(dir, "outside");
|
||||
|
||||
await mkdir(source, { recursive: true });
|
||||
await mkdir(safetyRoot, { recursive: true });
|
||||
|
||||
await assert.rejects(
|
||||
() => safeReplaceDir(source, outside, safetyRoot),
|
||||
/outside safety root/,
|
||||
);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("safeReplaceDir refuses when target equals the safety root", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-same-"));
|
||||
try {
|
||||
const safetyRoot = path.join(dir, "root");
|
||||
const source = path.join(dir, "source");
|
||||
|
||||
await mkdir(source, { recursive: true });
|
||||
await mkdir(safetyRoot, { recursive: true });
|
||||
|
||||
await assert.rejects(
|
||||
() => safeReplaceDir(source, safetyRoot, safetyRoot),
|
||||
/outside safety root/,
|
||||
);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("safeReplaceDir refuses an empty target string", async () => {
|
||||
await assert.rejects(
|
||||
() => safeReplaceDir("/any", "", "/root"),
|
||||
/unsafe target/,
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, mkdir, writeFile, rm, lstat, symlink } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
import { removeTarget } from "../lib/skill-manager-core.mjs";
|
||||
|
||||
// ── Happy path: remove existing directory ─────────────────────────────────
|
||||
|
||||
test("removeTarget removes an installed skill directory", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "smc-remove-dir-"));
|
||||
try {
|
||||
const skillsRoot = path.join(dir, "skills");
|
||||
const target = path.join(skillsRoot, "create-plan");
|
||||
await mkdir(target, { recursive: true });
|
||||
await writeFile(path.join(target, "SKILL.md"), "---\nname: create-plan\n---\n");
|
||||
|
||||
const op = { kind: "skill", action: "remove", target, skillsRoot };
|
||||
const result = await removeTarget(op);
|
||||
|
||||
assert.equal(result.status, "ok");
|
||||
let exists = true;
|
||||
try {
|
||||
await lstat(target);
|
||||
} catch {
|
||||
exists = false;
|
||||
}
|
||||
assert.equal(exists, false, "target directory should be gone");
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// ── Happy path: remove symbolic link ─────────────────────────────────────
|
||||
|
||||
test("removeTarget removes a symlink without following it", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "smc-remove-sym-"));
|
||||
try {
|
||||
const skillsRoot = path.join(dir, "skills");
|
||||
const realDir = path.join(dir, "real-skill");
|
||||
const target = path.join(skillsRoot, "create-plan");
|
||||
|
||||
await mkdir(skillsRoot, { recursive: true });
|
||||
await mkdir(realDir, { recursive: true });
|
||||
await writeFile(path.join(realDir, "SKILL.md"), "---\nname: create-plan\n---\n");
|
||||
await symlink(realDir, target, "dir");
|
||||
|
||||
const op = { kind: "skill", action: "remove", target, skillsRoot };
|
||||
const result = await removeTarget(op);
|
||||
|
||||
assert.equal(result.status, "ok");
|
||||
|
||||
// symlink itself should be gone
|
||||
let symlinkExists = true;
|
||||
try {
|
||||
await lstat(target);
|
||||
} catch {
|
||||
symlinkExists = false;
|
||||
}
|
||||
assert.equal(symlinkExists, false, "symlink should be removed");
|
||||
|
||||
// real directory should still exist
|
||||
const realStat = await lstat(realDir);
|
||||
assert.ok(realStat.isDirectory(), "real directory must not be touched");
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// ── Missing skill (partial state): target does not exist ─────────────────
|
||||
|
||||
test("removeTarget succeeds when target does not exist (idempotent)", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "smc-remove-missing-"));
|
||||
try {
|
||||
const skillsRoot = path.join(dir, "skills");
|
||||
const target = path.join(skillsRoot, "create-plan");
|
||||
await mkdir(skillsRoot, { recursive: true });
|
||||
// target intentionally NOT created
|
||||
|
||||
const op = { kind: "skill", action: "remove", target, skillsRoot };
|
||||
const result = await removeTarget(op);
|
||||
|
||||
assert.equal(result.status, "ok");
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// ── Partial state: directory exists but is empty ─────────────────────────
|
||||
|
||||
test("removeTarget removes an empty skill directory", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "smc-remove-empty-"));
|
||||
try {
|
||||
const skillsRoot = path.join(dir, "skills");
|
||||
const target = path.join(skillsRoot, "create-plan");
|
||||
await mkdir(target, { recursive: true });
|
||||
// directory exists but has no SKILL.md (partial install state)
|
||||
|
||||
const op = { kind: "skill", action: "remove", target, skillsRoot };
|
||||
const result = await removeTarget(op);
|
||||
|
||||
assert.equal(result.status, "ok");
|
||||
let exists = true;
|
||||
try {
|
||||
await lstat(target);
|
||||
} catch {
|
||||
exists = false;
|
||||
}
|
||||
assert.equal(exists, false);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// ── Safety: refuses to remove path outside skills root ────────────────────
|
||||
|
||||
test("removeTarget refuses to remove a path outside the skills root", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "smc-remove-outside-"));
|
||||
try {
|
||||
const skillsRoot = path.join(dir, "skills");
|
||||
const outsideTarget = path.join(dir, "outside");
|
||||
await mkdir(skillsRoot, { recursive: true });
|
||||
await mkdir(outsideTarget, { recursive: true });
|
||||
|
||||
const op = {
|
||||
kind: "skill",
|
||||
action: "remove",
|
||||
target: outsideTarget,
|
||||
skillsRoot,
|
||||
};
|
||||
|
||||
await assert.rejects(() => removeTarget(op), /outside skills root/);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user