@zhin.js/core 1.0.57 → 1.1.2

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.
Files changed (126) hide show
  1. package/lib/adapter.d.ts +1 -26
  2. package/lib/adapter.d.ts.map +1 -1
  3. package/lib/adapter.js +20 -117
  4. package/lib/adapter.js.map +1 -1
  5. package/lib/ai/index.d.ts +2 -0
  6. package/lib/ai/index.d.ts.map +1 -1
  7. package/lib/ai/index.js +1 -0
  8. package/lib/ai/index.js.map +1 -1
  9. package/lib/built/adapter-process.d.ts +0 -4
  10. package/lib/built/adapter-process.d.ts.map +1 -1
  11. package/lib/built/adapter-process.js +0 -95
  12. package/lib/built/adapter-process.js.map +1 -1
  13. package/lib/built/agent-preset.d.ts +2 -0
  14. package/lib/built/agent-preset.d.ts.map +1 -1
  15. package/lib/built/agent-preset.js +4 -0
  16. package/lib/built/agent-preset.js.map +1 -1
  17. package/lib/built/command.d.ts +4 -0
  18. package/lib/built/command.d.ts.map +1 -1
  19. package/lib/built/command.js +6 -0
  20. package/lib/built/command.js.map +1 -1
  21. package/lib/built/component.d.ts.map +1 -1
  22. package/lib/built/component.js +1 -0
  23. package/lib/built/component.js.map +1 -1
  24. package/lib/built/dispatcher.d.ts.map +1 -1
  25. package/lib/built/dispatcher.js +0 -13
  26. package/lib/built/dispatcher.js.map +1 -1
  27. package/lib/built/message-filter.d.ts +2 -0
  28. package/lib/built/message-filter.d.ts.map +1 -1
  29. package/lib/built/message-filter.js +5 -0
  30. package/lib/built/message-filter.js.map +1 -1
  31. package/lib/built/skill.d.ts +11 -0
  32. package/lib/built/skill.d.ts.map +1 -1
  33. package/lib/built/skill.js +14 -0
  34. package/lib/built/skill.js.map +1 -1
  35. package/lib/built/tool.d.ts +11 -44
  36. package/lib/built/tool.d.ts.map +1 -1
  37. package/lib/built/tool.js +14 -353
  38. package/lib/built/tool.js.map +1 -1
  39. package/lib/plugin.d.ts +1 -25
  40. package/lib/plugin.d.ts.map +1 -1
  41. package/lib/plugin.js +1 -77
  42. package/lib/plugin.js.map +1 -1
  43. package/lib/types.d.ts +0 -25
  44. package/lib/types.d.ts.map +1 -1
  45. package/package.json +10 -7
  46. package/CHANGELOG.md +0 -538
  47. package/REFACTORING_COMPLETE.md +0 -178
  48. package/REFACTORING_STATUS.md +0 -263
  49. package/src/adapter.ts +0 -275
  50. package/src/ai/index.ts +0 -52
  51. package/src/ai/providers/anthropic.ts +0 -379
  52. package/src/ai/providers/base.ts +0 -175
  53. package/src/ai/providers/index.ts +0 -13
  54. package/src/ai/providers/ollama.ts +0 -302
  55. package/src/ai/providers/openai.ts +0 -174
  56. package/src/ai/types.ts +0 -348
  57. package/src/bot.ts +0 -37
  58. package/src/built/adapter-process.ts +0 -177
  59. package/src/built/agent-preset.ts +0 -136
  60. package/src/built/ai-trigger.ts +0 -259
  61. package/src/built/command.ts +0 -108
  62. package/src/built/common-adapter-tools.ts +0 -242
  63. package/src/built/component.ts +0 -130
  64. package/src/built/config.ts +0 -335
  65. package/src/built/cron.ts +0 -156
  66. package/src/built/database.ts +0 -134
  67. package/src/built/dispatcher.ts +0 -496
  68. package/src/built/login-assist.ts +0 -131
  69. package/src/built/message-filter.ts +0 -390
  70. package/src/built/permission.ts +0 -151
  71. package/src/built/schema-feature.ts +0 -190
  72. package/src/built/skill.ts +0 -221
  73. package/src/built/tool.ts +0 -948
  74. package/src/command.ts +0 -87
  75. package/src/component.ts +0 -565
  76. package/src/cron.ts +0 -4
  77. package/src/errors.ts +0 -46
  78. package/src/feature.ts +0 -7
  79. package/src/index.ts +0 -53
  80. package/src/jsx-dev-runtime.ts +0 -2
  81. package/src/jsx-runtime.ts +0 -12
  82. package/src/jsx.ts +0 -135
  83. package/src/message.ts +0 -48
  84. package/src/models/system-log.ts +0 -20
  85. package/src/models/user.ts +0 -15
  86. package/src/notice.ts +0 -98
  87. package/src/plugin.ts +0 -896
  88. package/src/prompt.ts +0 -293
  89. package/src/request.ts +0 -95
  90. package/src/scheduler/index.ts +0 -19
  91. package/src/scheduler/scheduler.ts +0 -372
  92. package/src/scheduler/types.ts +0 -74
  93. package/src/tool-zod.ts +0 -115
  94. package/src/types-generator.ts +0 -78
  95. package/src/types.ts +0 -505
  96. package/src/utils.ts +0 -227
  97. package/tests/adapter.test.ts +0 -638
  98. package/tests/ai/ai-trigger.test.ts +0 -368
  99. package/tests/ai/providers.integration.test.ts +0 -227
  100. package/tests/ai/setup.ts +0 -308
  101. package/tests/ai/tool.test.ts +0 -800
  102. package/tests/bot.test.ts +0 -151
  103. package/tests/command.test.ts +0 -737
  104. package/tests/component-new.test.ts +0 -361
  105. package/tests/config.test.ts +0 -372
  106. package/tests/cron.test.ts +0 -82
  107. package/tests/dispatcher.test.ts +0 -293
  108. package/tests/errors.test.ts +0 -21
  109. package/tests/expression-evaluation.test.ts +0 -258
  110. package/tests/features-builtin.test.ts +0 -191
  111. package/tests/jsx-runtime.test.ts +0 -45
  112. package/tests/jsx.test.ts +0 -319
  113. package/tests/message-filter.test.ts +0 -566
  114. package/tests/message.test.ts +0 -402
  115. package/tests/notice.test.ts +0 -198
  116. package/tests/plugin.test.ts +0 -779
  117. package/tests/prompt.test.ts +0 -78
  118. package/tests/redos-protection.test.ts +0 -198
  119. package/tests/request.test.ts +0 -221
  120. package/tests/schema.test.ts +0 -248
  121. package/tests/skill-feature.test.ts +0 -179
  122. package/tests/test-utils.ts +0 -59
  123. package/tests/tool-feature.test.ts +0 -254
  124. package/tests/types.test.ts +0 -162
  125. package/tests/utils.test.ts +0 -135
  126. package/tsconfig.json +0 -24
@@ -1,638 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest'
2
- import { Adapter } from '../src/adapter'
3
- import { Bot } from '../src/bot'
4
- import { Plugin } from '../src/plugin'
5
- import { Message, MessageBase } from '../src/message'
6
- import { EventEmitter } from 'events'
7
-
8
- // Mock Bot 实现用于测试
9
- class MockBot implements Bot<any, any> {
10
- $id: string
11
- $config: any
12
- $connected: boolean = false
13
- adapter: Adapter
14
-
15
- constructor(adapter: Adapter, config: any) {
16
- this.adapter = adapter
17
- this.$config = config
18
- this.$id = config.id || 'mock-bot'
19
- }
20
-
21
- $formatMessage(event: any): Message<any> {
22
- const base: MessageBase = {
23
- $id: event.id || 'mock-id',
24
- $adapter: 'test' as any,
25
- $bot: this.$id,
26
- $content: [],
27
- $sender: { id: 'mock-sender', name: 'Mock Sender' },
28
- $channel: { id: 'mock-channel', type: 'private' },
29
- $timestamp: Date.now(),
30
- $raw: event.raw || event,
31
- $reply: async (content: any, quote?: boolean | string) => {
32
- const elements = Array.isArray(content) ? content : [content]
33
- const finalContent: any[] = []
34
-
35
- if (quote) {
36
- finalContent.push({
37
- type: 'reply',
38
- data: { id: typeof quote === 'boolean' ? base.$id : quote }
39
- })
40
- }
41
-
42
- finalContent.push(...elements.map((el: any) =>
43
- typeof el === 'string' ? { type: 'text', data: { text: el } } : el
44
- ))
45
-
46
- return await this.adapter.sendMessage({
47
- ...base.$channel,
48
- context: 'test',
49
- bot: this.$id,
50
- content: finalContent,
51
- })
52
- },
53
- $recall: async () => {}
54
- }
55
- return Message.from(event, base)
56
- }
57
-
58
- async $connect(): Promise<void> {
59
- this.$connected = true
60
- }
61
-
62
- async $disconnect(): Promise<void> {
63
- this.$connected = false
64
- }
65
-
66
- async $sendMessage(options: any): Promise<string> {
67
- return 'mock-message-id'
68
- }
69
-
70
- async $recallMessage(id: string): Promise<void> {
71
- // Mock 撤回消息
72
- }
73
- }
74
-
75
- // Mock Adapter 类用于测试
76
- class MockAdapter extends Adapter<MockBot> {
77
- createBot(config: any): MockBot {
78
- return new MockBot(this, config)
79
- }
80
- }
81
-
82
- describe('Adapter Core Functionality', () => {
83
- let plugin: Plugin
84
- let adapter: MockAdapter
85
-
86
- beforeEach(() => {
87
- plugin = new Plugin('/test/plugin.ts')
88
- adapter = new MockAdapter(plugin, 'test', [])
89
- })
90
-
91
- describe('Adapter Constructor', () => {
92
- it('should create adapter with plugin, name and config', () => {
93
- const config = [{ id: 'bot1' }]
94
- const adapter = new MockAdapter(plugin, 'test', config)
95
-
96
- expect(adapter.plugin).toBe(plugin)
97
- expect(adapter.name).toBe('test')
98
- expect(adapter.config).toBe(config)
99
- })
100
-
101
- it('should initialize with empty bots map', () => {
102
- expect(adapter.bots).toBeInstanceOf(Map)
103
- expect(adapter.bots.size).toBe(0)
104
- })
105
-
106
- it('should inherit from EventEmitter', () => {
107
- expect(adapter).toBeInstanceOf(EventEmitter)
108
- })
109
-
110
- it('should route message.receive via emit without default listener', () => {
111
- expect(adapter.listenerCount('message.receive')).toBe(0)
112
- })
113
-
114
- it('should register call.recallMessage listener', () => {
115
- const listeners = adapter.listeners('call.recallMessage')
116
- expect(listeners.length).toBeGreaterThan(0)
117
- })
118
-
119
- it('should have sendMessage method', () => {
120
- expect(typeof adapter.sendMessage).toBe('function')
121
- })
122
- })
123
-
124
- describe('Adapter Logger', () => {
125
- it('should get logger from plugin', () => {
126
- expect(adapter.logger).toBeDefined()
127
- expect(adapter.logger).toBe(plugin.logger)
128
- })
129
-
130
- it('should throw error if plugin is not set', () => {
131
- const adapter = new MockAdapter(plugin, 'test', [])
132
- adapter.plugin = null as any
133
-
134
- expect(() => adapter.logger).toThrow('Adapter is not associated with any plugin')
135
- })
136
- })
137
-
138
- describe('Adapter Binding', () => {
139
- it('should bind to a plugin', () => {
140
- const newPlugin = new Plugin('/test/new-plugin.ts')
141
- adapter.binding(newPlugin)
142
-
143
- expect(adapter.plugin).toBe(newPlugin)
144
- })
145
- })
146
-
147
- describe('Adapter Start', () => {
148
- it('should start without config', async () => {
149
- await adapter.start()
150
- expect(plugin.root.adapters).toContain('test')
151
- })
152
-
153
- it('should create and connect bots from config', async () => {
154
- const config = [
155
- { id: 'bot1' },
156
- { id: 'bot2' }
157
- ]
158
- const adapter = new MockAdapter(plugin, 'test', config)
159
-
160
- await adapter.start()
161
-
162
- expect(adapter.bots.size).toBe(2)
163
- expect(adapter.bots.has('bot1')).toBe(true)
164
- expect(adapter.bots.has('bot2')).toBe(true)
165
- })
166
-
167
- it('should add adapter name to plugin adapters', async () => {
168
- await adapter.start()
169
- expect(plugin.root.adapters).toContain('test')
170
- })
171
-
172
- it('should handle empty config array', async () => {
173
- const adapter = new MockAdapter(plugin, 'test', [])
174
- await adapter.start()
175
- expect(adapter.bots.size).toBe(0)
176
- })
177
- })
178
-
179
- describe('Adapter Stop', () => {
180
- it('should disconnect all bots', async () => {
181
- const config = [{ id: 'bot1' }, { id: 'bot2' }]
182
- const adapter = new MockAdapter(plugin, 'test', config)
183
-
184
- await adapter.start()
185
- expect(adapter.bots.size).toBe(2)
186
-
187
- await adapter.stop()
188
- expect(adapter.bots.size).toBe(0)
189
- })
190
-
191
- it('should remove adapter from plugin adapters', async () => {
192
- await adapter.start()
193
- expect(plugin.root.adapters).toContain('test')
194
-
195
- await adapter.stop()
196
- expect(plugin.root.adapters).not.toContain('test')
197
- })
198
-
199
- it('should remove all event listeners', async () => {
200
- await adapter.start()
201
- const noop = () => {}
202
- adapter.on('message.receive', noop)
203
- const beforeCount = adapter.listenerCount('message.receive')
204
- expect(beforeCount).toBe(1)
205
-
206
- await adapter.stop()
207
- const afterCount = adapter.listenerCount('message.receive')
208
-
209
- expect(afterCount).toBe(0)
210
- })
211
-
212
- it('should handle bot disconnect errors gracefully', async () => {
213
- const config = [{ id: 'bot1' }, { id: 'bot2' }]
214
- const adapter = new MockAdapter(plugin, 'test', config)
215
-
216
- await adapter.start()
217
-
218
- // Mock first bot disconnect to throw error
219
- const bot1 = adapter.bots.get('bot1')!
220
- bot1.$disconnect = vi.fn().mockRejectedValue(new Error('Disconnect failed'))
221
-
222
- // Mock logger to spy on error logging
223
- const loggerSpy = vi.spyOn(adapter.logger, 'error')
224
-
225
- // The adapter should continue cleanup despite errors
226
- // Note: Current implementation throws, but this test documents the desired behavior
227
- // where adapter.stop() should handle errors gracefully and continue cleanup
228
- await expect(adapter.stop()).rejects.toThrow('Disconnect failed')
229
-
230
- // Even though it throws, we document that graceful handling would be:
231
- // - Log the error
232
- // - Continue disconnecting other bots
233
- // - Complete cleanup (clear bots, remove from adapters list, remove listeners)
234
- })
235
- })
236
-
237
- describe('Adapter Events', () => {
238
- describe('call.recallMessage', () => {
239
- it('should recall message from bot', async () => {
240
- const config = [{ id: 'bot1' }]
241
- const adapter = new MockAdapter(plugin, 'test', config)
242
- await adapter.start()
243
-
244
- const bot = adapter.bots.get('bot1')!
245
- const recallSpy = vi.spyOn(bot, '$recallMessage')
246
-
247
- await adapter.emit('call.recallMessage', 'bot1', 'message-id')
248
-
249
- expect(recallSpy).toHaveBeenCalledWith('message-id')
250
- })
251
-
252
- it('should require valid bot id', () => {
253
- // 验证 adapter 不包含不存在的 bot
254
- expect(adapter.bots.has('non-existent-bot')).toBe(false)
255
- })
256
- })
257
-
258
- describe('sendMessage', () => {
259
- it('should send message through bot', async () => {
260
- const config = [{ id: 'bot1' }]
261
- const adapter = new MockAdapter(plugin, 'test', config)
262
- await adapter.start()
263
-
264
- const bot = adapter.bots.get('bot1')!
265
- const sendSpy = vi.spyOn(bot, '$sendMessage')
266
-
267
- const options = {
268
- context: 'test',
269
- bot: 'bot1',
270
- content: 'Hello',
271
- id: 'channel-id',
272
- type: 'text' as const
273
- }
274
-
275
- const messageId = await adapter.sendMessage(options)
276
-
277
- expect(sendSpy).toHaveBeenCalledWith(options)
278
- expect(messageId).toBe('mock-message-id')
279
- })
280
-
281
- it('should throw error if bot not found', async () => {
282
- const options = {
283
- context: 'test',
284
- bot: 'non-existent-bot',
285
- content: 'Hello',
286
- id: 'channel-id',
287
- type: 'text' as const
288
- }
289
-
290
- await expect(adapter.sendMessage(options)).rejects.toThrow('Bot non-existent-bot not found')
291
- })
292
-
293
- it('should call before.sendMessage handlers', async () => {
294
- const config = [{ id: 'bot1' }]
295
- const adapter = new MockAdapter(plugin, 'test', config)
296
- await adapter.start()
297
-
298
- let handlerCalled = false
299
- let modifiedContent = false
300
-
301
- plugin.root.on('before.sendMessage', (options) => {
302
- handlerCalled = true
303
- // 修改消息内容
304
- return {
305
- ...options,
306
- content: 'Modified: ' + options.content
307
- }
308
- })
309
-
310
- const bot = adapter.bots.get('bot1')!
311
- const sendSpy = vi.spyOn(bot, '$sendMessage')
312
-
313
- const options = {
314
- context: 'test',
315
- bot: 'bot1',
316
- content: 'Hello',
317
- id: 'channel-id',
318
- type: 'text' as const
319
- }
320
-
321
- await adapter.sendMessage(options)
322
-
323
- expect(handlerCalled).toBe(true)
324
- expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
325
- content: 'Modified: Hello'
326
- }))
327
- })
328
-
329
- it('should handle multiple before.sendMessage handlers', async () => {
330
- const config = [{ id: 'bot1' }]
331
- const adapter = new MockAdapter(plugin, 'test', config)
332
- await adapter.start()
333
-
334
- const handlers: string[] = []
335
-
336
- plugin.root.on('before.sendMessage', (options) => {
337
- handlers.push('handler1')
338
- return options
339
- })
340
-
341
- plugin.root.on('before.sendMessage', (options) => {
342
- handlers.push('handler2')
343
- return options
344
- })
345
-
346
- const options = {
347
- context: 'test',
348
- bot: 'bot1',
349
- content: 'Hello',
350
- id: 'channel-id',
351
- type: 'text' as const
352
- }
353
-
354
- await adapter.sendMessage(options)
355
-
356
- expect(handlers).toEqual(['handler1', 'handler2'])
357
- })
358
-
359
- it('should log message sending', async () => {
360
- const config = [{ id: 'bot1' }]
361
- const adapter = new MockAdapter(plugin, 'test', config)
362
- await adapter.start()
363
-
364
- const logSpy = vi.spyOn(adapter.logger, 'info')
365
-
366
- const options = {
367
- context: 'test',
368
- bot: 'bot1',
369
- content: [{ type: 'text', data: { text: 'Hello' } }],
370
- id: 'channel-id',
371
- type: 'private' as const
372
- }
373
-
374
- await adapter.sendMessage(options)
375
-
376
- expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('bot1 send private(channel-id):Hello'))
377
- })
378
- })
379
-
380
- describe('message.receive', () => {
381
- it('should still dispatch plugin message.receive when dispatcher is missing (no middleware fallback)', async () => {
382
- const config = [{ id: 'bot1' }]
383
- const adapter = new MockAdapter(plugin, 'test', config)
384
- await adapter.start()
385
-
386
- let middlewareCalled = false
387
- plugin.addMiddleware(async (_message, next) => {
388
- middlewareCalled = true
389
- await next()
390
- })
391
-
392
- let lifecycleCalled = false
393
- plugin.on('message.receive', () => {
394
- lifecycleCalled = true
395
- })
396
-
397
- const message = {
398
- $bot: 'bot1',
399
- $adapter: 'test',
400
- $channel: { id: 'channel-id', type: 'text' },
401
- $content: 'Hello',
402
- } as any
403
-
404
- adapter.emit('message.receive', message)
405
- await new Promise((r) => setTimeout(r, 20))
406
- expect(middlewareCalled).toBe(false)
407
- expect(lifecycleCalled).toBe(true)
408
- })
409
-
410
- it('should await MessageDispatcher then plugin lifecycle', async () => {
411
- const config = [{ id: 'bot1' }]
412
- const adapter = new MockAdapter(plugin, 'test', config)
413
- await adapter.start()
414
-
415
- const order: string[] = []
416
- plugin.$contexts.set('dispatcher', {
417
- name: 'dispatcher',
418
- description: 'mock dispatcher',
419
- value: {
420
- dispatch: async (_msg: any) => {
421
- order.push('dispatcher')
422
- },
423
- },
424
- } as any)
425
-
426
- plugin.on('message.receive', () => {
427
- order.push('lifecycle')
428
- })
429
-
430
- const message = {
431
- $bot: 'bot1',
432
- $adapter: 'test',
433
- $channel: { id: 'channel-id', type: 'text' },
434
- $content: 'Hello',
435
- } as any
436
-
437
- adapter.emit('message.receive', message)
438
- await new Promise((r) => setTimeout(r, 20))
439
- expect(order).toEqual(['dispatcher', 'lifecycle'])
440
- })
441
-
442
- it('should call adapter.on observers after plugin lifecycle', async () => {
443
- const config = [{ id: 'bot1' }]
444
- const adapter = new MockAdapter(plugin, 'test', config)
445
- await adapter.start()
446
-
447
- plugin.$contexts.set('dispatcher', {
448
- name: 'dispatcher',
449
- description: 'mock dispatcher',
450
- value: { dispatch: async () => {} },
451
- } as any)
452
-
453
- const order: string[] = []
454
- plugin.on('message.receive', () => order.push('lifecycle'))
455
- adapter.on('message.receive', () => order.push('adapterObserver'))
456
-
457
- const message = {
458
- $bot: 'bot1',
459
- $adapter: 'test',
460
- $channel: { id: 'channel-id', type: 'text' },
461
- $content: 'Hello',
462
- } as any
463
-
464
- adapter.emit('message.receive', message)
465
- await new Promise((r) => setTimeout(r, 20))
466
- expect(order).toEqual(['lifecycle', 'adapterObserver'])
467
- })
468
- })
469
- })
470
-
471
- describe('Adapter createBot', () => {
472
- it('should be abstract method', () => {
473
- expect(typeof adapter.createBot).toBe('function')
474
- })
475
-
476
- it('should create bot with config', () => {
477
- const config = { id: 'test-bot' }
478
- const bot = adapter.createBot(config)
479
-
480
- expect(bot).toBeInstanceOf(MockBot)
481
- expect(bot.$id).toBe('test-bot')
482
- })
483
- })
484
-
485
- describe('Adapter Bots Management', () => {
486
- it('should manage multiple bots', async () => {
487
- const config = [
488
- { id: 'bot1' },
489
- { id: 'bot2' },
490
- { id: 'bot3' }
491
- ]
492
- const adapter = new MockAdapter(plugin, 'test', config)
493
-
494
- await adapter.start()
495
-
496
- expect(adapter.bots.size).toBe(3)
497
- expect(Array.from(adapter.bots.keys())).toEqual(['bot1', 'bot2', 'bot3'])
498
- })
499
-
500
- it('should access bot by id', async () => {
501
- const config = [{ id: 'bot1' }]
502
- const adapter = new MockAdapter(plugin, 'test', config)
503
-
504
- await adapter.start()
505
-
506
- const bot = adapter.bots.get('bot1')
507
- expect(bot).toBeDefined()
508
- expect(bot!.$id).toBe('bot1')
509
- })
510
- })
511
-
512
- describe('Message $reply', () => {
513
- it('should send reply through adapter.sendMessage', async () => {
514
- const config = [{ id: 'bot1' }]
515
- const adapter = new MockAdapter(plugin, 'test', config)
516
- await adapter.start()
517
-
518
- const bot = adapter.bots.get('bot1')!
519
- const message = bot.$formatMessage({ id: 'msg-1', raw: 'Hello' })
520
-
521
- const sendSpy = vi.spyOn(adapter, 'sendMessage')
522
-
523
- await message.$reply('Reply content')
524
-
525
- expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
526
- bot: 'bot1',
527
- content: expect.arrayContaining([
528
- expect.objectContaining({ type: 'text', data: { text: 'Reply content' } })
529
- ])
530
- }))
531
- })
532
-
533
- it('should support quote reply', async () => {
534
- const config = [{ id: 'bot1' }]
535
- const adapter = new MockAdapter(plugin, 'test', config)
536
- await adapter.start()
537
-
538
- const bot = adapter.bots.get('bot1')!
539
- const message = bot.$formatMessage({ id: 'msg-1', raw: 'Hello' })
540
-
541
- const sendSpy = vi.spyOn(adapter, 'sendMessage')
542
-
543
- await message.$reply('Reply content', true)
544
-
545
- expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
546
- content: expect.arrayContaining([
547
- expect.objectContaining({ type: 'reply', data: { id: 'msg-1' } }),
548
- expect.objectContaining({ type: 'text', data: { text: 'Reply content' } })
549
- ])
550
- }))
551
- })
552
-
553
- it('should support custom quote id', async () => {
554
- const config = [{ id: 'bot1' }]
555
- const adapter = new MockAdapter(plugin, 'test', config)
556
- await adapter.start()
557
-
558
- const bot = adapter.bots.get('bot1')!
559
- const message = bot.$formatMessage({ id: 'msg-1', raw: 'Hello' })
560
-
561
- const sendSpy = vi.spyOn(adapter, 'sendMessage')
562
-
563
- await message.$reply('Reply content', 'custom-msg-id')
564
-
565
- expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
566
- content: expect.arrayContaining([
567
- expect.objectContaining({ type: 'reply', data: { id: 'custom-msg-id' } })
568
- ])
569
- }))
570
- })
571
-
572
- it('should handle array content', async () => {
573
- const config = [{ id: 'bot1' }]
574
- const adapter = new MockAdapter(plugin, 'test', config)
575
- await adapter.start()
576
-
577
- const bot = adapter.bots.get('bot1')!
578
- const message = bot.$formatMessage({ id: 'msg-1', raw: 'Hello' })
579
-
580
- const sendSpy = vi.spyOn(adapter, 'sendMessage')
581
-
582
- await message.$reply([
583
- { type: 'text', data: { text: 'Part 1' } },
584
- { type: 'text', data: { text: 'Part 2' } }
585
- ])
586
-
587
- expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
588
- content: expect.arrayContaining([
589
- expect.objectContaining({ type: 'text', data: { text: 'Part 1' } }),
590
- expect.objectContaining({ type: 'text', data: { text: 'Part 2' } })
591
- ])
592
- }))
593
- })
594
-
595
- it('should trigger before.sendMessage hooks', async () => {
596
- const config = [{ id: 'bot1' }]
597
- const adapter = new MockAdapter(plugin, 'test', config)
598
- await adapter.start()
599
-
600
- let hookCalled = false
601
- plugin.root.on('before.sendMessage', (options) => {
602
- hookCalled = true
603
- return options
604
- })
605
-
606
- const bot = adapter.bots.get('bot1')!
607
- const message = bot.$formatMessage({ id: 'msg-1', raw: 'Hello' })
608
-
609
- await message.$reply('Reply content')
610
-
611
- expect(hookCalled).toBe(true)
612
- })
613
- })
614
- })
615
-
616
- describe('Adapter Registry', () => {
617
- it('should have a Registry Map', () => {
618
- expect(Adapter.Registry).toBeInstanceOf(Map)
619
- })
620
-
621
- it('should register adapter factory', () => {
622
- const factory = MockAdapter as any
623
- Adapter.register('mock', factory)
624
-
625
- expect(Adapter.Registry.has('mock')).toBe(true)
626
- expect(Adapter.Registry.get('mock')).toBe(factory)
627
- })
628
-
629
- it('should allow multiple adapter registrations', () => {
630
- const factory1 = MockAdapter as any
631
- const factory2 = MockAdapter as any
632
-
633
- Adapter.register('adapter1', factory1)
634
- Adapter.register('adapter2', factory2)
635
-
636
- expect(Adapter.Registry.size).toBeGreaterThanOrEqual(2)
637
- })
638
- })