Files
rea/tests/acceptance/setup/skillPathRecoveryCli.test.ts

85 lines
3.2 KiB
TypeScript

import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { expect } from "vitest";
import { z } from "zod";
import {
MANAGED_SKILL_FILES,
existingSkillDestinations,
validateCanonicalSkillDirectories,
} from "../../../src/application/SetupSkill.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest.for(["ancestor", "selected-root"] as const)(
"explains a non-directory skill %s and recovers after preserving the blocker",
async (kind, { cli }) => {
const home = await createTestTempDirectory("rea-skill-path-recovery-");
const selected = join(home, ".agents/skills/reverse-engineer-anything");
const blocker = kind === "ancestor" ? join(home, ".agents") : selected;
await mkdir(dirname(blocker), { recursive: true });
await writeFile(blocker, "caller-owned blocker\n");
const run = (
arguments_: readonly string[] = ["setup", "--yes", "--skill", "--json"],
) =>
cli.run({
arguments: arguments_,
cwd: home,
environment: { HOME: home, USERPROFILE: home },
});
expect(await validateCanonicalSkillDirectories(home, [])).toMatchObject({
ok: false,
error: {
stage: "prepare",
cause: [{ path: selected, kind: "not_directory" }],
},
});
expect(await existingSkillDestinations(home)).toEqual({
destinations: [],
issues: [],
});
const refused = await run();
expect(refused.exitCode, refused.stderr).toBe(1);
expect(refused.json).toMatchObject({
status: "needs_human",
code: "skill_install_failed",
appliedActions: [],
});
const { remediation } = z
.object({ remediation: z.string() })
.parse(refused.json);
expect(remediation).toContain("not a directory");
expect(remediation).toContain("Use a regular skill directory");
expect(remediation).toContain("--skill=false");
expect(remediation).not.toMatch(/permissions/iu);
expect(remediation.split(selected)).toHaveLength(2);
if (kind === "ancestor") expect(remediation).toContain("ENOTDIR");
expect(await readFile(blocker, "utf8")).toBe("caller-owned blocker\n");
const doctor = await run(["doctor", "--skill", "--json"]);
expect(doctor.exitCode, doctor.stderr).toBe(1);
expect(doctor.json).toMatchObject({
healthy: false,
identity: { skill: { state: "unmanaged" } },
});
expect(doctor.stdout).toContain(selected);
const saved = `${blocker}.saved`;
await rename(blocker, saved);
await mkdir(blocker);
for (let attempt = 0; attempt < 2; attempt++) {
const installed = await run();
expect(installed.exitCode, installed.stderr).toBe(0);
expect(installed.json).toMatchObject({ status: "ready" });
for (const file of MANAGED_SKILL_FILES)
expect(
(await readFile(join(selected, file))).equals(
await readFile(resolve("skills/reverse-engineer-anything", file)),
),
file,
).toBe(true);
expect(await readFile(saved, "utf8")).toBe("caller-owned blocker\n");
}
},
);