import { beforeEach, describe, expect, it, vi } from 'vitest';
const { createUserSkill } = vi.hoisted(() => ({ createUserSkill: vi.fn() }));
vi.mock('@/lib/server/agent-runtime/user-skills', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/lib/server/agent-runtime/user-skills')>();
return { ...actual, createUserSkill };
});
import { buildCreateSkillTool } from '@/lib/server/agent-runtime/create-skill';
import { UserSkillError } from '@/lib/server/agent-runtime/user-skills';
const params = {
name: 'my-review-method',
title: 'Review method',
description: 'Turn a discussion into a reusable review procedure',
instructions: 'First restate the goal, then list the evidence, then give the next step.',
};
beforeEach(() => {
createUserSkill.mockReset();
});
describe('create_skill tool', () => {
it('binds ownership outside model parameters and returns durable details', async () => {
createUserSkill.mockResolvedValue({
id: 'usk_abc',
ownerId: 'user:u1',
version: 1,
createdAt: new Date(),
updatedAt: new Date(),
content: params.instructions,
...params,
});
const tool = buildCreateSkillTool('user:u1');
expect(Object.keys((tool.parameters as { properties: object }).properties)).not.toContain(
'ownerId',
);
const result = (await tool.execute('call-1', params)) as {
details: Record<string, unknown>;
isError?: boolean;
};
expect(createUserSkill).toHaveBeenCalledWith('user:u1', {
name: params.name,
title: params.title,
description: params.description,
content: params.instructions,
});
expect(result.isError).not.toBe(true);
expect(result.details).toEqual({
skillId: 'usk_abc',
name: params.name,
title: params.title,
description: params.description,
content: params.instructions,
source: 'user',
});
});
it('returns readable duplicate and quota errors without throwing', async () => {
const tool = buildCreateSkillTool('user:u1');
for (const [code, message] of [
['duplicate', 'will not be overwritten'],
['quota', 'at most 50'],
] as const) {
createUserSkill.mockRejectedValueOnce(new UserSkillError(message, code));
const result = (await tool.execute(`call-${code}`, params)) as {
details: Record<string, unknown>;
isError?: boolean;
content: { text: string }[];
};
expect(result.isError).toBe(true);
expect(result.details.error).toBe(code);
expect(result.content[0].text).toBe(message);
}
});
});