import { afterAll, beforeAll, describe, expect, it } from "bun:test"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { parseAgentContent } from "../src/agent/parser.ts"; import { isValidRegistryRef, parseRegistryRef, SkillsManager } from "../src/agent/skills.ts"; const TEST_DIR = join(tmpdir(), `mozart-skills-test-${Date.now()}`); beforeAll(() => { mkdirSync(TEST_DIR, { recursive: true }); }); afterAll(() => { rmSync(TEST_DIR, { recursive: true, force: true }); }); describe("parseRegistryRef", () => { it("parses valid owner/repo@skill-name", () => { const result = parseRegistryRef("vercel-labs/agent-skills@code-review"); expect(result).toEqual({ owner: "vercel-labs", repo: "agent-skills", skillName: "code-review", }); }); it("parses refs with nested repo names", () => { const result = parseRegistryRef("microsoft/github-copilot-for-azure@azure-ai"); expect(result).toEqual({ owner: "microsoft", repo: "github-copilot-for-azure", skillName: "azure-ai", }); }); it("returns null for bare names", () => { expect(parseRegistryRef("code-review")).toBeNull(); }); it("returns null for missing skill name", () => { expect(parseRegistryRef("owner/repo@")).toBeNull(); }); it("returns null for empty string", () => { expect(parseRegistryRef("")).toBeNull(); }); }); describe("isValidRegistryRef", () => { it("accepts valid refs", () => { expect(isValidRegistryRef("vercel-labs/agent-skills@code-review")).toBe(true); expect(isValidRegistryRef("anthropics/skills@skill-creator")).toBe(true); }); it("rejects bare names", () => { expect(isValidRegistryRef("code-review")).toBe(false); expect(isValidRegistryRef("just-a-name")).toBe(false); }); it("rejects missing parts", () => { expect(isValidRegistryRef("owner/repo")).toBe(false); expect(isValidRegistryRef("@skill-name")).toBe(false); }); }); describe("parser SKILL instruction", () => { it("parses SKILL instructions into skills array", () => { const content = [ "MODEL openai/gpt-4.1-mini", "SOUL A code reviewer.", "SKILL anthropics/skills@skill-creator", "SKILL vercel-labs/agent-skills@code-review", ].join("\n"); const config = parseAgentContent(content, "reviewer"); expect(config.skills).toEqual(["anthropics/skills@skill-creator", "vercel-labs/agent-skills@code-review"]); }); it("produces empty skills array when no SKILL lines", () => { const content = "MODEL openai/gpt-4.1-mini\nSOUL Test."; const config = parseAgentContent(content, "test"); expect(config.skills).toEqual([]); }); it("rejects bare skill names", () => { const content = ["MODEL openai/gpt-4.1-mini", "SOUL Test.", "SKILL code-review"].join("\n"); expect(() => parseAgentContent(content, "test")).toThrow("Invalid SKILL reference"); }); it("rejects SKILL with missing value", () => { const content = ["MODEL openai/gpt-4.1-mini", "SOUL Test.", "SKILL "].join("\n"); expect(() => parseAgentContent(content, "test")).toThrow("SKILL requires a value"); }); }); describe("SkillsManager", () => { it("loads a skill from disk", async () => { const skillDir = join(TEST_DIR, "test-skill"); mkdirSync(skillDir, { recursive: true }); writeFileSync( join(skillDir, "SKILL.md"), [ "---", "name: test-skill", "description: A test skill for unit testing.", "---", "", "# Test Skill", "", "This is a test skill with instructions.", ].join("\n"), ); const mgr = new SkillsManager(TEST_DIR); const installed = mgr.listInstalled(); expect(installed.length).toBeGreaterThanOrEqual(1); const skill = installed.find((s) => s.name === "test-skill"); expect(skill).toBeDefined(); expect(skill!.description).toBe("A test skill for unit testing."); expect(skill!.content).toContain("Test Skill"); }); it("fetches a real skill from GitHub", async () => { const fetchDir = join(TEST_DIR, "fetch-test"); mkdirSync(fetchDir, { recursive: true }); const mgr = new SkillsManager(fetchDir); try { const skill = await mgr.resolve("vercel-labs/skills@find-skills"); expect(skill.name).toBeTruthy(); expect(skill.content).toContain("find"); expect(skill.ref).toBe("vercel-labs/skills@find-skills"); } catch (err) { // Network may be unavailable in CI; skip gracefully console.log("Skipping GitHub fetch test (network unavailable):", (err as Error).message); } }, 15_000); });