@zhin.js/ai 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/tests/setup.ts ADDED
@@ -0,0 +1,304 @@
1
+ /**
2
+ * AI 模块测试环境设置
3
+ *
4
+ * 提供:
5
+ * - Mock 对象工厂
6
+ * - 测试辅助函数
7
+ * - 公共测试配置
8
+ */
9
+ import { vi } from 'vitest';
10
+ import type { Message, MessageElement, Tool, ToolContext } from '@zhin.js/core';
11
+ import type { AIConfig, AIProviderConfig, ChatMessage } from '../src/types.js';
12
+
13
+ // ============================================================================
14
+ // Logger Mock
15
+ // ============================================================================
16
+
17
+ export const createMockLogger = () => ({
18
+ debug: vi.fn(),
19
+ info: vi.fn(),
20
+ warn: vi.fn(),
21
+ error: vi.fn(),
22
+ });
23
+
24
+ // ============================================================================
25
+ // Plugin Mock
26
+ // ============================================================================
27
+
28
+ export const createMockPlugin = (name = 'test-plugin') => ({
29
+ name,
30
+ root: {
31
+ inject: vi.fn(),
32
+ contexts: new Map(),
33
+ middleware: vi.fn(),
34
+ },
35
+ logger: createMockLogger(),
36
+ onDispose: vi.fn(),
37
+ collectAllTools: vi.fn(() => []),
38
+ provide: vi.fn(),
39
+ useContext: vi.fn(),
40
+ addMiddleware: vi.fn(),
41
+ });
42
+
43
+ // ============================================================================
44
+ // Message Mock
45
+ // ============================================================================
46
+
47
+ export interface MockMessageOptions {
48
+ content?: string;
49
+ elements?: MessageElement[];
50
+ platform?: string;
51
+ channelType?: 'group' | 'private' | 'guild';
52
+ channelId?: string;
53
+ senderId?: string;
54
+ senderPermissions?: string[];
55
+ senderRole?: string;
56
+ botId?: string;
57
+ }
58
+
59
+ export const createMockMessage = (options: MockMessageOptions = {}): Partial<Message> => {
60
+ const {
61
+ content = '测试消息',
62
+ elements,
63
+ platform = 'test',
64
+ channelType = 'group',
65
+ channelId = 'channel-1',
66
+ senderId = 'user-1',
67
+ senderPermissions = [],
68
+ senderRole,
69
+ botId = 'bot-1',
70
+ } = options;
71
+
72
+ const $content: MessageElement[] = elements || [
73
+ { type: 'text', data: { text: content } },
74
+ ];
75
+
76
+ return {
77
+ $content,
78
+ $bot: botId,
79
+ $adapter: platform,
80
+ $channel: {
81
+ type: channelType,
82
+ id: channelId,
83
+ name: 'Test Channel',
84
+ },
85
+ $sender: {
86
+ id: senderId,
87
+ name: 'Test User',
88
+ permissions: senderPermissions,
89
+ role: senderRole,
90
+ },
91
+ $reply: vi.fn().mockResolvedValue(undefined),
92
+ $quote: vi.fn().mockResolvedValue(undefined),
93
+ };
94
+ };
95
+
96
+ // ============================================================================
97
+ // Tool Mock
98
+ // ============================================================================
99
+
100
+ export interface MockToolOptions {
101
+ name: string;
102
+ description?: string;
103
+ parameters?: Record<string, { type: string; description?: string }>;
104
+ required?: string[];
105
+ platforms?: string[];
106
+ scopes?: ('group' | 'private' | 'guild')[];
107
+ permissionLevel?: 'user' | 'group_admin' | 'group_owner' | 'bot_admin' | 'owner';
108
+ tags?: string[];
109
+ executeResult?: any;
110
+ executeError?: Error;
111
+ }
112
+
113
+ export const createMockTool = (options: MockToolOptions): Tool => {
114
+ const {
115
+ name,
116
+ description = `${name} 工具`,
117
+ parameters = {},
118
+ required = [],
119
+ platforms,
120
+ scopes,
121
+ permissionLevel,
122
+ tags = [],
123
+ executeResult = 'success',
124
+ executeError,
125
+ } = options;
126
+
127
+ const properties: Record<string, any> = {};
128
+ for (const [key, value] of Object.entries(parameters)) {
129
+ properties[key] = {
130
+ type: value.type,
131
+ description: value.description,
132
+ };
133
+ }
134
+
135
+ return {
136
+ name,
137
+ description,
138
+ parameters: {
139
+ type: 'object',
140
+ properties,
141
+ required,
142
+ },
143
+ platforms,
144
+ scopes,
145
+ permissionLevel,
146
+ tags,
147
+ execute: executeError
148
+ ? vi.fn().mockRejectedValue(executeError)
149
+ : vi.fn().mockResolvedValue(executeResult),
150
+ };
151
+ };
152
+
153
+ // ============================================================================
154
+ // AI Provider Mock
155
+ // ============================================================================
156
+
157
+ export interface MockProviderOptions {
158
+ name?: string;
159
+ response?: string | AsyncGenerator<string>;
160
+ toolCalls?: Array<{ name: string; arguments: Record<string, any> }>;
161
+ error?: Error;
162
+ }
163
+
164
+ export const createMockProvider = (options: MockProviderOptions = {}) => {
165
+ const {
166
+ name = 'mock',
167
+ response = '这是 AI 的回复',
168
+ toolCalls = [],
169
+ error,
170
+ } = options;
171
+
172
+ if (error) {
173
+ return {
174
+ name,
175
+ chat: vi.fn().mockRejectedValue(error),
176
+ healthCheck: vi.fn().mockResolvedValue(false),
177
+ };
178
+ }
179
+
180
+ const generateResponse = async function* (): AsyncGenerator<{
181
+ content?: string;
182
+ toolCalls?: Array<{ name: string; arguments: Record<string, any> }>;
183
+ done: boolean;
184
+ }> {
185
+ if (toolCalls.length > 0) {
186
+ yield { toolCalls, done: false };
187
+ }
188
+
189
+ if (typeof response === 'string') {
190
+ yield { content: response, done: true };
191
+ } else {
192
+ for await (const chunk of response) {
193
+ yield { content: chunk, done: false };
194
+ }
195
+ yield { done: true };
196
+ }
197
+ };
198
+
199
+ return {
200
+ name,
201
+ chat: vi.fn().mockImplementation(() => generateResponse()),
202
+ healthCheck: vi.fn().mockResolvedValue(true),
203
+ };
204
+ };
205
+
206
+ // ============================================================================
207
+ // Context Factory
208
+ // ============================================================================
209
+
210
+ export const createToolContext = (options: Partial<ToolContext> = {}): ToolContext => ({
211
+ platform: 'test',
212
+ scope: 'group',
213
+ permissionLevel: 'user',
214
+ ...options,
215
+ });
216
+
217
+ // ============================================================================
218
+ // AI Config Factory
219
+ // ============================================================================
220
+
221
+ export const createMockAIConfig = (overrides: Partial<AIConfig> = {}): AIConfig => ({
222
+ defaultProvider: 'mock',
223
+ sessions: {
224
+ maxHistory: 10,
225
+ timeout: 300000,
226
+ },
227
+ context: {
228
+ enabled: false,
229
+ maxSize: 100,
230
+ },
231
+ trigger: {
232
+ enabled: true,
233
+ prefixes: ['#'],
234
+ ignorePrefixes: ['/'],
235
+ allowAtBot: true,
236
+ allowPrivateChat: true,
237
+ },
238
+ ...overrides,
239
+ });
240
+
241
+ // ============================================================================
242
+ // Test Utilities
243
+ // ============================================================================
244
+
245
+ /**
246
+ * 等待 Promise 解决或超时
247
+ */
248
+ export const waitFor = async <T>(
249
+ promise: Promise<T>,
250
+ timeout = 5000
251
+ ): Promise<T> => {
252
+ return Promise.race([
253
+ promise,
254
+ new Promise<T>((_, reject) =>
255
+ setTimeout(() => reject(new Error('Timeout')), timeout)
256
+ ),
257
+ ]);
258
+ };
259
+
260
+ /**
261
+ * 收集 AsyncGenerator 的所有值
262
+ */
263
+ export const collectAsyncGenerator = async <T>(
264
+ generator: AsyncGenerator<T>
265
+ ): Promise<T[]> => {
266
+ const results: T[] = [];
267
+ for await (const item of generator) {
268
+ results.push(item);
269
+ }
270
+ return results;
271
+ };
272
+
273
+ /**
274
+ * 创建延迟 Promise
275
+ */
276
+ export const delay = (ms: number): Promise<void> =>
277
+ new Promise((resolve) => setTimeout(resolve, ms));
278
+
279
+ /**
280
+ * 创建带有所有字段的 ChatMessage
281
+ */
282
+ export const createChatMessage = (
283
+ role: 'user' | 'assistant' | 'system',
284
+ content: string
285
+ ): ChatMessage => ({
286
+ role,
287
+ content,
288
+ });
289
+
290
+ /**
291
+ * 验证工具参数结构
292
+ */
293
+ export const assertToolParameters = (
294
+ tool: Tool,
295
+ expectedProperties: string[],
296
+ expectedRequired: string[] = []
297
+ ) => {
298
+ const props = Object.keys(tool.parameters.properties || {});
299
+ expect(props).toEqual(expect.arrayContaining(expectedProperties));
300
+
301
+ if (expectedRequired.length > 0) {
302
+ expect(tool.parameters.required).toEqual(expect.arrayContaining(expectedRequired));
303
+ }
304
+ };