- Tests for GET /api/skill-registry/skills (list, includeRemoved param) - Tests for GET /api/skill-registry/skills/:id (found, 404) - Tests for GET /api/skill-registry/skills/:id/versions - Tests for POST /api/skill-registry/fetch - Tests for POST /api/skill-registry/skills/:id/install (success, 400) - Tests for POST /api/skill-registry/skills/:id/rollback (success, 400) - Tests for DELETE /api/skill-registry/skills/:id
238 lines
7.8 KiB
TypeScript
238 lines
7.8 KiB
TypeScript
import express from "express";
|
|
import request from "supertest";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { skillRegistryRoutes } from "../routes/skill-registry.js";
|
|
import { errorHandler } from "../middleware/index.js";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Mock skillRegistryService
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const mockSkillRegistryService = vi.hoisted(() => ({
|
|
list: vi.fn(),
|
|
getById: vi.fn(),
|
|
getVersions: vi.fn(),
|
|
fetchAll: vi.fn(),
|
|
install: vi.fn(),
|
|
rollback: vi.fn(),
|
|
uninstall: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../services/skill-registry.js", () => ({
|
|
skillRegistryService: () => mockSkillRegistryService,
|
|
}));
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// App factory
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function createApp() {
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use((req, _res, next) => {
|
|
(req as any).actor = {
|
|
type: "board",
|
|
userId: "local-board",
|
|
companyIds: [],
|
|
source: "local_implicit",
|
|
isInstanceAdmin: false,
|
|
};
|
|
next();
|
|
});
|
|
app.use("/api", skillRegistryRoutes());
|
|
app.use(errorHandler);
|
|
return app;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Fixtures
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const skill1 = {
|
|
id: "anthropic-official/bash",
|
|
sourceId: "anthropic-official",
|
|
name: "Bash",
|
|
description: "A bash skill",
|
|
activeVersionId: null,
|
|
removedAt: null,
|
|
createdAt: 1000,
|
|
updatedAt: 1000,
|
|
};
|
|
|
|
const version1 = {
|
|
id: "anthropic-official/bash@abc123",
|
|
skillId: "anthropic-official/bash",
|
|
sha: "abc123",
|
|
cacheDir: "/tmp/cache/bash@abc123",
|
|
fetchedAt: 1000,
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("skill registry routes", () => {
|
|
let app: ReturnType<typeof createApp>;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
app = createApp();
|
|
});
|
|
|
|
// ---- Test 1: GET /api/skill-registry/skills ----
|
|
|
|
describe("GET /api/skill-registry/skills", () => {
|
|
it("returns 200 with JSON array of skills", async () => {
|
|
mockSkillRegistryService.list.mockResolvedValue([skill1]);
|
|
|
|
const res = await request(app).get("/api/skill-registry/skills");
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual([skill1]);
|
|
expect(mockSkillRegistryService.list).toHaveBeenCalledWith({ includeRemoved: false });
|
|
});
|
|
|
|
it("passes includeRemoved=true when query param set", async () => {
|
|
mockSkillRegistryService.list.mockResolvedValue([skill1]);
|
|
|
|
const res = await request(app).get("/api/skill-registry/skills?includeRemoved=true");
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(mockSkillRegistryService.list).toHaveBeenCalledWith({ includeRemoved: true });
|
|
});
|
|
});
|
|
|
|
// ---- Test 2: GET /api/skill-registry/skills/:id ----
|
|
|
|
describe("GET /api/skill-registry/skills/:id", () => {
|
|
it("returns 200 with skill object when found", async () => {
|
|
mockSkillRegistryService.getById.mockResolvedValue(skill1);
|
|
|
|
const res = await request(app).get("/api/skill-registry/skills/anthropic-official%2Fbash");
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual(skill1);
|
|
});
|
|
|
|
it("returns 404 when skill not found", async () => {
|
|
mockSkillRegistryService.getById.mockResolvedValue(undefined);
|
|
|
|
const res = await request(app).get("/api/skill-registry/skills/unknown%2Fskill");
|
|
|
|
expect(res.status).toBe(404);
|
|
expect(res.body).toEqual({ error: "Skill not found" });
|
|
});
|
|
});
|
|
|
|
// ---- Test 3: GET /api/skill-registry/skills/:id/versions ----
|
|
|
|
describe("GET /api/skill-registry/skills/:id/versions", () => {
|
|
it("returns 200 with version array", async () => {
|
|
mockSkillRegistryService.getVersions.mockResolvedValue([version1]);
|
|
|
|
const res = await request(app).get("/api/skill-registry/skills/anthropic-official%2Fbash/versions");
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual([version1]);
|
|
});
|
|
});
|
|
|
|
// ---- Test 4: POST /api/skill-registry/fetch ----
|
|
|
|
describe("POST /api/skill-registry/fetch", () => {
|
|
it("returns 200 with { fetched, errors } object", async () => {
|
|
mockSkillRegistryService.fetchAll.mockResolvedValue({ fetched: 3, errors: [] });
|
|
|
|
const res = await request(app).post("/api/skill-registry/fetch");
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual({ fetched: 3, errors: [] });
|
|
});
|
|
});
|
|
|
|
// ---- Test 5: POST /api/skill-registry/skills/:id/install ----
|
|
|
|
describe("POST /api/skill-registry/skills/:id/install", () => {
|
|
it("returns 200 with install result when agentSkillsDir provided", async () => {
|
|
const installResult = {
|
|
type: "installed",
|
|
skillId: "anthropic-official/bash",
|
|
versionId: "anthropic-official/bash@abc123",
|
|
targetDir: "/agent/skills/bash",
|
|
};
|
|
mockSkillRegistryService.install.mockResolvedValue(installResult);
|
|
|
|
const res = await request(app)
|
|
.post("/api/skill-registry/skills/anthropic-official%2Fbash/install")
|
|
.send({ agentSkillsDir: "/agent/skills" });
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual(installResult);
|
|
expect(mockSkillRegistryService.install).toHaveBeenCalledWith(
|
|
"anthropic-official/bash",
|
|
"/agent/skills",
|
|
);
|
|
});
|
|
|
|
it("returns 400 when agentSkillsDir is missing", async () => {
|
|
const res = await request(app)
|
|
.post("/api/skill-registry/skills/anthropic-official%2Fbash/install")
|
|
.send({});
|
|
|
|
expect(res.status).toBe(400);
|
|
expect(res.body).toEqual({ error: "agentSkillsDir required" });
|
|
});
|
|
});
|
|
|
|
// ---- Test 6: POST /api/skill-registry/skills/:id/rollback ----
|
|
|
|
describe("POST /api/skill-registry/skills/:id/rollback", () => {
|
|
it("returns 200 when versionId and agentSkillsDir provided", async () => {
|
|
mockSkillRegistryService.rollback.mockResolvedValue(undefined);
|
|
|
|
const res = await request(app)
|
|
.post("/api/skill-registry/skills/anthropic-official%2Fbash/rollback")
|
|
.send({ versionId: "anthropic-official/bash@abc123", agentSkillsDir: "/agent/skills" });
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual({ ok: true });
|
|
expect(mockSkillRegistryService.rollback).toHaveBeenCalledWith(
|
|
"anthropic-official/bash",
|
|
"anthropic-official/bash@abc123",
|
|
"/agent/skills",
|
|
);
|
|
});
|
|
|
|
it("returns 400 when versionId is missing", async () => {
|
|
const res = await request(app)
|
|
.post("/api/skill-registry/skills/anthropic-official%2Fbash/rollback")
|
|
.send({ agentSkillsDir: "/agent/skills" });
|
|
|
|
expect(res.status).toBe(400);
|
|
expect(res.body).toEqual({ error: "versionId and agentSkillsDir required" });
|
|
});
|
|
|
|
it("returns 400 when agentSkillsDir is missing", async () => {
|
|
const res = await request(app)
|
|
.post("/api/skill-registry/skills/anthropic-official%2Fbash/rollback")
|
|
.send({ versionId: "anthropic-official/bash@abc123" });
|
|
|
|
expect(res.status).toBe(400);
|
|
expect(res.body).toEqual({ error: "versionId and agentSkillsDir required" });
|
|
});
|
|
});
|
|
|
|
// ---- Test 7: DELETE /api/skill-registry/skills/:id ----
|
|
|
|
describe("DELETE /api/skill-registry/skills/:id", () => {
|
|
it("returns 200 after soft-delete", async () => {
|
|
mockSkillRegistryService.uninstall.mockResolvedValue(undefined);
|
|
|
|
const res = await request(app).delete("/api/skill-registry/skills/anthropic-official%2Fbash");
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual({ ok: true });
|
|
expect(mockSkillRegistryService.uninstall).toHaveBeenCalledWith("anthropic-official/bash");
|
|
});
|
|
});
|
|
});
|