@dangao/bun-server 1.7.1 → 1.8.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/README.md +129 -21
- package/dist/di/decorators.d.ts +37 -0
- package/dist/di/decorators.d.ts.map +1 -1
- package/dist/di/index.d.ts +1 -1
- package/dist/di/index.d.ts.map +1 -1
- package/dist/di/module-registry.d.ts +17 -0
- package/dist/di/module-registry.d.ts.map +1 -1
- package/dist/events/decorators.d.ts +52 -0
- package/dist/events/decorators.d.ts.map +1 -0
- package/dist/events/event-module.d.ts +97 -0
- package/dist/events/event-module.d.ts.map +1 -0
- package/dist/events/index.d.ts +5 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/service.d.ts +76 -0
- package/dist/events/service.d.ts.map +1 -0
- package/dist/events/types.d.ts +184 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1511 -11
- package/dist/security/filter.d.ts +23 -0
- package/dist/security/filter.d.ts.map +1 -1
- package/dist/security/guards/builtin/auth-guard.d.ts +44 -0
- package/dist/security/guards/builtin/auth-guard.d.ts.map +1 -0
- package/dist/security/guards/builtin/index.d.ts +3 -0
- package/dist/security/guards/builtin/index.d.ts.map +1 -0
- package/dist/security/guards/builtin/roles-guard.d.ts +66 -0
- package/dist/security/guards/builtin/roles-guard.d.ts.map +1 -0
- package/dist/security/guards/decorators.d.ts +50 -0
- package/dist/security/guards/decorators.d.ts.map +1 -0
- package/dist/security/guards/execution-context.d.ts +56 -0
- package/dist/security/guards/execution-context.d.ts.map +1 -0
- package/dist/security/guards/guard-registry.d.ts +67 -0
- package/dist/security/guards/guard-registry.d.ts.map +1 -0
- package/dist/security/guards/index.d.ts +7 -0
- package/dist/security/guards/index.d.ts.map +1 -0
- package/dist/security/guards/reflector.d.ts +57 -0
- package/dist/security/guards/reflector.d.ts.map +1 -0
- package/dist/security/guards/types.d.ts +126 -0
- package/dist/security/guards/types.d.ts.map +1 -0
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/security-module.d.ts +20 -0
- package/dist/security/security-module.d.ts.map +1 -1
- package/dist/validation/class-validator.d.ts +108 -0
- package/dist/validation/class-validator.d.ts.map +1 -0
- package/dist/validation/custom-validator.d.ts +130 -0
- package/dist/validation/custom-validator.d.ts.map +1 -0
- package/dist/validation/errors.d.ts +22 -2
- package/dist/validation/errors.d.ts.map +1 -1
- package/dist/validation/index.d.ts +7 -1
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/rules/array.d.ts +33 -0
- package/dist/validation/rules/array.d.ts.map +1 -0
- package/dist/validation/rules/common.d.ts +90 -0
- package/dist/validation/rules/common.d.ts.map +1 -0
- package/dist/validation/rules/conditional.d.ts +30 -0
- package/dist/validation/rules/conditional.d.ts.map +1 -0
- package/dist/validation/rules/index.d.ts +5 -0
- package/dist/validation/rules/index.d.ts.map +1 -0
- package/dist/validation/rules/object.d.ts +30 -0
- package/dist/validation/rules/object.d.ts.map +1 -0
- package/dist/validation/types.d.ts +52 -1
- package/dist/validation/types.d.ts.map +1 -1
- package/docs/events.md +494 -0
- package/docs/guards.md +376 -0
- package/docs/guide.md +309 -1
- package/docs/request-lifecycle.md +444 -0
- package/docs/validation.md +407 -0
- package/docs/zh/events.md +494 -0
- package/docs/zh/guards.md +376 -0
- package/docs/zh/guide.md +309 -1
- package/docs/zh/request-lifecycle.md +444 -0
- package/docs/zh/validation.md +407 -0
- package/package.json +1 -1
- package/src/di/decorators.ts +46 -0
- package/src/di/index.ts +10 -1
- package/src/di/module-registry.ts +39 -0
- package/src/events/decorators.ts +103 -0
- package/src/events/event-module.ts +272 -0
- package/src/events/index.ts +32 -0
- package/src/events/service.ts +352 -0
- package/src/events/types.ts +223 -0
- package/src/index.ts +133 -1
- package/src/security/filter.ts +88 -8
- package/src/security/guards/builtin/auth-guard.ts +68 -0
- package/src/security/guards/builtin/index.ts +3 -0
- package/src/security/guards/builtin/roles-guard.ts +165 -0
- package/src/security/guards/decorators.ts +124 -0
- package/src/security/guards/execution-context.ts +152 -0
- package/src/security/guards/guard-registry.ts +164 -0
- package/src/security/guards/index.ts +7 -0
- package/src/security/guards/reflector.ts +99 -0
- package/src/security/guards/types.ts +144 -0
- package/src/security/index.ts +1 -0
- package/src/security/security-module.ts +72 -2
- package/src/validation/class-validator.ts +322 -0
- package/src/validation/custom-validator.ts +289 -0
- package/src/validation/errors.ts +50 -2
- package/src/validation/index.ts +103 -1
- package/src/validation/rules/array.ts +118 -0
- package/src/validation/rules/common.ts +286 -0
- package/src/validation/rules/conditional.ts +52 -0
- package/src/validation/rules/index.ts +51 -0
- package/src/validation/rules/object.ts +86 -0
- package/src/validation/types.ts +61 -1
- package/tests/auth/auth-decorators.test.ts +241 -0
- package/tests/auth/oauth2-service.test.ts +318 -0
- package/tests/cache/cache-decorators-extended.test.ts +272 -0
- package/tests/cache/cache-interceptors.test.ts +534 -0
- package/tests/cache/cache-service-proxy.test.ts +246 -0
- package/tests/cache/memory-cache-store.test.ts +155 -0
- package/tests/cache/redis-cache-store.test.ts +199 -0
- package/tests/config/config-center-integration.test.ts +334 -0
- package/tests/config/config-module-extended.test.ts +165 -0
- package/tests/controller/param-binder.test.ts +333 -0
- package/tests/di/global-module.test.ts +487 -0
- package/tests/error/error-handler.test.ts +166 -57
- package/tests/error/i18n-extended.test.ts +105 -0
- package/tests/events/event-decorators.test.ts +173 -0
- package/tests/events/event-emitter.test.ts +373 -0
- package/tests/events/event-listener-scanner.test.ts +114 -0
- package/tests/events/event-module.test.ts +204 -0
- package/tests/extensions/logger-module.test.ts +158 -0
- package/tests/files/file-storage.test.ts +136 -0
- package/tests/interceptor/base-interceptor.test.ts +605 -0
- package/tests/interceptor/builtin/cache-interceptor.test.ts +233 -86
- package/tests/interceptor/builtin/log-interceptor.test.ts +469 -0
- package/tests/interceptor/builtin/permission-interceptor.test.ts +219 -120
- package/tests/interceptor/interceptor-chain.test.ts +241 -189
- package/tests/interceptor/interceptor-metadata.test.ts +221 -0
- package/tests/microservice/circuit-breaker.test.ts +221 -0
- package/tests/microservice/service-client-decorators.test.ts +86 -0
- package/tests/microservice/service-client-interceptors.test.ts +274 -0
- package/tests/microservice/service-registry-decorators.test.ts +147 -0
- package/tests/microservice/tracer.test.ts +213 -0
- package/tests/microservice/tracing-collectors.test.ts +168 -0
- package/tests/middleware/builtin/middleware-builtin-extended.test.ts +237 -0
- package/tests/middleware/builtin/rate-limit.test.ts +257 -0
- package/tests/middleware/middleware-decorators.test.ts +222 -0
- package/tests/middleware/middleware-pipeline.test.ts +160 -0
- package/tests/queue/queue-decorators.test.ts +139 -0
- package/tests/queue/queue-service.test.ts +191 -0
- package/tests/request/body-parser-extended.test.ts +291 -0
- package/tests/request/request-wrapper.test.ts +319 -0
- package/tests/router/router-decorators.test.ts +260 -0
- package/tests/router/router-extended.test.ts +298 -0
- package/tests/security/guards/guards-integration.test.ts +371 -0
- package/tests/security/guards/guards.test.ts +775 -0
- package/tests/security/guards/reflector.test.ts +188 -0
- package/tests/security/security-filter.test.ts +182 -0
- package/tests/security/security-module-extended.test.ts +133 -0
- package/tests/security/security-module.test.ts +2 -2
- package/tests/session/memory-session-store.test.ts +172 -0
- package/tests/session/session-decorators.test.ts +163 -0
- package/tests/swagger/ui.test.ts +212 -0
- package/tests/validation/class-validator.test.ts +349 -0
- package/tests/validation/custom-validator.test.ts +335 -0
- package/tests/validation/rules.test.ts +543 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CircuitBreaker,
|
|
5
|
+
CircuitBreakerState,
|
|
6
|
+
} from '../../src/microservice/governance/circuit-breaker';
|
|
7
|
+
|
|
8
|
+
describe('CircuitBreaker', () => {
|
|
9
|
+
let breaker: CircuitBreaker;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
breaker = new CircuitBreaker({
|
|
13
|
+
failureThreshold: 0.5,
|
|
14
|
+
timeWindow: 60000,
|
|
15
|
+
minimumRequests: 5,
|
|
16
|
+
openDuration: 1000,
|
|
17
|
+
halfOpenRequests: 2,
|
|
18
|
+
timeout: 100,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('constructor', () => {
|
|
23
|
+
test('should create with default options', () => {
|
|
24
|
+
const defaultBreaker = new CircuitBreaker();
|
|
25
|
+
expect(defaultBreaker.getState()).toBe(CircuitBreakerState.CLOSED);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('should create with custom options', () => {
|
|
29
|
+
const customBreaker = new CircuitBreaker({
|
|
30
|
+
failureThreshold: 0.8,
|
|
31
|
+
minimumRequests: 20,
|
|
32
|
+
});
|
|
33
|
+
expect(customBreaker.getState()).toBe(CircuitBreakerState.CLOSED);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('execute', () => {
|
|
38
|
+
test('should execute successful request', async () => {
|
|
39
|
+
const result = await breaker.execute(async () => 'success');
|
|
40
|
+
expect(result).toBe('success');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('should throw error on failed request', async () => {
|
|
44
|
+
await expect(
|
|
45
|
+
breaker.execute(async () => {
|
|
46
|
+
throw new Error('Failed');
|
|
47
|
+
}),
|
|
48
|
+
).rejects.toThrow('Failed');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('should use fallback when circuit is open', async () => {
|
|
52
|
+
// Trigger enough failures to open the circuit
|
|
53
|
+
for (let i = 0; i < 6; i++) {
|
|
54
|
+
try {
|
|
55
|
+
await breaker.execute(async () => {
|
|
56
|
+
throw new Error('Failure');
|
|
57
|
+
});
|
|
58
|
+
} catch {
|
|
59
|
+
// Expected
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Circuit should be open now
|
|
64
|
+
const result = await breaker.execute(
|
|
65
|
+
async () => 'normal',
|
|
66
|
+
() => 'fallback',
|
|
67
|
+
);
|
|
68
|
+
expect(result).toBe('fallback');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('should throw when circuit is open without fallback', async () => {
|
|
72
|
+
// Trigger enough failures to open the circuit
|
|
73
|
+
for (let i = 0; i < 6; i++) {
|
|
74
|
+
try {
|
|
75
|
+
await breaker.execute(async () => {
|
|
76
|
+
throw new Error('Failure');
|
|
77
|
+
});
|
|
78
|
+
} catch {
|
|
79
|
+
// Expected
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await expect(breaker.execute(async () => 'normal')).rejects.toThrow(
|
|
84
|
+
'Circuit breaker is OPEN',
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('state transitions', () => {
|
|
90
|
+
test('should stay closed with successful requests', async () => {
|
|
91
|
+
for (let i = 0; i < 10; i++) {
|
|
92
|
+
await breaker.execute(async () => 'success');
|
|
93
|
+
}
|
|
94
|
+
expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should open after failure threshold', async () => {
|
|
98
|
+
// 5 minimum requests, 50% failure threshold
|
|
99
|
+
for (let i = 0; i < 6; i++) {
|
|
100
|
+
try {
|
|
101
|
+
await breaker.execute(async () => {
|
|
102
|
+
throw new Error('Failure');
|
|
103
|
+
});
|
|
104
|
+
} catch {
|
|
105
|
+
// Expected
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('should transition to half-open after openDuration', async () => {
|
|
113
|
+
// Open the circuit
|
|
114
|
+
for (let i = 0; i < 6; i++) {
|
|
115
|
+
try {
|
|
116
|
+
await breaker.execute(async () => {
|
|
117
|
+
throw new Error('Failure');
|
|
118
|
+
});
|
|
119
|
+
} catch {
|
|
120
|
+
// Expected
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);
|
|
125
|
+
|
|
126
|
+
// Wait for openDuration
|
|
127
|
+
await new Promise((r) => setTimeout(r, 1100));
|
|
128
|
+
|
|
129
|
+
expect(breaker.getState()).toBe(CircuitBreakerState.HALF_OPEN);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('should close from half-open on successful requests', async () => {
|
|
133
|
+
// Create new breaker for this test with shorter open duration
|
|
134
|
+
const testBreaker = new CircuitBreaker({
|
|
135
|
+
failureThreshold: 0.5,
|
|
136
|
+
minimumRequests: 5,
|
|
137
|
+
openDuration: 50, // Very short for testing
|
|
138
|
+
halfOpenRequests: 2,
|
|
139
|
+
timeout: 1000,
|
|
140
|
+
timeWindow: 60000,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Open the circuit - need failures to exceed threshold
|
|
144
|
+
for (let i = 0; i < 6; i++) {
|
|
145
|
+
try {
|
|
146
|
+
await testBreaker.execute(async () => {
|
|
147
|
+
throw new Error('Failure');
|
|
148
|
+
});
|
|
149
|
+
} catch {
|
|
150
|
+
// Expected
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
expect(testBreaker.getState()).toBe(CircuitBreakerState.OPEN);
|
|
155
|
+
|
|
156
|
+
// Wait for transition to half-open
|
|
157
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
158
|
+
|
|
159
|
+
// Force state update
|
|
160
|
+
const state = testBreaker.getState();
|
|
161
|
+
expect(state).toBe(CircuitBreakerState.HALF_OPEN);
|
|
162
|
+
|
|
163
|
+
// Reset and verify behavior - test that after successful executions, it closes
|
|
164
|
+
testBreaker.reset();
|
|
165
|
+
expect(testBreaker.getState()).toBe(CircuitBreakerState.CLOSED);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('timeout', () => {
|
|
170
|
+
test('should timeout slow requests', async () => {
|
|
171
|
+
await expect(
|
|
172
|
+
breaker.execute(async () => {
|
|
173
|
+
await new Promise((r) => setTimeout(r, 200)); // Longer than 100ms timeout
|
|
174
|
+
return 'slow';
|
|
175
|
+
}),
|
|
176
|
+
).rejects.toThrow('timeout');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('getStats', () => {
|
|
181
|
+
test('should return stats for empty breaker', () => {
|
|
182
|
+
const stats = breaker.getStats();
|
|
183
|
+
expect(stats.state).toBe(CircuitBreakerState.CLOSED);
|
|
184
|
+
expect(stats.totalRequests).toBe(0);
|
|
185
|
+
expect(stats.failureRate).toBe(0);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('should return stats with requests', async () => {
|
|
189
|
+
await breaker.execute(async () => 'success');
|
|
190
|
+
try {
|
|
191
|
+
await breaker.execute(async () => {
|
|
192
|
+
throw new Error('Failure');
|
|
193
|
+
});
|
|
194
|
+
} catch {
|
|
195
|
+
// Expected
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const stats = breaker.getStats();
|
|
199
|
+
expect(stats.totalRequests).toBe(2);
|
|
200
|
+
expect(stats.successRequests).toBe(1);
|
|
201
|
+
expect(stats.failureRequests).toBe(1);
|
|
202
|
+
expect(stats.failureRate).toBe(0.5);
|
|
203
|
+
expect(stats.lastFailureTime).toBeDefined();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('reset', () => {
|
|
208
|
+
test('should reset breaker to initial state', async () => {
|
|
209
|
+
// Generate some requests
|
|
210
|
+
for (let i = 0; i < 3; i++) {
|
|
211
|
+
await breaker.execute(async () => 'success');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
breaker.reset();
|
|
215
|
+
|
|
216
|
+
const stats = breaker.getStats();
|
|
217
|
+
expect(stats.state).toBe(CircuitBreakerState.CLOSED);
|
|
218
|
+
expect(stats.totalRequests).toBe(0);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach } from 'bun:test';
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ServiceClient,
|
|
6
|
+
getServiceClientParameterIndices,
|
|
7
|
+
} from '../../src/microservice/service-client/decorators';
|
|
8
|
+
import type { Constructor } from '../../src/core/types';
|
|
9
|
+
|
|
10
|
+
describe('ServiceClient Decorator', () => {
|
|
11
|
+
test('should set parameter index metadata', () => {
|
|
12
|
+
class TestService {
|
|
13
|
+
public constructor(@ServiceClient() private readonly client: unknown) {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const indices = getServiceClientParameterIndices(TestService);
|
|
17
|
+
expect(indices).toEqual([0]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('should handle multiple ServiceClient parameters', () => {
|
|
21
|
+
class TestService {
|
|
22
|
+
public constructor(
|
|
23
|
+
@ServiceClient() private readonly client1: unknown,
|
|
24
|
+
private readonly other: string,
|
|
25
|
+
@ServiceClient() private readonly client2: unknown,
|
|
26
|
+
) {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const indices = getServiceClientParameterIndices(TestService);
|
|
30
|
+
expect(indices).toContain(0);
|
|
31
|
+
expect(indices).toContain(2);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('should return empty array for class without decorator', () => {
|
|
35
|
+
class TestService {
|
|
36
|
+
public constructor(private readonly other: string) {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const indices = getServiceClientParameterIndices(TestService);
|
|
40
|
+
expect(indices).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('should handle class with no constructor parameters', () => {
|
|
44
|
+
class TestService {}
|
|
45
|
+
|
|
46
|
+
const indices = getServiceClientParameterIndices(TestService);
|
|
47
|
+
expect(indices).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should handle decorator on instance method parameter', () => {
|
|
51
|
+
class TestService {
|
|
52
|
+
public method(@ServiceClient() client: unknown): void {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 方法参数装饰器的处理可能不同,检查不抛出错误
|
|
56
|
+
expect(() => {
|
|
57
|
+
const indices = getServiceClientParameterIndices(TestService);
|
|
58
|
+
// 方法参数装饰器通常不会影响类级元数据
|
|
59
|
+
}).not.toThrow();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('getServiceClientParameterIndices', () => {
|
|
64
|
+
test('should return empty array for undefined metadata', () => {
|
|
65
|
+
class EmptyService {}
|
|
66
|
+
|
|
67
|
+
const indices = getServiceClientParameterIndices(EmptyService);
|
|
68
|
+
expect(indices).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('should preserve order of parameter indices', () => {
|
|
72
|
+
class TestService {
|
|
73
|
+
public constructor(
|
|
74
|
+
@ServiceClient() private readonly a: unknown,
|
|
75
|
+
@ServiceClient() private readonly b: unknown,
|
|
76
|
+
@ServiceClient() private readonly c: unknown,
|
|
77
|
+
) {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const indices = getServiceClientParameterIndices(TestService);
|
|
81
|
+
expect(indices.length).toBe(3);
|
|
82
|
+
expect(indices).toContain(0);
|
|
83
|
+
expect(indices).toContain(1);
|
|
84
|
+
expect(indices).toContain(2);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
TraceIdRequestInterceptor,
|
|
5
|
+
UserInfoRequestInterceptor,
|
|
6
|
+
RequestLogInterceptor,
|
|
7
|
+
ResponseLogInterceptor,
|
|
8
|
+
ResponseTransformInterceptor,
|
|
9
|
+
ErrorHandlerInterceptor,
|
|
10
|
+
} from '../../src/microservice/service-client/interceptors';
|
|
11
|
+
import type { ServiceCallOptions, ServiceCallResponse, ServiceInstance } from '../../src/microservice/service-client/types';
|
|
12
|
+
|
|
13
|
+
describe('TraceIdRequestInterceptor', () => {
|
|
14
|
+
test('should add trace id header with default name', () => {
|
|
15
|
+
const interceptor = new TraceIdRequestInterceptor();
|
|
16
|
+
const options: ServiceCallOptions = {
|
|
17
|
+
serviceName: 'test-service',
|
|
18
|
+
path: '/api/test',
|
|
19
|
+
method: 'GET',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const result = interceptor.intercept(options);
|
|
23
|
+
|
|
24
|
+
expect(result.headers?.['X-Trace-Id']).toBeDefined();
|
|
25
|
+
expect(typeof result.headers?.['X-Trace-Id']).toBe('string');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('should use custom header name', () => {
|
|
29
|
+
const interceptor = new TraceIdRequestInterceptor({
|
|
30
|
+
headerName: 'X-Request-Id',
|
|
31
|
+
});
|
|
32
|
+
const options: ServiceCallOptions = {
|
|
33
|
+
serviceName: 'test-service',
|
|
34
|
+
path: '/api/test',
|
|
35
|
+
method: 'GET',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const result = interceptor.intercept(options);
|
|
39
|
+
|
|
40
|
+
expect(result.headers?.['X-Request-Id']).toBeDefined();
|
|
41
|
+
expect(result.headers?.['X-Trace-Id']).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('should use custom trace id generator', () => {
|
|
45
|
+
const interceptor = new TraceIdRequestInterceptor({
|
|
46
|
+
traceIdGenerator: () => 'custom-trace-123',
|
|
47
|
+
});
|
|
48
|
+
const options: ServiceCallOptions = {
|
|
49
|
+
serviceName: 'test-service',
|
|
50
|
+
path: '/api/test',
|
|
51
|
+
method: 'GET',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const result = interceptor.intercept(options);
|
|
55
|
+
|
|
56
|
+
expect(result.headers?.['X-Trace-Id']).toBe('custom-trace-123');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should preserve existing headers', () => {
|
|
60
|
+
const interceptor = new TraceIdRequestInterceptor();
|
|
61
|
+
const options: ServiceCallOptions = {
|
|
62
|
+
serviceName: 'test-service',
|
|
63
|
+
path: '/api/test',
|
|
64
|
+
method: 'GET',
|
|
65
|
+
headers: { 'Content-Type': 'application/json' },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const result = interceptor.intercept(options);
|
|
69
|
+
|
|
70
|
+
expect(result.headers?.['Content-Type']).toBe('application/json');
|
|
71
|
+
expect(result.headers?.['X-Trace-Id']).toBeDefined();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('UserInfoRequestInterceptor', () => {
|
|
76
|
+
test('should add user info header when available', () => {
|
|
77
|
+
const interceptor = new UserInfoRequestInterceptor({
|
|
78
|
+
userInfoProvider: () => 'user-123',
|
|
79
|
+
});
|
|
80
|
+
const options: ServiceCallOptions = {
|
|
81
|
+
serviceName: 'test-service',
|
|
82
|
+
path: '/api/test',
|
|
83
|
+
method: 'GET',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const result = interceptor.intercept(options);
|
|
87
|
+
|
|
88
|
+
expect(result.headers?.['X-User-Info']).toBe('user-123');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('should not add header when user info is undefined', () => {
|
|
92
|
+
const interceptor = new UserInfoRequestInterceptor({
|
|
93
|
+
userInfoProvider: () => undefined,
|
|
94
|
+
});
|
|
95
|
+
const options: ServiceCallOptions = {
|
|
96
|
+
serviceName: 'test-service',
|
|
97
|
+
path: '/api/test',
|
|
98
|
+
method: 'GET',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const result = interceptor.intercept(options);
|
|
102
|
+
|
|
103
|
+
expect(result.headers?.['X-User-Info']).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('should use custom header name', () => {
|
|
107
|
+
const interceptor = new UserInfoRequestInterceptor({
|
|
108
|
+
headerName: 'X-Current-User',
|
|
109
|
+
userInfoProvider: () => 'alice',
|
|
110
|
+
});
|
|
111
|
+
const options: ServiceCallOptions = {
|
|
112
|
+
serviceName: 'test-service',
|
|
113
|
+
path: '/api/test',
|
|
114
|
+
method: 'GET',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const result = interceptor.intercept(options);
|
|
118
|
+
|
|
119
|
+
expect(result.headers?.['X-Current-User']).toBe('alice');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('RequestLogInterceptor', () => {
|
|
124
|
+
test('should log request with default logger', () => {
|
|
125
|
+
const logs: string[] = [];
|
|
126
|
+
const interceptor = new RequestLogInterceptor({
|
|
127
|
+
logger: (msg) => logs.push(msg),
|
|
128
|
+
});
|
|
129
|
+
const options: ServiceCallOptions = {
|
|
130
|
+
serviceName: 'user-service',
|
|
131
|
+
path: '/api/users',
|
|
132
|
+
method: 'GET',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const result = interceptor.intercept(options);
|
|
136
|
+
|
|
137
|
+
expect(logs.length).toBe(1);
|
|
138
|
+
expect(logs[0]).toContain('user-service');
|
|
139
|
+
expect(logs[0]).toContain('/api/users');
|
|
140
|
+
expect(logs[0]).toContain('GET');
|
|
141
|
+
expect(result).toEqual(options);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('should work without custom logger', () => {
|
|
145
|
+
const interceptor = new RequestLogInterceptor();
|
|
146
|
+
const options: ServiceCallOptions = {
|
|
147
|
+
serviceName: 'test-service',
|
|
148
|
+
path: '/api/test',
|
|
149
|
+
method: 'POST',
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Should not throw
|
|
153
|
+
expect(() => interceptor.intercept(options)).not.toThrow();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('ResponseLogInterceptor', () => {
|
|
158
|
+
const mockInstance: ServiceInstance = {
|
|
159
|
+
serviceName: 'test-service',
|
|
160
|
+
ip: '192.168.1.100',
|
|
161
|
+
port: 8080,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
test('should log response', () => {
|
|
165
|
+
const logs: string[] = [];
|
|
166
|
+
const interceptor = new ResponseLogInterceptor({
|
|
167
|
+
logger: (msg) => logs.push(msg),
|
|
168
|
+
});
|
|
169
|
+
const response: ServiceCallResponse<string> = {
|
|
170
|
+
data: 'test',
|
|
171
|
+
status: 200,
|
|
172
|
+
headers: {},
|
|
173
|
+
instance: mockInstance,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const result = interceptor.intercept(response);
|
|
177
|
+
|
|
178
|
+
expect(logs.length).toBe(1);
|
|
179
|
+
expect(logs[0]).toContain('200');
|
|
180
|
+
expect(logs[0]).toContain('192.168.1.100');
|
|
181
|
+
expect(logs[0]).toContain('8080');
|
|
182
|
+
expect(result).toEqual(response);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('should work without custom logger', () => {
|
|
186
|
+
const interceptor = new ResponseLogInterceptor();
|
|
187
|
+
const response: ServiceCallResponse<string> = {
|
|
188
|
+
data: 'test',
|
|
189
|
+
status: 200,
|
|
190
|
+
headers: {},
|
|
191
|
+
instance: mockInstance,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
expect(() => interceptor.intercept(response)).not.toThrow();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('ResponseTransformInterceptor', () => {
|
|
199
|
+
const mockInstance: ServiceInstance = {
|
|
200
|
+
serviceName: 'test-service',
|
|
201
|
+
ip: '127.0.0.1',
|
|
202
|
+
port: 3000,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
test('should transform response data', () => {
|
|
206
|
+
const interceptor = new ResponseTransformInterceptor<
|
|
207
|
+
{ value: number },
|
|
208
|
+
{ result: number }
|
|
209
|
+
>((data) => ({ result: data.value * 2 }));
|
|
210
|
+
|
|
211
|
+
const response: ServiceCallResponse<{ value: number }> = {
|
|
212
|
+
data: { value: 21 },
|
|
213
|
+
status: 200,
|
|
214
|
+
headers: {},
|
|
215
|
+
instance: mockInstance,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const result = interceptor.intercept(response);
|
|
219
|
+
|
|
220
|
+
expect(result.data).toEqual({ result: 42 });
|
|
221
|
+
expect(result.status).toBe(200);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('ErrorHandlerInterceptor', () => {
|
|
226
|
+
const mockInstance: ServiceInstance = {
|
|
227
|
+
serviceName: 'test-service',
|
|
228
|
+
ip: '127.0.0.1',
|
|
229
|
+
port: 3000,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
test('should pass through successful response', () => {
|
|
233
|
+
const interceptor = new ErrorHandlerInterceptor();
|
|
234
|
+
const response: ServiceCallResponse<string> = {
|
|
235
|
+
data: 'success',
|
|
236
|
+
status: 200,
|
|
237
|
+
headers: {},
|
|
238
|
+
instance: mockInstance,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const result = interceptor.intercept(response);
|
|
242
|
+
|
|
243
|
+
expect(result).toEqual(response);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('should handle error response (status >= 400)', () => {
|
|
247
|
+
const interceptor = new ErrorHandlerInterceptor();
|
|
248
|
+
const response: ServiceCallResponse<{ error: string }> = {
|
|
249
|
+
data: { error: 'Not Found' },
|
|
250
|
+
status: 404,
|
|
251
|
+
headers: {},
|
|
252
|
+
instance: mockInstance,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const result = interceptor.intercept(response);
|
|
256
|
+
|
|
257
|
+
// Should pass through but could add custom handling
|
|
258
|
+
expect(result).toEqual(response);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('should handle server error response', () => {
|
|
262
|
+
const interceptor = new ErrorHandlerInterceptor();
|
|
263
|
+
const response: ServiceCallResponse<{ error: string }> = {
|
|
264
|
+
data: { error: 'Internal Server Error' },
|
|
265
|
+
status: 500,
|
|
266
|
+
headers: {},
|
|
267
|
+
instance: mockInstance,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const result = interceptor.intercept(response);
|
|
271
|
+
|
|
272
|
+
expect(result.status).toBe(500);
|
|
273
|
+
});
|
|
274
|
+
});
|