Add unit tests for skill capabilities, skill planner, and UV setup

- Implement tests for random ID generation, ensuring preference for crypto.randomUUID.
- Create tests for runtime context capabilities, validating the injection of enabled skill capabilities.
- Add tests for skill capability parsing, including classification and command example extraction.
- Introduce tests for the skill planner, verifying tool call planning based on user requests and attachment requirements.
- Establish tests for UV setup, ensuring proper handling of Python installation scenarios and environment checks.
This commit is contained in:
DEV_DSW
2026-04-24 17:02:59 +08:00
parent e11a2296cc
commit 4c61e93c3e
42 changed files with 12560 additions and 224 deletions

View File

@@ -0,0 +1,206 @@
// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from 'vitest';
const mocks = vi.hoisted(() => {
const sessionMessages: any[] = [];
let activeRun:
| {
runId: string;
abortController: AbortController;
}
| undefined;
return {
sessionMessages,
getActiveRun: vi.fn(() => activeRun),
providerChat: vi.fn(),
openUrlInBrowser: vi.fn(),
appendTranscriptLine: vi.fn(),
logger: {
error: vi.fn(),
warn: vi.fn(),
},
sessionStore: {
appendMessage: vi.fn((_: string, message: unknown) => {
sessionMessages.push(message);
}),
getOrCreate: vi.fn(() => ({
key: 'agent:test:main',
messages: [...sessionMessages],
updatedAt: Date.now(),
})),
setActiveRun: vi.fn((_: string, runId: string, abortController: AbortController) => {
activeRun = { runId, abortController };
}),
clearActiveRun: vi.fn(() => {
activeRun = undefined;
}),
getActiveRun: vi.fn(() => activeRun),
},
};
});
vi.mock('@electron/providers', () => ({
createProvider: vi.fn(() => ({
getCapabilities: () => ({
structuredMessages: true,
toolCalls: true,
toolResults: true,
thinking: false,
}),
chat: mocks.providerChat,
})),
}));
vi.mock('@electron/service/provider-api-service', () => ({
providerApiService: {
getDefault: () => ({ accountId: 'provider-1' }),
getAccounts: () => [
{
id: 'provider-1',
model: 'gpt-4o-mini',
vendorId: 'openai',
label: 'OpenAI',
},
],
},
}));
vi.mock('../electron/gateway/session-store', () => ({
sessionStore: mocks.sessionStore,
}));
vi.mock('@electron/service/browser-open-service', () => ({
openUrlInBrowser: mocks.openUrlInBrowser,
}));
vi.mock('@electron/utils/token-usage-writer', () => ({
appendTranscriptLine: mocks.appendTranscriptLine,
}));
vi.mock('../electron/gateway/skill-capability-registry', () => ({
getEnabledSkillCapabilities: () => [],
}));
vi.mock('@electron/service/logger', () => ({
default: mocks.logger,
}));
function createStream(chunks: Array<Record<string, unknown>>) {
return {
async *[Symbol.asyncIterator]() {
for (const chunk of chunks) {
yield chunk;
}
},
};
}
function flushAsyncTasks(): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, 0));
}
describe('chat provider tool loop', () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.sessionMessages.length = 0;
mocks.openUrlInBrowser.mockResolvedValue({
url: 'https://example.com/',
pageUrl: 'https://example.com/',
title: 'Example Domain',
});
mocks.providerChat
.mockResolvedValueOnce(createStream([
{
toolCalls: [
{
index: 0,
id: 'call_browser_1',
name: 'browser.open_url',
argumentsDelta: '{"url":"https://example.com/"}',
},
],
finishReason: 'tool_calls',
},
]))
.mockResolvedValueOnce(createStream([
{
result: '已打开页面并获取标题Example Domain',
finishReason: 'stop',
},
]));
});
it('executes provider-requested tools and resumes the model with tool_result context', async () => {
const { handleChatSend } = await import('../electron/gateway/handlers/chat');
const broadcast = vi.fn();
const result = handleChatSend(
{
sessionKey: 'agent:test:main',
message: {
role: 'user',
content: '请帮我查看一下官网首页信息',
},
},
broadcast,
);
expect(result.runId).toBeTypeOf('string');
await flushAsyncTasks();
await flushAsyncTasks();
expect(mocks.providerChat).toHaveBeenCalledTimes(2);
expect(mocks.providerChat.mock.calls[0]?.[2]).toEqual(expect.objectContaining({
tools: expect.arrayContaining([
expect.objectContaining({
name: 'browser.open_url',
}),
]),
toolChoice: 'auto',
}));
const secondCallMessages = mocks.providerChat.mock.calls[1]?.[0] as Array<Record<string, unknown>>;
expect(secondCallMessages).toEqual(expect.arrayContaining([
expect.objectContaining({
role: 'assistant',
content: expect.arrayContaining([
expect.objectContaining({
type: 'tool_use',
name: 'browser.open_url',
}),
]),
}),
expect.objectContaining({
role: 'tool_result',
}),
]));
expect(mocks.openUrlInBrowser).toHaveBeenCalledWith(
'https://example.com/',
expect.objectContaining({
signal: expect.any(AbortSignal),
}),
);
expect(broadcast).toHaveBeenCalledWith(expect.objectContaining({
type: 'tool:status',
toolName: 'browser.open_url',
status: 'running',
}));
expect(broadcast).toHaveBeenCalledWith(expect.objectContaining({
type: 'tool:status',
toolName: 'browser.open_url',
status: 'completed',
}));
expect(broadcast).toHaveBeenCalledWith(expect.objectContaining({
type: 'chat:final',
message: expect.objectContaining({
role: 'assistant',
content: '已打开页面并获取标题Example Domain',
}),
}));
});
});