skillhub/skillhub-cli/tests/source-parser.test.ts
chenbaowang 6c0d4f4f16 feat(skillhub-cli): add skillhub CLI with interactive multi-agent support
- Interactive agent selection for install/uninstall/list
- Namespace search selection for unspecified namespace
- --skill-version option to skip interactive selection
- --from option for local/github source installation
- Remove deprecated add command (use install --from instead)
- Path-based grouping for uninstall results
2026-04-14 10:21:09 +08:00

84 lines
2.9 KiB
TypeScript

import { describe, it, expect, vi } from "vitest";
// local fs mock
vi.mock("node:fs", () => ({
existsSync: vi.fn(),
}));
it("parses local path", async () => {
vi.resetModules();
vi.doMock("node:fs", () => ({ existsSync: vi.fn().mockReturnValue(true) }));
const mod = await import("../src/core/source-parser");
const res = mod.parseSource("/abs/path");
expect(res.type).toBe("local");
expect(res.localPath).toBe("/abs/path");
});
it("parses github url from github.com", async () => {
vi.resetModules();
const mod = await import("../src/core/source-parser");
const res = mod.parseSource("https://github.com/owner/repo.git");
expect(res.type).toBe("github");
expect(res.owner).toBe("owner");
expect(res.repo).toBe("repo");
expect(res.cloneUrl).toBe("https://github.com/owner/repo.git");
});
it("parses shorthand owner/repo", async () => {
vi.resetModules();
const mod = await import("../src/core/source-parser");
const res = mod.parseSource("alice/awesome");
expect(res.type).toBe("github");
expect(res.owner).toBe("alice");
expect(res.repo).toBe("awesome");
});
it("throws on invalid source format", async () => {
vi.resetModules();
const mod = await import("../src/core/source-parser");
expect(() => mod.parseSource("invalid")).toThrow();
});
it("getCloneUrl uses cloneUrl when provided", async () => {
vi.resetModules();
const mod = await import("../src/core/source-parser");
const url = mod.getCloneUrl({ type: "github", owner: "a", repo: "b", cloneUrl: "https://example.com/a/b.git" } as any);
expect(url).toBe("https://example.com/a/b.git");
});
it("getCloneUrl builds default for github without cloneUrl", async () => {
vi.resetModules();
const mod = await import("../src/core/source-parser");
const url = mod.getCloneUrl({ type: "github", owner: "x", repo: "y" } as any);
expect(url).toBe("https://github.com/x/y.git");
});
it("parses @skill syntax: owner/repo@skillname", async () => {
vi.resetModules();
const mod = await import("../src/core/source-parser");
const res = mod.parseSource("vercel-labs/skills@openspec");
expect(res.type).toBe("github");
expect(res.owner).toBe("vercel-labs");
expect(res.repo).toBe("skills");
expect(res.skillFilter).toBe("openspec");
});
it("parses @skill syntax with branch: owner/repo@skillname", async () => {
vi.resetModules();
const mod = await import("../src/core/source-parser");
const res = mod.parseSource("owner/repo@something");
expect(res.type).toBe("github");
expect(res.owner).toBe("owner");
expect(res.repo).toBe("repo");
expect(res.skillFilter).toBe("something");
});
it("does not confuse @ in path with @skill syntax", async () => {
vi.resetModules();
const mod = await import("../src/core/source-parser");
const res = mod.parseSource("https://github.com/owner/repo");
expect(res.type).toBe("github");
expect(res.owner).toBe("owner");
expect(res.repo).toBe("repo");
expect(res.skillFilter).toBeUndefined();
});