import { describe, it, expect, vi, beforeEach } from "vitest"; const mockKv = { get: vi.fn(), set: vi.fn(), list: vi.fn(), delete: vi.fn(), }; const mockSdk = { registerFunction: vi.fn(), }; const mockProvider = { name: "test", compress: vi.fn(), summarize: vi.fn(), }; vi.mock("../src/logger.js", () => ({ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, })); vi.mock("../src/functions/audit.js", () => ({ recordAudit: vi.fn(), })); import { registerSkillExtractFunctions } from "../src/functions/skill-extract.js"; describe("skill-extract", () => { let handlers: Record; beforeEach(() => { vi.clearAllMocks(); mockKv.get.mockResolvedValue(null); mockKv.set.mockResolvedValue(undefined); mockKv.list.mockResolvedValue([]); handlers = {}; mockSdk.registerFunction.mockImplementation((idOrMeta: any, handler: any) => { const id = typeof idOrMeta === "string" ? idOrMeta : idOrMeta.id; handlers[id] = handler; }); registerSkillExtractFunctions(mockSdk as any, mockKv as any, mockProvider); }); it("registers all skill functions", () => { expect(handlers["mem::skill-extract"]).toBeDefined(); expect(handlers["mem::skill-list"]).toBeDefined(); expect(handlers["mem::skill-match"]).toBeDefined(); }); it("skill-extract requires sessionId", async () => { const result = await handlers["mem::skill-extract"]({}); expect(result.success).toBe(false); }); it("skill-extract returns error for missing session", async () => { mockKv.get.mockReturnValue(Promise.resolve(null)); const result = await handlers["mem::skill-extract"]({ sessionId: "nonexistent", }); expect(result.success).toBe(false); expect(result.error).toContain("session not found"); }); it("skill-extract parses LLM response into ProceduralMemory", async () => { mockKv.get.mockImplementation((scope: string, key: string) => { if (scope === "mem:sessions") return Promise.resolve({ id: "s1", project: "test", status: "completed" }); if (scope === "mem:summaries") return Promise.resolve({ sessionId: "s1", project: "test", title: "Fix auth bug", narrative: "Debugged and fixed JWT expiration", keyDecisions: ["Switch to RS256"], filesModified: ["auth.ts"], concepts: ["authentication", "JWT"], createdAt: new Date().toISOString(), observationCount: 10, }); return Promise.resolve(null); }); mockKv.list.mockReturnValue( Promise.resolve(Array.from({ length: 5 }, (_, i) => ({ id: `obs${i}`, sessionId: "s1", timestamp: new Date().toISOString(), type: "file_edit", title: `Edit auth.ts step ${i}`, narrative: `Modified JWT validation logic step ${i}`, importance: 7, concepts: ["JWT"], files: ["auth.ts"], facts: [], }))), ); mockProvider.summarize.mockResolvedValue(` When the agent encounters JWT authentication failures or token expiration issues Fix JWT Token Expiration Check the JWT library configuration for clock skew tolerance Verify the signing algorithm matches between issuer and verifier Update token expiration to use RS256 with proper key rotation JWT auth works reliably with proper expiration handling jwt,authentication,security `); const result = await handlers["mem::skill-extract"]({ sessionId: "s1" }); expect(result.success).toBe(true); expect(result.extracted).toBe(true); expect(result.skill.name).toBe("Fix JWT Token Expiration"); expect(result.skill.steps).toHaveLength(3); expect(result.skill.triggerCondition).toContain("JWT"); expect(mockKv.set).toHaveBeenCalled(); }); it("skill-extract returns no-skill for exploratory sessions", async () => { mockKv.get.mockImplementation((scope: string) => { if (scope === "mem:sessions") return Promise.resolve({ id: "s1", project: "test", status: "completed" }); if (scope === "mem:summaries") return Promise.resolve({ sessionId: "s1", project: "test", title: "Explore codebase", narrative: "Browsed files", keyDecisions: [], filesModified: [], concepts: [], createdAt: new Date().toISOString(), observationCount: 3, }); return Promise.resolve(null); }); mockKv.list.mockReturnValue( Promise.resolve(Array.from({ length: 4 }, (_, i) => ({ id: `obs${i}`, sessionId: "s1", timestamp: new Date().toISOString(), type: "file_read", title: `Read file ${i}`, importance: 5, concepts: [], files: [], facts: [], narrative: "", }))), ); mockProvider.summarize.mockResolvedValue(""); const result = await handlers["mem::skill-extract"]({ sessionId: "s1" }); expect(result.success).toBe(true); expect(result.extracted).toBe(false); }); it("skill-list returns sorted by strength", async () => { mockKv.list.mockResolvedValue([ { id: "s1", name: "Low", strength: 0.3 }, { id: "s2", name: "High", strength: 0.9 }, ]); const result = await handlers["mem::skill-list"]({}); expect(result.skills[0].name).toBe("High"); }); it("skill-match finds relevant skills", async () => { mockKv.list.mockResolvedValue([ { id: "s1", name: "Fix JWT Auth", triggerCondition: "JWT failures", tags: ["jwt", "auth"], steps: ["check config", "update key"], strength: 0.8, }, { id: "s2", name: "Deploy Docker", triggerCondition: "container deployment", tags: ["docker", "deploy"], steps: ["build image", "push"], strength: 0.7, }, ]); const result = await handlers["mem::skill-match"]({ query: "JWT authentication token expired", }); expect(result.matches.length).toBe(1); expect(result.matches[0].skill.name).toBe("Fix JWT Auth"); }); });