@zhin.js/core 1.0.0

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 (57) hide show
  1. package/README.md +159 -0
  2. package/dist/adapter.d.ts +22 -0
  3. package/dist/adapter.d.ts.map +1 -0
  4. package/dist/adapter.js +67 -0
  5. package/dist/adapter.js.map +1 -0
  6. package/dist/app.d.ts +69 -0
  7. package/dist/app.d.ts.map +1 -0
  8. package/dist/app.js +307 -0
  9. package/dist/app.js.map +1 -0
  10. package/dist/bot.d.ts +9 -0
  11. package/dist/bot.d.ts.map +1 -0
  12. package/dist/bot.js +2 -0
  13. package/dist/bot.js.map +1 -0
  14. package/dist/config.d.ts +24 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +242 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/index.d.ts +9 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +12 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/logger.d.ts +3 -0
  23. package/dist/logger.d.ts.map +1 -0
  24. package/dist/logger.js +3 -0
  25. package/dist/logger.js.map +1 -0
  26. package/dist/plugin.d.ts +41 -0
  27. package/dist/plugin.d.ts.map +1 -0
  28. package/dist/plugin.js +95 -0
  29. package/dist/plugin.js.map +1 -0
  30. package/dist/types-generator.d.ts +6 -0
  31. package/dist/types-generator.d.ts.map +1 -0
  32. package/dist/types-generator.js +69 -0
  33. package/dist/types-generator.js.map +1 -0
  34. package/dist/types.d.ts +69 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +2 -0
  37. package/dist/types.js.map +1 -0
  38. package/package.json +29 -0
  39. package/src/adapter.ts +69 -0
  40. package/src/app.ts +339 -0
  41. package/src/bot.ts +9 -0
  42. package/src/config.ts +276 -0
  43. package/src/index.ts +14 -0
  44. package/src/logger.ts +3 -0
  45. package/src/plugin.ts +122 -0
  46. package/src/types-generator.ts +74 -0
  47. package/src/types.ts +74 -0
  48. package/tests/adapter.test.ts +187 -0
  49. package/tests/app.test.ts +207 -0
  50. package/tests/bot.test.ts +132 -0
  51. package/tests/config.test.ts +328 -0
  52. package/tests/logger.test.ts +170 -0
  53. package/tests/plugin.test.ts +226 -0
  54. package/tests/test-utils.ts +59 -0
  55. package/tests/types.test.ts +162 -0
  56. package/tsconfig.json +25 -0
  57. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,187 @@
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 { App } from '../src/app'
6
+ import type { BotConfig } from '../src/types'
7
+
8
+ describe('适配器类测试', () => {
9
+ // 创建测试用的Bot类
10
+ class TestBot implements Bot {
11
+ connected = false
12
+
13
+ constructor(public plugin: Plugin, public config: BotConfig) {}
14
+
15
+ async connect(): Promise<void> {
16
+ this.connected = true
17
+ }
18
+
19
+ async disconnect(): Promise<void> {
20
+ this.connected = false
21
+ }
22
+
23
+ async sendMessage(): Promise<void> {
24
+ if (!this.connected) throw new Error('机器人未连接')
25
+ }
26
+ }
27
+
28
+ let app: App
29
+ let plugin: Plugin
30
+ let adapter: Adapter<TestBot>
31
+
32
+ beforeEach(() => {
33
+ // 创建测试环境
34
+ app = new App({
35
+ bots: [
36
+ { name: 'test-bot-1', context: 'test-adapter' },
37
+ { name: 'test-bot-2', context: 'test-adapter' },
38
+ { name: 'other-bot', context: 'other-adapter' }
39
+ ]
40
+ })
41
+ plugin = app.createDependency('test-plugin', 'test-plugin.ts')
42
+ })
43
+
44
+ describe('构造函数工厂方法测试', () => {
45
+ it('应该使用构造函数创建适配器', () => {
46
+ adapter = new Adapter('test-adapter', TestBot)
47
+ expect(adapter.name).toBe('test-adapter')
48
+ expect(adapter.bots.size).toBe(0)
49
+ })
50
+
51
+ it('应该使用工厂函数创建适配器', () => {
52
+ const botFactory = (plugin: Plugin, config: BotConfig) => new TestBot(plugin, config)
53
+ adapter = new Adapter('test-adapter', botFactory)
54
+ expect(adapter.name).toBe('test-adapter')
55
+ expect(adapter.bots.size).toBe(0)
56
+ })
57
+ })
58
+
59
+ describe('启动和停止测试', () => {
60
+ beforeEach(() => {
61
+ adapter = new Adapter('test-adapter', TestBot)
62
+ })
63
+
64
+ it('应该正确启动适配器和机器人', async () => {
65
+ const loggerSpy = vi.spyOn(plugin.logger, 'info')
66
+ await adapter.start(plugin)
67
+
68
+ expect(adapter.bots.size).toBe(2)
69
+ expect(adapter.bots.get('test-bot-1')).toBeDefined()
70
+ expect(adapter.bots.get('test-bot-2')).toBeDefined()
71
+ expect(adapter.bots.get('test-bot-1')?.connected).toBe(true)
72
+ expect(adapter.bots.get('test-bot-2')?.connected).toBe(true)
73
+
74
+ expect(loggerSpy).toHaveBeenCalledWith('bot test-bot-1 of adapter test-adapter connected')
75
+ expect(loggerSpy).toHaveBeenCalledWith('bot test-bot-2 of adapter test-adapter connected')
76
+ expect(loggerSpy).toHaveBeenCalledWith('adapter test-adapter started')
77
+ })
78
+
79
+ it('应该正确停止适配器和机器人', async () => {
80
+ await adapter.start(plugin)
81
+ const loggerSpy = vi.spyOn(plugin.logger, 'info')
82
+ await adapter.stop(plugin)
83
+
84
+ expect(adapter.bots.size).toBe(0)
85
+ expect(loggerSpy).toHaveBeenCalledWith('bot test-bot-1 of adapter test-adapter disconnected')
86
+ expect(loggerSpy).toHaveBeenCalledWith('bot test-bot-2 of adapter test-adapter disconnected')
87
+ expect(loggerSpy).toHaveBeenCalledWith('adapter test-adapter stopped')
88
+ })
89
+
90
+ it('没有匹配的机器人配置时应该正常启动', async () => {
91
+ const emptyAdapter = new Adapter('empty-adapter', TestBot)
92
+ await emptyAdapter.start(plugin)
93
+ expect(emptyAdapter.bots.size).toBe(0)
94
+ })
95
+ })
96
+
97
+ describe('工具函数测试', () => {
98
+ it('应该正确识别Bot构造函数', () => {
99
+ // 修改测试用例,因为isBotConstructor返回undefined而不是false
100
+ expect(Adapter.isBotConstructor(TestBot)).toBe(true)
101
+ const botFactory = (plugin: Plugin, config: BotConfig) => new TestBot(plugin, config)
102
+ expect(Adapter.isBotConstructor(botFactory)).toBeFalsy()
103
+ })
104
+ })
105
+
106
+ describe('错误处理测试', () => {
107
+ it('应该处理机器人连接失败', async () => {
108
+ class FailingBot extends TestBot {
109
+ async connect(): Promise<void> {
110
+ throw new Error('连接失败')
111
+ }
112
+ }
113
+
114
+ const failingAdapter = new Adapter('failing-adapter', FailingBot)
115
+ // 修改配置以包含失败的机器人
116
+ app.updateConfig({
117
+ bots: [
118
+ { name: 'failing-bot', context: 'failing-adapter' }
119
+ ]
120
+ })
121
+ await expect(failingAdapter.start(plugin)).rejects.toThrow('连接失败')
122
+ })
123
+
124
+ it('应该处理机器人断开连接失败', async () => {
125
+ class FailingBot extends TestBot {
126
+ async disconnect(): Promise<void> {
127
+ throw new Error('断开连接失败')
128
+ }
129
+ }
130
+
131
+ const failingAdapter = new Adapter('failing-adapter', FailingBot)
132
+ // 修改配置以包含失败的机器人
133
+ app.updateConfig({
134
+ bots: [
135
+ { name: 'failing-bot', context: 'failing-adapter' }
136
+ ]
137
+ })
138
+ await failingAdapter.start(plugin)
139
+ await expect(failingAdapter.stop(plugin)).rejects.toThrow('断开连接失败')
140
+ })
141
+ })
142
+
143
+ describe('类型系统测试', () => {
144
+ it('应该支持扩展的Bot配置', async () => {
145
+ interface ExtendedBotConfig extends BotConfig {
146
+ token: string
147
+ platform: string
148
+ }
149
+
150
+ class ExtendedBot implements Bot<ExtendedBotConfig> {
151
+ connected = false
152
+
153
+ constructor(public plugin: Plugin, public config: ExtendedBotConfig) {}
154
+
155
+ async connect(): Promise<void> {
156
+ this.connected = true
157
+ }
158
+
159
+ async disconnect(): Promise<void> {
160
+ this.connected = false
161
+ }
162
+
163
+ async sendMessage(): Promise<void> {
164
+ if (!this.connected) throw new Error('机器人未连接')
165
+ }
166
+ }
167
+
168
+ const extendedApp = new App({
169
+ bots: [{
170
+ name: 'extended-bot',
171
+ context: 'extended-adapter',
172
+ token: 'test-token',
173
+ platform: 'test-platform'
174
+ }]
175
+ })
176
+
177
+ const extendedPlugin = extendedApp.createDependency('test-plugin', 'test-plugin.ts')
178
+ const extendedAdapter = new Adapter('extended-adapter', ExtendedBot)
179
+ await extendedAdapter.start(extendedPlugin)
180
+
181
+ const bot = extendedAdapter.bots.get('extended-bot')
182
+ expect(bot).toBeDefined()
183
+ expect(bot?.config.token).toBe('test-token')
184
+ expect(bot?.config.platform).toBe('test-platform')
185
+ })
186
+ })
187
+ })
@@ -0,0 +1,207 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2
+ import { App, createApp } from '../src/app'
3
+ import { AppConfig } from '../src/types'
4
+ import { Plugin } from '../src/plugin'
5
+ import { Adapter } from '../src/adapter'
6
+ import { TestLogger } from './test-utils'
7
+ import path from 'path'
8
+ import fs from 'fs'
9
+
10
+ describe('App类测试', () => {
11
+ let app: App
12
+ let testConfig: AppConfig
13
+ let testPluginDir: string
14
+
15
+ beforeEach(() => {
16
+ // 设置测试配置
17
+ testConfig = {
18
+ plugin_dirs: ['./test-plugins'],
19
+ plugins: [],
20
+ bots: [],
21
+ debug: true
22
+ }
23
+
24
+ // 创建测试插件目录
25
+ testPluginDir = path.join(process.cwd(), 'test-plugins')
26
+ if (!fs.existsSync(testPluginDir)) {
27
+ fs.mkdirSync(testPluginDir, { recursive: true })
28
+ }
29
+
30
+ // 创建App实例
31
+ app = new App(testConfig)
32
+ })
33
+
34
+ afterEach(async () => {
35
+ // 停止App
36
+ await app.stop()
37
+
38
+ // 清理测试插件目录
39
+ if (fs.existsSync(testPluginDir)) {
40
+ fs.rmSync(testPluginDir, { recursive: true, force: true })
41
+ }
42
+ })
43
+
44
+ describe('构造函数测试', () => {
45
+ it('应该使用默认配置创建App实例', () => {
46
+ const defaultApp = new App()
47
+ expect(defaultApp.getConfig()).toEqual(App.defaultConfig)
48
+ })
49
+
50
+ it('应该使用自定义配置创建App实例', () => {
51
+ const config = app.getConfig()
52
+ expect(config.plugin_dirs).toEqual(['./test-plugins'])
53
+ expect(config.debug).toBe(true)
54
+ })
55
+ })
56
+
57
+ describe('配置管理测试', () => {
58
+ it('应该正确获取配置', () => {
59
+ const config = app.getConfig()
60
+ expect(config).toEqual(testConfig)
61
+ })
62
+
63
+ it('应该正确更新配置', () => {
64
+ const newConfig: Partial<AppConfig> = {
65
+ debug: false,
66
+ plugin_dirs: ['./new-plugins']
67
+ }
68
+ app.updateConfig(newConfig)
69
+ const config = app.getConfig()
70
+ expect(config.debug).toBe(false)
71
+ expect(config.plugin_dirs).toEqual(['./new-plugins'])
72
+ })
73
+ })
74
+
75
+ describe('插件管理测试', () => {
76
+ it('应该正确创建插件依赖', () => {
77
+ const plugin = app.createDependency('test-plugin', 'test-plugin.ts')
78
+ expect(plugin).toBeInstanceOf(Plugin)
79
+ expect(plugin.name).toBe('test-plugin')
80
+ expect(plugin.filename).toBe('test-plugin.ts')
81
+ })
82
+
83
+ it('应该正确加载插件', async () => {
84
+ // 创建测试插件文件
85
+ const pluginPath = path.join(testPluginDir, 'test-plugin.ts')
86
+ fs.writeFileSync(pluginPath, `
87
+ import { Plugin } from '@zhin.js/core'
88
+ export default function(plugin: Plugin) {
89
+ plugin.logger.info('插件已加载')
90
+ }
91
+ `)
92
+
93
+ // 更新配置并加载插件
94
+ app.updateConfig({
95
+ plugins: [pluginPath]
96
+ })
97
+
98
+ // 启动App
99
+ await app.start()
100
+
101
+ // 验证插件是否被加载
102
+ const plugin = app.findChild(pluginPath)
103
+ expect(plugin).toBeDefined()
104
+ expect(plugin?.isReady).toBe(true)
105
+ })
106
+ })
107
+
108
+ describe('上下文管理测试', () => {
109
+ it('应该正确获取上下文', async () => {
110
+ // 创建测试适配器
111
+ class TestAdapter extends Adapter {
112
+ constructor() {
113
+ super('test-adapter', () => ({} as any))
114
+ }
115
+ async start() {}
116
+ async stop() {}
117
+ }
118
+ const adapter = new TestAdapter()
119
+
120
+ // 注册适配器
121
+ const plugin = app.createDependency('test-plugin', 'test-plugin.ts')
122
+ const context = {
123
+ name: adapter.name,
124
+ mounted: () => adapter,
125
+ dispose: () => {}
126
+ }
127
+ plugin.register(context)
128
+ // 等待插件挂载
129
+ await plugin.mounted()
130
+ plugin.useContext('test-adapter',()=>{
131
+ // 获取上下文
132
+ const retrievedContext = app.getContext<TestAdapter>('test-adapter')
133
+ expect(retrievedContext).toBe(adapter)
134
+ })
135
+ })
136
+
137
+ it('当上下文不存在时应该抛出错误', () => {
138
+ expect(() => app.getContext('non-existent')).toThrow("can't find Context of non-existent")
139
+ })
140
+ })
141
+
142
+ describe('消息处理测试', () => {
143
+ it('应该正确处理发送消息前的钩子', async () => {
144
+ const options = {
145
+ id: '123',
146
+ type: 'group' as const,
147
+ context: 'test-adapter',
148
+ bot: 'test-bot',
149
+ content: '测试消息'
150
+ }
151
+
152
+ const plugin = app.createDependency('test-plugin', 'test-plugin.ts')
153
+ const handler = vi.fn((opts) => ({
154
+ ...opts,
155
+ content: '修改后的消息'
156
+ }))
157
+ plugin.on('before-message.send', handler)
158
+
159
+ // 触发发送消息事件
160
+ plugin.emit('before-message.send', options)
161
+
162
+ expect(handler).toHaveBeenCalledWith(options)
163
+ })
164
+ })
165
+
166
+ describe('日志系统测试', () => {
167
+ it('应该正确创建日志记录器', () => {
168
+ const logger = app.getLogger('测试', '日志')
169
+ expect(logger).toBeDefined()
170
+ })
171
+ })
172
+
173
+ describe('生命周期测试', () => {
174
+ it('应该正确启动和停止', async () => {
175
+ const startSpy = vi.spyOn(app, 'start')
176
+ const stopSpy = vi.spyOn(app, 'stop')
177
+
178
+ await app.start()
179
+ expect(startSpy).toHaveBeenCalled()
180
+ expect(app.isReady).toBe(true)
181
+
182
+ await app.stop()
183
+ expect(stopSpy).toHaveBeenCalled()
184
+ expect(app.isDispose).toBe(true)
185
+ })
186
+ })
187
+ })
188
+
189
+ describe('工厂函数测试', () => {
190
+ it('应该正确创建App实例', async () => {
191
+ const app = await createApp({
192
+ debug: true,
193
+ plugin_dirs: ['./test-plugins']
194
+ })
195
+ expect(app).toBeInstanceOf(App)
196
+ expect(app.getConfig().debug).toBe(true)
197
+ expect(app.getConfig().plugin_dirs).toEqual(['./test-plugins'])
198
+ await app.stop()
199
+ })
200
+
201
+ it('应该使用默认配置创建App实例', async () => {
202
+ const app = await createApp()
203
+ expect(app).toBeInstanceOf(App)
204
+ expect(app.getConfig()).toEqual(App.defaultConfig)
205
+ await app.stop()
206
+ })
207
+ })
@@ -0,0 +1,132 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import type { Bot } from '../src/bot'
3
+ import type { BotConfig, SendOptions } from '../src/types'
4
+
5
+ describe('Bot接口测试', () => {
6
+ // 创建一个测试用的Bot实现类
7
+ class TestBot implements Bot {
8
+ connected = false
9
+
10
+ constructor(public config: BotConfig) {}
11
+
12
+ async connect(): Promise<void> {
13
+ this.connected = true
14
+ }
15
+
16
+ async disconnect(): Promise<void> {
17
+ this.connected = false
18
+ }
19
+
20
+ async sendMessage(options: SendOptions): Promise<void> {
21
+ if (!this.connected) {
22
+ throw new Error('机器人未连接')
23
+ }
24
+ // 模拟发送消息
25
+ }
26
+ }
27
+
28
+ let bot: TestBot
29
+ let testConfig: BotConfig
30
+
31
+ beforeEach(() => {
32
+ testConfig = {
33
+ name: '测试机器人',
34
+ context: 'test'
35
+ }
36
+ bot = new TestBot(testConfig)
37
+ })
38
+
39
+ describe('基本属性测试', () => {
40
+ it('应该正确设置配置', () => {
41
+ expect(bot.config).toEqual(testConfig)
42
+ })
43
+
44
+ it('应该正确初始化连接状态', () => {
45
+ expect(bot.connected).toBe(false)
46
+ })
47
+ })
48
+
49
+ describe('连接管理测试', () => {
50
+ it('应该正确处理连接', async () => {
51
+ await bot.connect()
52
+ expect(bot.connected).toBe(true)
53
+ })
54
+
55
+ it('应该正确处理断开连接', async () => {
56
+ await bot.connect()
57
+ await bot.disconnect()
58
+ expect(bot.connected).toBe(false)
59
+ })
60
+ })
61
+
62
+ describe('消息发送测试', () => {
63
+ it('未连接时应该抛出错误', async () => {
64
+ const options: SendOptions = {
65
+ id: '123',
66
+ type: 'group',
67
+ context: 'test',
68
+ bot: 'test-bot',
69
+ content: '测试消息'
70
+ }
71
+
72
+ await expect(bot.sendMessage(options)).rejects.toThrow('机器人未连接')
73
+ })
74
+
75
+ it('连接后应该正确发送消息', async () => {
76
+ const options: SendOptions = {
77
+ id: '123',
78
+ type: 'group',
79
+ context: 'test',
80
+ bot: 'test-bot',
81
+ content: '测试消息'
82
+ }
83
+
84
+ const sendSpy = vi.spyOn(bot, 'sendMessage')
85
+ await bot.connect()
86
+ await bot.sendMessage(options)
87
+ expect(sendSpy).toHaveBeenCalledWith(options)
88
+ })
89
+ })
90
+
91
+ describe('自定义配置测试', () => {
92
+ it('应该支持扩展的配置类型', () => {
93
+ interface ExtendedConfig extends BotConfig {
94
+ token: string
95
+ platform: string
96
+ }
97
+
98
+ class ExtendedBot implements Bot<ExtendedConfig> {
99
+ connected = false
100
+
101
+ constructor(public config: ExtendedConfig) {}
102
+
103
+ async connect(): Promise<void> {
104
+ this.connected = true
105
+ }
106
+
107
+ async disconnect(): Promise<void> {
108
+ this.connected = false
109
+ }
110
+
111
+ async sendMessage(options: SendOptions): Promise<void> {
112
+ if (!this.connected) {
113
+ throw new Error('机器人未连接')
114
+ }
115
+ // 模拟发送消息
116
+ }
117
+ }
118
+
119
+ const extendedConfig: ExtendedConfig = {
120
+ name: '扩展机器人',
121
+ context: 'extended',
122
+ token: 'test-token',
123
+ platform: 'test-platform'
124
+ }
125
+
126
+ const extendedBot = new ExtendedBot(extendedConfig)
127
+ expect(extendedBot.config).toEqual(extendedConfig)
128
+ expect(extendedBot.config.token).toBe('test-token')
129
+ expect(extendedBot.config.platform).toBe('test-platform')
130
+ })
131
+ })
132
+ })