@onebots/core 0.5.0 → 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.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @onebots/core
2
2
 
3
- OneBots 核心库 - 提供多平台多协议机器人应用的基础架构。
3
+ onebots 核心库 - 提供多平台多协议机器人应用的基础架构。
4
4
 
5
5
  ## 简介
6
6
 
7
- `@onebots/core` 是 OneBots 框架的核心包,提供了构建多平台多协议机器人应用所需的基础类和工具。它包含:
7
+ `@onebots/core` 是 onebots 框架的核心包,提供了构建多平台多协议机器人应用所需的基础类和工具。它包含:
8
8
 
9
9
  - **BaseApp**: 应用基类,提供核心功能
10
10
  - **Adapter**: 适配器基类,用于连接不同平台
@@ -157,7 +157,7 @@ pnpm test:run
157
157
  - [@onebots/adapter-wechat](../adapter-wechat) - 微信适配器
158
158
  - [@onebots/protocol-onebot-v11](../protocol-onebot-v11) - OneBot V11 协议
159
159
  - [@onebots/protocol-onebot-v12](../protocol-onebot-v12) - OneBot V12 协议
160
- - [@onebots/protocol-satori](../protocol-satori) - Satori 协议
160
+ - [@onebots/protocol-satori-v1](../protocol-satori) - Satori 协议
161
161
  - [@onebots/protocol-milky-v1](../protocol-milky-v1) - Milky V1 协议
162
162
 
163
163
  ## 许可证
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 配置验证系统测试
3
+ */
4
+ export {};
@@ -0,0 +1,152 @@
1
+ /**
2
+ * 配置验证系统测试
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { ConfigValidator, ValidationError } from '../config-validator.js';
6
+ describe('Config Validator', () => {
7
+ describe('Basic Validation', () => {
8
+ it('should validate required fields', () => {
9
+ const schema = {
10
+ name: { type: 'string', required: true },
11
+ age: { type: 'number', required: true },
12
+ };
13
+ expect(() => {
14
+ ConfigValidator.validate({}, schema);
15
+ }).toThrow(ValidationError);
16
+ expect(() => {
17
+ ConfigValidator.validate({ name: 'test', age: 20 }, schema);
18
+ }).not.toThrow();
19
+ });
20
+ it('should apply default values', () => {
21
+ const schema = {
22
+ port: { type: 'number', default: 8080 },
23
+ enabled: { type: 'boolean', default: true },
24
+ };
25
+ const result = ConfigValidator.validate({}, schema);
26
+ expect(result.port).toBe(8080);
27
+ expect(result.enabled).toBe(true);
28
+ });
29
+ it('should validate type', () => {
30
+ const schema = {
31
+ port: { type: 'number' },
32
+ name: { type: 'string' },
33
+ enabled: { type: 'boolean' },
34
+ };
35
+ expect(() => {
36
+ ConfigValidator.validate({ port: '8080' }, schema);
37
+ }).toThrow(ValidationError);
38
+ expect(() => {
39
+ ConfigValidator.validate({ port: 8080, name: 'test', enabled: true }, schema);
40
+ }).not.toThrow();
41
+ });
42
+ it('should validate number range', () => {
43
+ const schema = {
44
+ port: { type: 'number', min: 1, max: 65535 },
45
+ };
46
+ expect(() => {
47
+ ConfigValidator.validate({ port: 0 }, schema);
48
+ }).toThrow(ValidationError);
49
+ expect(() => {
50
+ ConfigValidator.validate({ port: 70000 }, schema);
51
+ }).toThrow(ValidationError);
52
+ expect(() => {
53
+ ConfigValidator.validate({ port: 8080 }, schema);
54
+ }).not.toThrow();
55
+ });
56
+ it('should validate string length', () => {
57
+ const schema = {
58
+ name: { type: 'string', min: 3, max: 10 },
59
+ };
60
+ expect(() => {
61
+ ConfigValidator.validate({ name: 'ab' }, schema);
62
+ }).toThrow(ValidationError);
63
+ expect(() => {
64
+ ConfigValidator.validate({ name: 'abcdefghijklmnop' }, schema);
65
+ }).toThrow(ValidationError);
66
+ expect(() => {
67
+ ConfigValidator.validate({ name: 'test' }, schema);
68
+ }).not.toThrow();
69
+ });
70
+ it('should validate enum values', () => {
71
+ const schema = {
72
+ level: { type: 'string', enum: ['low', 'medium', 'high'] },
73
+ };
74
+ expect(() => {
75
+ ConfigValidator.validate({ level: 'invalid' }, schema);
76
+ }).toThrow(ValidationError);
77
+ expect(() => {
78
+ ConfigValidator.validate({ level: 'medium' }, schema);
79
+ }).not.toThrow();
80
+ });
81
+ it('should validate with pattern', () => {
82
+ const schema = {
83
+ email: { type: 'string', pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
84
+ };
85
+ expect(() => {
86
+ ConfigValidator.validate({ email: 'invalid-email' }, schema);
87
+ }).toThrow(ValidationError);
88
+ expect(() => {
89
+ ConfigValidator.validate({ email: 'test@example.com' }, schema);
90
+ }).not.toThrow();
91
+ });
92
+ it('should use custom validator', () => {
93
+ const schema = {
94
+ password: {
95
+ type: 'string',
96
+ validator: (value) => {
97
+ if (value.length < 8) {
98
+ return 'Password must be at least 8 characters';
99
+ }
100
+ return true;
101
+ },
102
+ },
103
+ };
104
+ expect(() => {
105
+ ConfigValidator.validate({ password: 'short' }, schema);
106
+ }).toThrow(ValidationError);
107
+ expect(() => {
108
+ ConfigValidator.validate({ password: 'longpassword' }, schema);
109
+ }).not.toThrow();
110
+ });
111
+ });
112
+ describe('Nested Schema', () => {
113
+ it('should validate nested objects', () => {
114
+ const schema = {
115
+ server: {
116
+ host: { type: 'string', required: true },
117
+ port: { type: 'number', default: 8080 },
118
+ },
119
+ };
120
+ expect(() => {
121
+ ConfigValidator.validate({ server: {} }, schema);
122
+ }).toThrow(ValidationError);
123
+ const result = ConfigValidator.validate({ server: { host: 'localhost' } }, schema);
124
+ expect(result.server.host).toBe('localhost');
125
+ expect(result.server.port).toBe(8080);
126
+ });
127
+ });
128
+ describe('Transform', () => {
129
+ it('should transform values', () => {
130
+ const schema = {
131
+ port: {
132
+ type: 'number',
133
+ transform: (value) => parseInt(value, 10),
134
+ },
135
+ };
136
+ const result = ConfigValidator.validate({ port: '8080' }, schema);
137
+ expect(result.port).toBe(8080);
138
+ expect(typeof result.port).toBe('number');
139
+ });
140
+ });
141
+ describe('validateWithDefaults', () => {
142
+ it('should apply defaults to missing fields', () => {
143
+ const schema = {
144
+ port: { type: 'number', default: 8080 },
145
+ host: { type: 'string', default: 'localhost' },
146
+ };
147
+ const result = ConfigValidator.validateWithDefaults({}, schema);
148
+ expect(result.port).toBe(8080);
149
+ expect(result.host).toBe('localhost');
150
+ });
151
+ });
152
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 依赖注入容器测试
3
+ */
4
+ export {};
@@ -0,0 +1,114 @@
1
+ /**
2
+ * 依赖注入容器测试
3
+ */
4
+ import { describe, it, expect, beforeEach } from 'vitest';
5
+ import { Container } from '../di-container.js';
6
+ describe('Dependency Injection Container', () => {
7
+ let container;
8
+ beforeEach(() => {
9
+ container = new Container();
10
+ });
11
+ describe('Service Registration', () => {
12
+ it('should register and retrieve singleton service', () => {
13
+ class TestService {
14
+ value = 1;
15
+ }
16
+ container.registerSingleton('TestService', TestService);
17
+ const instance1 = container.get('TestService');
18
+ const instance2 = container.get('TestService');
19
+ expect(instance1).toBe(instance2);
20
+ expect(instance1).toBeInstanceOf(TestService);
21
+ });
22
+ it('should register and retrieve transient service', () => {
23
+ class TestService {
24
+ value = Math.random();
25
+ }
26
+ container.registerTransient('TestService', TestService);
27
+ const instance1 = container.get('TestService');
28
+ const instance2 = container.get('TestService');
29
+ expect(instance1).not.toBe(instance2);
30
+ expect(instance1).toBeInstanceOf(TestService);
31
+ });
32
+ it('should register factory function', () => {
33
+ const factory = () => ({ value: 42 });
34
+ container.registerSingleton('TestService', factory);
35
+ const instance = container.get('TestService');
36
+ expect(instance.value).toBe(42);
37
+ });
38
+ it('should check if service exists', () => {
39
+ container.registerSingleton('TestService', class {
40
+ });
41
+ expect(container.has('TestService')).toBe(true);
42
+ expect(container.has('NonExistent')).toBe(false);
43
+ });
44
+ });
45
+ describe('Dependency Resolution', () => {
46
+ it('should resolve dependencies', () => {
47
+ class Dependency {
48
+ value = 'dependency';
49
+ }
50
+ class Service {
51
+ dep;
52
+ constructor(dep) {
53
+ this.dep = dep;
54
+ }
55
+ }
56
+ container.registerSingleton('Dependency', Dependency);
57
+ container.registerSingleton('Service', Service, ['Dependency']);
58
+ const service = container.get('Service');
59
+ expect(service.dep).toBeInstanceOf(Dependency);
60
+ expect(service.dep.value).toBe('dependency');
61
+ });
62
+ it('should resolve multiple dependencies', () => {
63
+ class Dep1 {
64
+ value = 1;
65
+ }
66
+ class Dep2 {
67
+ value = 2;
68
+ }
69
+ class Service {
70
+ dep1;
71
+ dep2;
72
+ constructor(dep1, dep2) {
73
+ this.dep1 = dep1;
74
+ this.dep2 = dep2;
75
+ }
76
+ }
77
+ container.registerSingleton('Dep1', Dep1);
78
+ container.registerSingleton('Dep2', Dep2);
79
+ container.registerSingleton('Service', Service, ['Dep1', 'Dep2']);
80
+ const service = container.get('Service');
81
+ expect(service.dep1.value).toBe(1);
82
+ expect(service.dep2.value).toBe(2);
83
+ });
84
+ });
85
+ describe('Error Handling', () => {
86
+ it('should throw error for unregistered service', () => {
87
+ expect(() => {
88
+ container.get('NonExistent');
89
+ }).toThrow('Service not found: NonExistent');
90
+ });
91
+ });
92
+ describe('Container Management', () => {
93
+ it('should clear all services', () => {
94
+ container.registerSingleton('Service1', class {
95
+ });
96
+ container.registerSingleton('Service2', class {
97
+ });
98
+ expect(container.has('Service1')).toBe(true);
99
+ expect(container.has('Service2')).toBe(true);
100
+ container.clear();
101
+ expect(container.has('Service1')).toBe(false);
102
+ expect(container.has('Service2')).toBe(false);
103
+ });
104
+ it('should remove specific service', () => {
105
+ container.registerSingleton('Service1', class {
106
+ });
107
+ container.registerSingleton('Service2', class {
108
+ });
109
+ expect(container.remove('Service1')).toBe(true);
110
+ expect(container.has('Service1')).toBe(false);
111
+ expect(container.has('Service2')).toBe(true);
112
+ });
113
+ });
114
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 错误处理系统测试
3
+ */
4
+ export {};
@@ -0,0 +1,111 @@
1
+ /**
2
+ * 错误处理系统测试
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { OneBotsError, NetworkError, ConfigError, ValidationError, ErrorHandler, ErrorCategory, ErrorSeverity, } from '../errors.js';
6
+ describe('Error Handling System', () => {
7
+ describe('OneBotsError', () => {
8
+ it('should create error with default values', () => {
9
+ const error = new OneBotsError('Test error');
10
+ expect(error.message).toBe('Test error');
11
+ expect(error.category).toBe(ErrorCategory.UNKNOWN);
12
+ expect(error.severity).toBe(ErrorSeverity.MEDIUM);
13
+ expect(error.code).toBe('UNKNOWN_ERROR');
14
+ });
15
+ it('should create error with custom options', () => {
16
+ const error = new OneBotsError('Test error', {
17
+ category: ErrorCategory.NETWORK,
18
+ severity: ErrorSeverity.HIGH,
19
+ code: 'CUSTOM_ERROR',
20
+ context: { key: 'value' },
21
+ });
22
+ expect(error.category).toBe(ErrorCategory.NETWORK);
23
+ expect(error.severity).toBe(ErrorSeverity.HIGH);
24
+ expect(error.code).toBe('CUSTOM_ERROR');
25
+ expect(error.context).toEqual({ key: 'value' });
26
+ });
27
+ it('should serialize to JSON', () => {
28
+ const error = new OneBotsError('Test error', {
29
+ category: ErrorCategory.CONFIG,
30
+ context: { test: 'value' },
31
+ });
32
+ const json = error.toJSON();
33
+ expect(json.message).toBe('Test error');
34
+ expect(json.category).toBe(ErrorCategory.CONFIG);
35
+ expect(json.context).toEqual({ test: 'value' });
36
+ expect(json.timestamp).toBeDefined();
37
+ });
38
+ it('should convert to string', () => {
39
+ const error = new OneBotsError('Test error', {
40
+ category: ErrorCategory.NETWORK,
41
+ code: 'NET_ERROR',
42
+ });
43
+ expect(error.toString()).toBe('[NETWORK:NET_ERROR] Test error');
44
+ });
45
+ });
46
+ describe('Specific Error Types', () => {
47
+ it('should create NetworkError', () => {
48
+ const error = new NetworkError('Connection failed');
49
+ expect(error.category).toBe(ErrorCategory.NETWORK);
50
+ expect(error.severity).toBe(ErrorSeverity.MEDIUM);
51
+ expect(error.name).toBe('NetworkError');
52
+ });
53
+ it('should create ConfigError', () => {
54
+ const error = new ConfigError('Invalid config');
55
+ expect(error.category).toBe(ErrorCategory.CONFIG);
56
+ expect(error.severity).toBe(ErrorSeverity.HIGH);
57
+ expect(error.name).toBe('ConfigError');
58
+ });
59
+ it('should create ValidationError', () => {
60
+ const error = new ValidationError('Validation failed');
61
+ expect(error.category).toBe(ErrorCategory.VALIDATION);
62
+ expect(error.severity).toBe(ErrorSeverity.MEDIUM);
63
+ expect(error.name).toBe('ValidationError');
64
+ });
65
+ });
66
+ describe('ErrorHandler', () => {
67
+ it('should wrap Error instance', () => {
68
+ const originalError = new Error('Original error');
69
+ const wrapped = ErrorHandler.wrap(originalError);
70
+ expect(wrapped).toBeInstanceOf(OneBotsError);
71
+ expect(wrapped.message).toBe('Original error');
72
+ expect(wrapped.cause).toBe(originalError);
73
+ });
74
+ it('should wrap OneBotsError without modification', () => {
75
+ const error = new NetworkError('Network error');
76
+ const wrapped = ErrorHandler.wrap(error);
77
+ expect(wrapped).toBe(error);
78
+ });
79
+ it('should wrap unknown error types', () => {
80
+ const wrapped = ErrorHandler.wrap('String error');
81
+ expect(wrapped).toBeInstanceOf(OneBotsError);
82
+ expect(wrapped.message).toBe('String error');
83
+ });
84
+ it('should infer error category from message', () => {
85
+ const networkError = ErrorHandler.wrap(new Error('Network timeout'));
86
+ expect(networkError.category).toBe(ErrorCategory.NETWORK);
87
+ const configError = ErrorHandler.wrap(new Error('Invalid configuration'));
88
+ expect(configError.category).toBe(ErrorCategory.CONFIG);
89
+ });
90
+ it('should check if error is recoverable', () => {
91
+ const recoverable = new OneBotsError('Test', {
92
+ severity: ErrorSeverity.LOW,
93
+ });
94
+ expect(ErrorHandler.isRecoverable(recoverable)).toBe(true);
95
+ const fatal = new OneBotsError('Test', {
96
+ severity: ErrorSeverity.CRITICAL,
97
+ });
98
+ expect(ErrorHandler.isRecoverable(fatal)).toBe(false);
99
+ });
100
+ it('should check if error is fatal', () => {
101
+ const fatal = new OneBotsError('Test', {
102
+ severity: ErrorSeverity.CRITICAL,
103
+ });
104
+ expect(ErrorHandler.isFatal(fatal)).toBe(true);
105
+ const nonFatal = new OneBotsError('Test', {
106
+ severity: ErrorSeverity.LOW,
107
+ });
108
+ expect(ErrorHandler.isFatal(nonFatal)).toBe(false);
109
+ });
110
+ });
111
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 集成测试示例
3
+ * 测试多个模块协同工作
4
+ */
5
+ export {};
@@ -0,0 +1,112 @@
1
+ /**
2
+ * 集成测试示例
3
+ * 测试多个模块协同工作
4
+ */
5
+ import { describe, it, expect } from 'vitest';
6
+ import { ConfigValidator, BaseAppConfigSchema } from '../config-validator.js';
7
+ import { LifecycleManager } from '../lifecycle.js';
8
+ import { Container } from '../di-container.js';
9
+ import { ErrorHandler } from '../errors.js';
10
+ import { createLogger } from '../logger.js';
11
+ describe('Integration Tests', () => {
12
+ describe('Config Validation Integration', () => {
13
+ it('should validate and apply defaults to BaseApp config', () => {
14
+ const config = {};
15
+ const validated = ConfigValidator.validateWithDefaults(config, BaseAppConfigSchema);
16
+ expect(validated.port).toBe(6727);
17
+ expect(validated.database).toBe('onebots.db');
18
+ expect(validated.username).toBe('admin');
19
+ expect(validated.password).toBe('123456');
20
+ expect(validated.log_level).toBe('info');
21
+ });
22
+ it('should throw ValidationError on invalid config', () => {
23
+ const invalidConfig = {
24
+ port: 'invalid', // 应该是数字
25
+ };
26
+ expect(() => {
27
+ ConfigValidator.validate(invalidConfig, BaseAppConfigSchema);
28
+ }).toThrow();
29
+ });
30
+ });
31
+ describe('Error Handling Integration', () => {
32
+ it('should handle errors with proper categorization', () => {
33
+ const networkError = new Error('Network timeout');
34
+ const wrapped = ErrorHandler.wrap(networkError);
35
+ expect(wrapped.category).toBe('NETWORK');
36
+ expect(ErrorHandler.isRecoverable(wrapped)).toBe(true);
37
+ });
38
+ it('should log errors with context', () => {
39
+ const logger = createLogger('test');
40
+ const error = new Error('Test error');
41
+ const wrapped = ErrorHandler.wrap(error, { userId: '123' });
42
+ // 验证错误可以被序列化
43
+ const json = wrapped.toJSON();
44
+ expect(json.context).toEqual({ userId: '123' });
45
+ });
46
+ });
47
+ describe('Lifecycle Management Integration', () => {
48
+ it('should manage resources through lifecycle', async () => {
49
+ const lifecycle = new LifecycleManager();
50
+ let cleaned = false;
51
+ lifecycle.register('test-resource', () => {
52
+ cleaned = true;
53
+ });
54
+ lifecycle.addHook({
55
+ onInit: () => {
56
+ // 初始化逻辑
57
+ },
58
+ onStart: () => {
59
+ // 启动逻辑
60
+ },
61
+ onStop: () => {
62
+ // 停止逻辑
63
+ },
64
+ onCleanup: () => {
65
+ // 清理逻辑
66
+ },
67
+ });
68
+ await lifecycle.init();
69
+ await lifecycle.start();
70
+ await lifecycle.stop();
71
+ await lifecycle.cleanup();
72
+ expect(cleaned).toBe(true);
73
+ });
74
+ });
75
+ describe('Dependency Injection Integration', () => {
76
+ it('should resolve dependencies correctly', () => {
77
+ const container = new Container();
78
+ class Database {
79
+ connect() {
80
+ return 'connected';
81
+ }
82
+ }
83
+ class Service {
84
+ db;
85
+ constructor(db) {
86
+ this.db = db;
87
+ }
88
+ }
89
+ container.registerSingleton('Database', Database);
90
+ container.registerSingleton('Service', Service, ['Database']);
91
+ const service = container.get('Service');
92
+ expect(service.db).toBeInstanceOf(Database);
93
+ expect(service.db.connect()).toBe('connected');
94
+ });
95
+ });
96
+ describe('Logger Integration', () => {
97
+ it('should log with context', () => {
98
+ const logger = createLogger('test');
99
+ const contextLogger = logger.withContext({ userId: '123' });
100
+ // 验证上下文被正确设置
101
+ expect(contextLogger).toBeInstanceOf(Object);
102
+ });
103
+ it('should measure performance', () => {
104
+ const logger = createLogger('test');
105
+ const stopTimer = logger.start('test-operation');
106
+ // 模拟操作
107
+ const duration = 100;
108
+ logger.performance('test-operation', duration);
109
+ stopTimer();
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 生命周期管理测试
3
+ */
4
+ export {};