diff --git a/server/src/__tests__/company-skills-routes.test.ts b/server/src/__tests__/company-skills-routes.test.ts index 765c9b2c420..e7df5c3f77c 100644 --- a/server/src/__tests__/company-skills-routes.test.ts +++ b/server/src/__tests__/company-skills-routes.test.ts @@ -45,6 +45,7 @@ const mockCompanySkillService = vi.hoisted(() => ({ deleteSkill: vi.fn(), auditSkill: vi.fn(), getById: vi.fn(), + getByRouteRef: vi.fn(), installUpdate: vi.fn(), resetSkill: vi.fn(), listTestInputs: vi.fn(), @@ -314,6 +315,10 @@ describe("company skill mutation permissions", () => { beforeEach(() => { vi.clearAllMocks(); + mockCompanySkillService.getByRouteRef.mockImplementation(async (companyId: string, ref: string) => { + const skill = await mockCompanySkillService.getById(companyId, ref); + return skill ? { ...skill, id: ref } : skill; + }); mockHeartbeatFactory.mockReturnValue(mockHeartbeatService); mockCaptureRunIdentity.mockResolvedValue({ run: { responsibleUserId: "validated-owner" } }); mockSkillSourceService.sourceForSkill.mockResolvedValue(null); @@ -826,6 +831,32 @@ describe("company skill mutation permissions", () => { expect(mockCompanySkillService.scanProjectWorkspaces).not.toHaveBeenCalled(); }); + it("resolves skill slugs in the skillId param to the canonical skill id", async () => { + const skillId = "22222222-2222-4222-8222-222222222222"; + mockCompanySkillService.getByRouteRef.mockResolvedValue({ id: skillId, slug: "inbox-triage" }); + const app = await createApp(boardActor("board-user", "session")); + + await request(app) + .post("/api/companies/company-1/skills/inbox-triage/audit") + .send({}) + .expect(200); + + expect(mockCompanySkillService.getByRouteRef).toHaveBeenCalledWith("company-1", "inbox-triage"); + expect(mockCompanySkillService.auditSkill).toHaveBeenCalledWith("company-1", skillId); + }); + + it("returns 404 when the skillId param does not match any skill", async () => { + mockCompanySkillService.getByRouteRef.mockResolvedValue(null); + const app = await createApp(boardActor("board-user", "session")); + + await request(app) + .post("/api/companies/company-1/skills/missing-skill/audit") + .send({}) + .expect(404); + + expect(mockCompanySkillService.auditSkill).not.toHaveBeenCalled(); + }); + it("allows board users with skills:create to create, import, install, update, delete, audit, and reset company skills", async () => { const app = await createApp(boardActor("board-user", "session")); diff --git a/server/src/routes/company-skills.ts b/server/src/routes/company-skills.ts index 765dd3b8777..c0226d9434d 100644 --- a/server/src/routes/company-skills.ts +++ b/server/src/routes/company-skills.ts @@ -109,7 +109,9 @@ export function companySkillRoutes(db: Db, options: { pluginWorkerManager?: Plug try { if (req.actor.type === "none") throw unauthorized("Authentication required"); assertCompanyAccess(req, String(req.params.companyId)); - if (!await svc.getById(String(req.params.companyId), id)) throw notFound("Skill not found"); + const skill = await svc.getByRouteRef(String(req.params.companyId), id); + if (!skill) throw notFound("Skill not found"); + req.params.skillId = skill.id; next(); } catch (error) { next(error); } });