test(skills): cover PAT import, skill auth, and scan-projects routes

- importFromSource is invoked with the PAT when one is supplied in the body
- PATCH /skills/:id/auth updates and clears tokens, with matching activity log entries
- POST /skills/scan-projects is reachable to agents that hold canCreateAgents
- Update existing import-permission assertion to include the new authToken arg
This commit is contained in:
2026-05-01 07:58:51 -04:00
parent 7b12d907cc
commit 9e30b72b27
@@ -14,6 +14,8 @@ const mockAccessService = vi.hoisted(() => ({
const mockCompanySkillService = vi.hoisted(() => ({
importFromSource: vi.fn(),
deleteSkill: vi.fn(),
updateSkillAuth: vi.fn(),
scanProjectWorkspaces: vi.fn(),
}));
const mockLogActivity = vi.hoisted(() => vi.fn());
@@ -97,6 +99,15 @@ describe("company skill mutation permissions", () => {
slug: "find-skills",
name: "Find Skills",
});
mockCompanySkillService.scanProjectWorkspaces.mockResolvedValue({
scannedProjects: 1,
scannedWorkspaces: 2,
discovered: [],
imported: [],
updated: [],
conflicts: [],
warnings: [],
});
mockLogActivity.mockResolvedValue(undefined);
mockAccessService.canUser.mockResolvedValue(true);
mockAccessService.hasPermission.mockResolvedValue(false);
@@ -294,9 +305,120 @@ describe("company skill mutation permissions", () => {
expect(mockCompanySkillService.importFromSource).toHaveBeenCalledWith(
"company-1",
"https://github.com/vercel-labs/agent-browser",
undefined,
);
});
it("passes a PAT through skill import requests", async () => {
const res = await request(await createApp({
type: "board",
userId: "local-board",
companyIds: ["company-1"],
source: "local_implicit",
isInstanceAdmin: false,
}))
.post("/api/companies/company-1/skills/import")
.send({
source: "https://github.com/vercel-labs/agent-browser",
authToken: "ghp_private_token",
});
expect(res.status, JSON.stringify(res.body)).toBe(201);
expect(mockCompanySkillService.importFromSource).toHaveBeenCalledWith(
"company-1",
"https://github.com/vercel-labs/agent-browser",
"ghp_private_token",
);
});
it("updates a skill auth token", async () => {
mockCompanySkillService.updateSkillAuth.mockResolvedValue({
id: "skill-1",
slug: "find-skills",
});
const res = await request(await createApp({
type: "board",
userId: "local-board",
companyIds: ["company-1"],
source: "local_implicit",
isInstanceAdmin: false,
}))
.patch("/api/companies/company-1/skills/skill-1/auth")
.send({ authToken: "ghp_private_token" });
expect(res.status, JSON.stringify(res.body)).toBe(200);
expect(mockCompanySkillService.updateSkillAuth).toHaveBeenCalledWith(
"company-1",
"skill-1",
"ghp_private_token",
);
expect(mockLogActivity).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
companyId: "company-1",
action: "company.skill_auth_updated",
entityType: "company_skill",
entityId: "skill-1",
details: { slug: "find-skills" },
}),
);
});
it("clears a skill auth token", async () => {
mockCompanySkillService.updateSkillAuth.mockResolvedValue({
id: "skill-1",
slug: "find-skills",
});
const res = await request(await createApp({
type: "board",
userId: "local-board",
companyIds: ["company-1"],
source: "local_implicit",
isInstanceAdmin: false,
}))
.patch("/api/companies/company-1/skills/skill-1/auth")
.send({ authToken: null });
expect(res.status, JSON.stringify(res.body)).toBe(200);
expect(mockCompanySkillService.updateSkillAuth).toHaveBeenCalledWith(
"company-1",
"skill-1",
null,
);
expect(mockLogActivity).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
companyId: "company-1",
action: "company.skill_auth_removed",
entityType: "company_skill",
entityId: "skill-1",
details: { slug: "find-skills" },
}),
);
});
it("allows agents with canCreateAgents to scan project workspaces", async () => {
mockAgentService.getById.mockResolvedValue({
id: "agent-1",
companyId: "company-1",
permissions: { canCreateAgents: true },
});
const res = await request(await createApp({
type: "agent",
agentId: "agent-1",
companyId: "company-1",
runId: "run-1",
}))
.post("/api/companies/company-1/skills/scan-projects")
.send({});
expect(res.status, JSON.stringify(res.body)).toBe(200);
expect(mockCompanySkillService.scanProjectWorkspaces).toHaveBeenCalledWith("company-1", {});
});
it("returns a blocking error when attempting to delete a skill still used by agents", async () => {
const { unprocessable } = await import("../errors.js");
mockCompanySkillService.deleteSkill.mockImplementationOnce(async () => {