refactor: share skills clawhub test helpers

This commit is contained in:
Vincent Koc
2026-06-01 03:32:25 +02:00
parent b6bac3cc2b
commit 9919e4601f

View File

@@ -47,6 +47,15 @@ vi.mock("../../infra/clawhub.js", () => ({
const { skillsHandlers } = await import("./skills.js");
const makeContext = () => ({ getRuntimeConfig: () => ({}) });
type SkillsHandlerName = keyof typeof skillsHandlers;
type SkillHandler = (options: {
params: unknown;
req: never;
client: never;
isWebchatConnect: () => boolean;
context: never;
respond: (success: boolean, result?: unknown, err?: unknown) => void;
}) => Promise<void> | void;
function emptySkillStatusReport() {
return {
@@ -56,6 +65,44 @@ function emptySkillStatusReport() {
};
}
async function callSkillsHandler(method: SkillsHandlerName, params: unknown) {
let ok: boolean | null = null;
let response: unknown;
let error: unknown;
const handler = skillsHandlers[method] as SkillHandler;
await handler({
params,
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: makeContext() as never,
respond: (success, result, err) => {
ok = success;
response = result;
error = err;
},
});
return { ok, response, error };
}
function expectEmptySecurityVerdicts(response: unknown): void {
expect(response).toEqual({
schema: "openclaw.skills.security-verdicts.v1",
items: [],
});
}
async function expectEmptySecurityVerdictsWithoutFetch(): Promise<void> {
const { ok, response, error } = await callSkillsHandler("skills.securityVerdicts", {});
expect(error).toBeUndefined();
expect(ok).toBe(true);
expect(fetchClawHubSkillSecurityVerdictsMock).not.toHaveBeenCalled();
expectEmptySecurityVerdicts(response);
}
describe("skills gateway handlers (clawhub)", () => {
beforeEach(() => {
loadConfigMock.mockReset();
@@ -75,30 +122,7 @@ describe("skills gateway handlers (clawhub)", () => {
});
it("returns an empty verdict batch without calling ClawHub when no skills are linked", async () => {
let ok: boolean | null = null;
let response: unknown;
let error: unknown;
await skillsHandlers["skills.securityVerdicts"]({
params: {},
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: makeContext() as never,
respond: (success, result, err) => {
ok = success;
response = result;
error = err;
},
});
expect(error).toBeUndefined();
expect(ok).toBe(true);
expect(fetchClawHubSkillSecurityVerdictsMock).not.toHaveBeenCalled();
expect(response).toEqual({
schema: "openclaw.skills.security-verdicts.v1",
items: [],
});
await expectEmptySecurityVerdictsWithoutFetch();
});
it("fetches one bulk ClawHub verdict batch for linked installed skills", async () => {
@@ -142,21 +166,7 @@ describe("skills gateway handlers (clawhub)", () => {
],
});
let ok: boolean | null = null;
let response: unknown;
let error: unknown;
await skillsHandlers["skills.securityVerdicts"]({
params: {},
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: makeContext() as never,
respond: (success, result, err) => {
ok = success;
response = result;
error = err;
},
});
const { ok, response, error } = await callSkillsHandler("skills.securityVerdicts", {});
expect(error).toBeUndefined();
expect(fetchClawHubSkillSecurityVerdictsMock).toHaveBeenCalledTimes(1);
@@ -204,29 +214,7 @@ describe("skills gateway handlers (clawhub)", () => {
],
});
let ok: boolean | null = null;
let response: unknown;
let error: unknown;
await skillsHandlers["skills.securityVerdicts"]({
params: {},
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: makeContext() as never,
respond: (success, result, err) => {
ok = success;
response = result;
error = err;
},
});
expect(error).toBeUndefined();
expect(ok).toBe(true);
expect(fetchClawHubSkillSecurityVerdictsMock).not.toHaveBeenCalled();
expect(response).toEqual({
schema: "openclaw.skills.security-verdicts.v1",
items: [],
});
await expectEmptySecurityVerdictsWithoutFetch();
});
it("loads local Skill Card content for a known installed skill", async () => {
@@ -248,20 +236,8 @@ describe("skills gateway handlers (clawhub)", () => {
});
readLocalSkillCardContentSyncMock.mockReturnValue("# AgentReceipt\n\nLocal trust card.\n");
let ok: boolean | null = null;
let response: unknown;
let error: unknown;
await skillsHandlers["skills.skillCard"]({
params: { skillKey: "agentreceipt" },
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: makeContext() as never,
respond: (success, result, err) => {
ok = success;
response = result;
error = err;
},
const { ok, response, error } = await callSkillsHandler("skills.skillCard", {
skillKey: "agentreceipt",
});
expect(error).toBeUndefined();
@@ -286,24 +262,10 @@ describe("skills gateway handlers (clawhub)", () => {
targetDir: "/tmp/workspace/skills/calendar",
});
let ok: boolean | null = null;
let response: unknown;
let error: unknown;
await skillsHandlers["skills.install"]({
params: {
source: "clawhub",
slug: "calendar",
version: "1.2.3",
},
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: makeContext() as never,
respond: (success, result, err) => {
ok = success;
response = result;
error = err;
},
const { ok, response, error } = await callSkillsHandler("skills.install", {
source: "clawhub",
slug: "calendar",
version: "1.2.3",
});
expect(installSkillFromClawHubMock).toHaveBeenCalledWith({
@@ -332,25 +294,11 @@ describe("skills gateway handlers (clawhub)", () => {
code: 0,
});
let ok: boolean | null = null;
let response: unknown;
let error: unknown;
await skillsHandlers["skills.install"]({
params: {
name: "calendar",
installId: "deps",
dangerouslyForceUnsafeInstall: true,
timeoutMs: 120_000,
},
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: makeContext() as never,
respond: (success, result, err) => {
ok = success;
response = result;
error = err;
},
const { ok, response, error } = await callSkillsHandler("skills.install", {
name: "calendar",
installId: "deps",
dangerouslyForceUnsafeInstall: true,
timeoutMs: 120_000,
});
expect(installSkillMock).toHaveBeenCalledWith({
@@ -380,23 +328,9 @@ describe("skills gateway handlers (clawhub)", () => {
},
]);
let ok: boolean | null = null;
let response: unknown;
let error: unknown;
await skillsHandlers["skills.update"]({
params: {
source: "clawhub",
slug: "calendar",
},
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: makeContext() as never,
respond: (success, result, err) => {
ok = success;
response = result;
error = err;
},
const { ok, response, error } = await callSkillsHandler("skills.update", {
source: "clawhub",
slug: "calendar",
});
expect(updateSkillsFromClawHubMock).toHaveBeenCalledWith({
@@ -425,24 +359,13 @@ describe("skills gateway handlers (clawhub)", () => {
});
it("rejects ClawHub skills.update requests without slug or all", async () => {
let ok: boolean | null = null;
let error: { code?: string; message?: string } | undefined;
await skillsHandlers["skills.update"]({
params: {
source: "clawhub",
},
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: makeContext() as never,
respond: (success, _result, err) => {
ok = success;
error = err as { code?: string; message?: string } | undefined;
},
const { ok, error } = await callSkillsHandler("skills.update", {
source: "clawhub",
});
const typedError = error as { code?: string; message?: string } | undefined;
expect(ok).toBe(false);
expect(error?.message).toContain('requires "slug" or "all"');
expect(typedError?.message).toContain('requires "slug" or "all"');
expect(updateSkillsFromClawHubMock).not.toHaveBeenCalled();
});
});