@wutiange/log-listener-plugin 2.0.2-alpha.2 → 2.0.2-alpha.3

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.
@@ -1,114 +1,236 @@
1
- import logger from '../logger';
2
- import { createClassWithErrorHandling, hasPort } from '../utils';
1
+ import { hasPort, formDataToString, sleep, typeReplacer } from '../utils';
3
2
 
4
- describe('createClassWithErrorHandling', () => {
5
- class TestClass {
6
- normalMethod(): string {
7
- return 'normal';
8
- }
3
+ describe('hasPort function', () => {
4
+ it('should return true for URLs with explicit ports', () => {
5
+ expect(hasPort('http://example.com:8080')).toBe(true);
6
+ expect(hasPort('ftp://example.com:210')).toBe(true);
7
+ });
8
+
9
+ it('should return false for URLs without explicit ports', () => {
10
+ expect(hasPort('http://example.com')).toBe(false);
11
+ expect(hasPort('https://example.com')).toBe(false);
12
+ expect(hasPort('ftp://example.com')).toBe(false);
13
+ });
14
+
15
+ it('should return false for invalid URLs', () => {
16
+ expect(hasPort('not a url')).toBe(false);
17
+ expect(hasPort('http:/example.com')).toBe(false);
18
+ expect(hasPort('example.com:8080')).toBe(false);
19
+ });
20
+
21
+ it('should return false for empty input', () => {
22
+ expect(hasPort('')).toBe(false);
23
+ });
9
24
 
10
- errorMethod(): void {
11
- throw new Error('Test error');
12
- }
25
+ it('should return false for non-string input', () => {
26
+ expect(hasPort(null as any)).toBe(false);
27
+ expect(hasPort(undefined as any)).toBe(false);
28
+ expect(hasPort(123 as any)).toBe(false);
29
+ expect(hasPort({} as any)).toBe(false);
30
+ });
13
31
 
14
- async asyncMethod(): Promise<string> {
15
- return 'async';
16
- }
32
+ it('should handle URLs with default ports correctly', () => {
33
+ expect(hasPort('http://example.com:80')).toBe(false);
34
+ expect(hasPort('https://example.com:443')).toBe(false);
35
+ });
17
36
 
18
- async asyncErrorMethod(): Promise<void> {
19
- throw new Error('Async test error');
20
- }
21
- }
37
+ it('should handle URLs with IPv6 addresses', () => {
38
+ expect(hasPort('http://[2001:db8::1]:8080')).toBe(true);
39
+ expect(hasPort('https://[2001:db8::1]')).toBe(false);
40
+ });
41
+
42
+ it('should handle URLs with userinfo', () => {
43
+ expect(hasPort('http://user:pass@example.com:8080')).toBe(true);
44
+ expect(hasPort('http://user:pass@example.com')).toBe(false);
45
+ });
46
+ });
22
47
 
23
- let consoleErrorSpy: jest.SpyInstance;
48
+ describe('formDataToString', () => {
49
+ let mockFormData: FormData;
24
50
 
25
51
  beforeEach(() => {
26
- consoleErrorSpy = jest.spyOn(logger, 'error').mockImplementation(() => {});
52
+ // 创建一个模拟的 FormData 对象
53
+ mockFormData = new FormData();
54
+ // 模拟 getParts 方法
55
+ (mockFormData as any).getParts = jest.fn();
27
56
  });
28
57
 
29
- afterEach(() => {
30
- consoleErrorSpy.mockRestore();
58
+ it('should convert form data with text fields to string', () => {
59
+ // 模拟 getParts 返回包含文本字段的数据
60
+ (mockFormData as any).getParts.mockReturnValue([
61
+ {
62
+ headers: {
63
+ 'content-disposition': 'form-data; name="field1"',
64
+ },
65
+ string: 'value1',
66
+ },
67
+ ]);
68
+
69
+ const result = formDataToString(mockFormData);
70
+
71
+ // 验证基本结构
72
+ expect(result).toMatch(/^------WebKitFormBoundary.*\r\n/);
73
+ expect(result).toMatch(/Content-Disposition: form-data; name="field1"\r\n/);
74
+ expect(result).toMatch(/Content-Length: 6\r\n/);
75
+ expect(result).toMatch(/value1\r\n/);
76
+ expect(result).toMatch(/----WebKitFormBoundary.*--\r\n$/);
31
77
  });
32
78
 
33
- test('should not interfere with normal methods', () => {
34
- const EnhancedClass = createClassWithErrorHandling(TestClass);
35
- const instance = new EnhancedClass();
36
- expect(instance.normalMethod()).toBe('normal');
79
+ it('should handle form data with content-type header', () => {
80
+ // 模拟 getParts 返回包含 content-type 的数据
81
+ (mockFormData as any).getParts.mockReturnValue([
82
+ {
83
+ headers: {
84
+ 'content-disposition': 'form-data; name="file"; filename="test.txt"',
85
+ 'content-type': 'text/plain',
86
+ },
87
+ string: 'file content',
88
+ },
89
+ ]);
90
+
91
+ const result = formDataToString(mockFormData);
92
+
93
+ expect(result).toMatch(
94
+ /Content-Disposition: form-data; name="file"; filename="test.txt"\r\n/,
95
+ );
96
+ expect(result).toMatch(/Content-Type: text\/plain\r\n/);
97
+ expect(result).toMatch(/Content-Length: 12\r\n/);
98
+ expect(result).toMatch(/file content\r\n/);
37
99
  });
38
100
 
39
- test('should catch and log errors from methods', () => {
40
- const EnhancedClass = createClassWithErrorHandling(TestClass);
41
- const instance = new EnhancedClass();
42
- expect(() => instance.errorMethod()).toThrow('Test error');
43
- expect(consoleErrorSpy).toHaveBeenCalledWith('Error in errorMethod:', expect.any(Error));
101
+ it('should handle multiple form fields', () => {
102
+ // 模拟 getParts 返回多个字段
103
+ (mockFormData as any).getParts.mockReturnValue([
104
+ {
105
+ headers: {
106
+ 'content-disposition': 'form-data; name="field1"',
107
+ },
108
+ string: 'value1',
109
+ },
110
+ {
111
+ headers: {
112
+ 'content-disposition': 'form-data; name="field2"',
113
+ },
114
+ string: 'value2',
115
+ },
116
+ ]);
117
+
118
+ const result = formDataToString(mockFormData);
119
+
120
+ expect(result).toMatch(/field1.*value1.*field2.*value2/s);
121
+ expect((result.match(/----WebKitFormBoundary/g) || []).length).toBe(3); // 开始、中间、结束
44
122
  });
45
123
 
46
- test('should not interfere with async methods that resolve', async () => {
47
- const EnhancedClass = createClassWithErrorHandling(TestClass);
48
- const instance = new EnhancedClass();
49
- await expect(instance.asyncMethod()).resolves.toBe('async');
124
+ it('should handle URI parts', () => {
125
+ // 模拟 getParts 返回包含 URI 的数据
126
+ (mockFormData as any).getParts.mockReturnValue([
127
+ {
128
+ headers: {
129
+ 'content-disposition': 'form-data; name="file"',
130
+ 'content-type': 'image/jpeg',
131
+ },
132
+ uri: 'file:///path/to/image.jpg',
133
+ },
134
+ ]);
135
+
136
+ const result = formDataToString(mockFormData);
137
+
138
+ expect(result).toMatch(/Content-Type: image\/jpeg\r\n/);
139
+ expect(result).toMatch(/file:\/\/\/path\/to\/image.jpg\r\n/);
50
140
  });
141
+ });
51
142
 
52
- test('should catch and log errors from async methods that reject', async () => {
53
- const EnhancedClass = createClassWithErrorHandling(TestClass);
54
- const instance = new EnhancedClass();
55
- await expect(instance.asyncErrorMethod()).rejects.toThrow('Async test error');
56
- expect(consoleErrorSpy).toHaveBeenCalledWith('Error in asyncErrorMethod:', expect.any(Error));
143
+ describe('sleep function', () => {
144
+ // 测试正常延迟情况
145
+ it('should resolve after specified delay', async () => {
146
+ const startTime = Date.now();
147
+ const delay = 100;
148
+
149
+ await sleep(delay);
150
+ const endTime = Date.now();
151
+ const actualDelay = endTime - startTime;
152
+
153
+ // 由于 JavaScript 定时器的不精确性,我们允许一个小的误差范围
154
+ expect(actualDelay).toBeGreaterThanOrEqual(delay);
155
+ expect(actualDelay).toBeLessThan(delay + 50); // 允许 50ms 的误差
57
156
  });
58
157
 
59
- test('should handle methods added after instantiation', () => {
60
- const EnhancedClass = createClassWithErrorHandling(TestClass);
61
- const instance = new EnhancedClass();
62
- (instance as any).dynamicMethod = function(): void {
63
- throw new Error('Dynamic method error');
64
- };
65
- expect(() => (instance as any).dynamicMethod()).toThrow('Dynamic method error');
66
- expect(consoleErrorSpy).toHaveBeenCalledWith('Error in dynamicMethod:', expect.any(Error));
158
+ // 测试超时拒绝情况
159
+ it('should reject with timeout error when isReject is true', async () => {
160
+ const delay = 100;
161
+
162
+ await expect(sleep(delay, true)).rejects.toEqual({
163
+ code: 11001,
164
+ key: '@wutiange/log-listener-plugin%%timeout',
165
+ message: 'Timeout',
166
+ });
67
167
  });
68
168
  });
69
169
 
170
+ describe('typeReplacer', () => {
171
+ // 测试 Error 类型转换
172
+ it('should convert Error to string', () => {
173
+ const error = new Error('test error');
174
+ expect(typeReplacer('error', error)).toBe('Error: test error');
175
+ });
70
176
 
71
- describe('hasPort function', () => {
72
- test('should return true for URLs with explicit ports', () => {
73
- expect(hasPort('http://example.com:8080')).toBe(true);
74
- expect(hasPort('ftp://example.com:210')).toBe(true);
177
+ // 测试 Function 类型转换
178
+ it('should convert Function to string', () => {
179
+ const fn = function test() {
180
+ return 'hello';
181
+ };
182
+ const result = typeReplacer('fn', fn);
183
+ expect(result).toContain('function test()');
75
184
  });
76
185
 
77
- test('should return false for URLs without explicit ports', () => {
78
- expect(hasPort('http://example.com')).toBe(false);
79
- expect(hasPort('https://example.com')).toBe(false);
80
- expect(hasPort('ftp://example.com')).toBe(false);
186
+ // 测试 Symbol 类型转换
187
+ it('should convert Symbol to string', () => {
188
+ const sym = Symbol('test');
189
+ expect(typeReplacer('symbol', sym)).toBe('Symbol(test)');
81
190
  });
82
191
 
83
- test('should return false for invalid URLs', () => {
84
- expect(hasPort('not a url')).toBe(false);
85
- expect(hasPort('http:/example.com')).toBe(false);
86
- expect(hasPort('example.com:8080')).toBe(false);
192
+ // 测试 BigInt 类型转换
193
+ it('should convert BigInt to string', () => {
194
+ const big = BigInt(9007199254740991);
195
+ expect(typeReplacer('bigint', big)).toBe('9007199254740991');
87
196
  });
88
197
 
89
- test('should return false for empty input', () => {
90
- expect(hasPort('')).toBe(false);
198
+ // 测试 RegExp 类型转换
199
+ it('should convert RegExp to string', () => {
200
+ const regex = /test/g;
201
+ expect(typeReplacer('regex', regex)).toBe('/test/g');
91
202
  });
92
203
 
93
- test('should return false for non-string input', () => {
94
- expect(hasPort(null as any)).toBe(false);
95
- expect(hasPort(undefined as any)).toBe(false);
96
- expect(hasPort(123 as any)).toBe(false);
97
- expect(hasPort({} as any)).toBe(false);
204
+ // 测试 Set 类型转换
205
+ it('should convert Set to array', () => {
206
+ const set = new Set([1, 2, 3]);
207
+ expect(typeReplacer('set', set)).toEqual([1, 2, 3]);
98
208
  });
99
209
 
100
- test('should handle URLs with default ports correctly', () => {
101
- expect(hasPort('http://example.com:80')).toBe(false);
102
- expect(hasPort('https://example.com:443')).toBe(false);
210
+ // 测试 Map 类型转换
211
+ it('should convert Map to object', () => {
212
+ const map = new Map([
213
+ ['key1', 'value1'],
214
+ ['key2', 'value2'],
215
+ ]);
216
+ expect(typeReplacer('map', map)).toEqual({
217
+ key1: 'value1',
218
+ key2: 'value2',
219
+ });
103
220
  });
104
221
 
105
- test('should handle URLs with IPv6 addresses', () => {
106
- expect(hasPort('http://[2001:db8::1]:8080')).toBe(true);
107
- expect(hasPort('https://[2001:db8::1]')).toBe(false);
222
+ // 测试普通值不变
223
+ it('should return primitive values as is', () => {
224
+ expect(typeReplacer('string', 'test')).toBe('test');
225
+ expect(typeReplacer('number', 42)).toBe(42);
226
+ expect(typeReplacer('boolean', true)).toBe(true);
227
+ expect(typeReplacer('null', null)).toBe(null);
228
+ expect(typeReplacer('undefined', undefined)).toBe(undefined);
108
229
  });
109
230
 
110
- test('should handle URLs with userinfo', () => {
111
- expect(hasPort('http://user:pass@example.com:8080')).toBe(true);
112
- expect(hasPort('http://user:pass@example.com')).toBe(false);
231
+ // 测试普通对象不变
232
+ it('should return objects as is', () => {
233
+ const obj = { name: 'test', age: 25 };
234
+ expect(typeReplacer('object', obj)).toEqual(obj);
113
235
  });
114
236
  });
package/src/logPlugin.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import Server from './Server';
2
- import { createClassWithErrorHandling } from './utils';
3
2
  import { httpInterceptor } from './HTTPInterceptor';
4
3
  import {
5
4
  DEFAULT_TIMEOUT,
@@ -232,7 +231,5 @@ class LogPlugin {
232
231
  this._log(Level.ERROR, Tag.DEFAULT, ...data);
233
232
  };
234
233
  }
235
- const SafeLogPlugin = createClassWithErrorHandling(LogPlugin);
236
- const logPlugin = new SafeLogPlugin();
237
- export { SafeLogPlugin };
234
+ const logPlugin = new LogPlugin();
238
235
  export default logPlugin;
package/src/utils.ts CHANGED
@@ -24,75 +24,12 @@ export function hasPort(url: string) {
24
24
  return false;
25
25
  }
26
26
 
27
- try {
28
- // 使用 URL 构造函数解析 URL
29
- const parsedUrl = new URL(url);
27
+ // 使用 URL 构造函数解析 URL
28
+ const parsedUrl = new URL(url);
30
29
 
31
- // 检查 port 属性是否为空
32
- // 注意:如果使用默认端口(如 HTTP 的 80 或 HTTPS 的 443),port 会是空字符串
33
- return parsedUrl.port !== '';
34
- } catch (error) {
35
- logger.error(error);
36
- // 如果 URL 无效,捕获错误并返回 false
37
- return false;
38
- }
39
- }
40
-
41
- type Constructor<T = {}> = new (...args: any[]) => T;
42
-
43
- export function createClassWithErrorHandling<T extends Constructor>(
44
- BaseClass: T,
45
- ): T {
46
- return new Proxy(BaseClass, {
47
- construct(target: T, args: any[]): object {
48
- const instance = new target(...args);
49
- return new Proxy(instance, {
50
- get(target: any, prop: string | symbol): any {
51
- const value = target[prop];
52
- if (typeof value === 'function') {
53
- return function (this: any, ...args: any[]): any {
54
- try {
55
- const result = value.apply(this, args);
56
- if (result instanceof Promise) {
57
- return result.catch((error: Error) => {
58
- logger.error(`Error in ${String(prop)}:`, error);
59
- throw error; // 重新抛出错误,以便调用者可以捕获它
60
- });
61
- }
62
- return result;
63
- } catch (error) {
64
- logger.error(`Error in ${String(prop)}:`, error);
65
- throw error; // 重新抛出错误,以便调用者可以捕获它
66
- }
67
- };
68
- }
69
- return value;
70
- },
71
- set(target: any, prop: string | symbol, value: any): boolean {
72
- if (typeof value === 'function') {
73
- target[prop] = function (this: any, ...args: any[]): any {
74
- try {
75
- const result = value.apply(this, args);
76
- if (result instanceof Promise) {
77
- return result.catch((error: Error) => {
78
- logger.error(`Error in ${String(prop)}:`, error);
79
- throw error;
80
- });
81
- }
82
- return result;
83
- } catch (error) {
84
- logger.error(`Error in ${String(prop)}:`, error);
85
- throw error;
86
- }
87
- };
88
- } else {
89
- target[prop] = value;
90
- }
91
- return true;
92
- },
93
- });
94
- },
95
- });
30
+ // 检查 port 属性是否为空
31
+ // 注意:如果使用默认端口(如 HTTP 的 80 或 HTTPS 的 443),port 会是空字符串
32
+ return parsedUrl.port !== '';
96
33
  }
97
34
 
98
35
  export function formDataToString(formData: FormData): string {
@@ -120,7 +57,7 @@ export function typeReplacer(key: string, val: any) {
120
57
  return val.toString();
121
58
  } else if (val instanceof Function) {
122
59
  return Function.prototype.toString.call(val);
123
- } else if (val instanceof Symbol) {
60
+ } else if (typeof val === 'symbol') {
124
61
  return val.toString();
125
62
  } else if (typeof val === 'bigint') {
126
63
  return val.toString();