chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
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<string, Function>;
|
||||
|
||||
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(`
|
||||
<skill>
|
||||
<trigger>When the agent encounters JWT authentication failures or token expiration issues</trigger>
|
||||
<title>Fix JWT Token Expiration</title>
|
||||
<steps>
|
||||
<step>Check the JWT library configuration for clock skew tolerance</step>
|
||||
<step>Verify the signing algorithm matches between issuer and verifier</step>
|
||||
<step>Update token expiration to use RS256 with proper key rotation</step>
|
||||
</steps>
|
||||
<expected_outcome>JWT auth works reliably with proper expiration handling</expected_outcome>
|
||||
<tags>jwt,authentication,security</tags>
|
||||
</skill>
|
||||
`);
|
||||
|
||||
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("<no-skill/>");
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user