@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.
Files changed (159) hide show
  1. package/README.md +129 -21
  2. package/dist/di/decorators.d.ts +37 -0
  3. package/dist/di/decorators.d.ts.map +1 -1
  4. package/dist/di/index.d.ts +1 -1
  5. package/dist/di/index.d.ts.map +1 -1
  6. package/dist/di/module-registry.d.ts +17 -0
  7. package/dist/di/module-registry.d.ts.map +1 -1
  8. package/dist/events/decorators.d.ts +52 -0
  9. package/dist/events/decorators.d.ts.map +1 -0
  10. package/dist/events/event-module.d.ts +97 -0
  11. package/dist/events/event-module.d.ts.map +1 -0
  12. package/dist/events/index.d.ts +5 -0
  13. package/dist/events/index.d.ts.map +1 -0
  14. package/dist/events/service.d.ts +76 -0
  15. package/dist/events/service.d.ts.map +1 -0
  16. package/dist/events/types.d.ts +184 -0
  17. package/dist/events/types.d.ts.map +1 -0
  18. package/dist/index.d.ts +5 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1511 -11
  21. package/dist/security/filter.d.ts +23 -0
  22. package/dist/security/filter.d.ts.map +1 -1
  23. package/dist/security/guards/builtin/auth-guard.d.ts +44 -0
  24. package/dist/security/guards/builtin/auth-guard.d.ts.map +1 -0
  25. package/dist/security/guards/builtin/index.d.ts +3 -0
  26. package/dist/security/guards/builtin/index.d.ts.map +1 -0
  27. package/dist/security/guards/builtin/roles-guard.d.ts +66 -0
  28. package/dist/security/guards/builtin/roles-guard.d.ts.map +1 -0
  29. package/dist/security/guards/decorators.d.ts +50 -0
  30. package/dist/security/guards/decorators.d.ts.map +1 -0
  31. package/dist/security/guards/execution-context.d.ts +56 -0
  32. package/dist/security/guards/execution-context.d.ts.map +1 -0
  33. package/dist/security/guards/guard-registry.d.ts +67 -0
  34. package/dist/security/guards/guard-registry.d.ts.map +1 -0
  35. package/dist/security/guards/index.d.ts +7 -0
  36. package/dist/security/guards/index.d.ts.map +1 -0
  37. package/dist/security/guards/reflector.d.ts +57 -0
  38. package/dist/security/guards/reflector.d.ts.map +1 -0
  39. package/dist/security/guards/types.d.ts +126 -0
  40. package/dist/security/guards/types.d.ts.map +1 -0
  41. package/dist/security/index.d.ts +1 -0
  42. package/dist/security/index.d.ts.map +1 -1
  43. package/dist/security/security-module.d.ts +20 -0
  44. package/dist/security/security-module.d.ts.map +1 -1
  45. package/dist/validation/class-validator.d.ts +108 -0
  46. package/dist/validation/class-validator.d.ts.map +1 -0
  47. package/dist/validation/custom-validator.d.ts +130 -0
  48. package/dist/validation/custom-validator.d.ts.map +1 -0
  49. package/dist/validation/errors.d.ts +22 -2
  50. package/dist/validation/errors.d.ts.map +1 -1
  51. package/dist/validation/index.d.ts +7 -1
  52. package/dist/validation/index.d.ts.map +1 -1
  53. package/dist/validation/rules/array.d.ts +33 -0
  54. package/dist/validation/rules/array.d.ts.map +1 -0
  55. package/dist/validation/rules/common.d.ts +90 -0
  56. package/dist/validation/rules/common.d.ts.map +1 -0
  57. package/dist/validation/rules/conditional.d.ts +30 -0
  58. package/dist/validation/rules/conditional.d.ts.map +1 -0
  59. package/dist/validation/rules/index.d.ts +5 -0
  60. package/dist/validation/rules/index.d.ts.map +1 -0
  61. package/dist/validation/rules/object.d.ts +30 -0
  62. package/dist/validation/rules/object.d.ts.map +1 -0
  63. package/dist/validation/types.d.ts +52 -1
  64. package/dist/validation/types.d.ts.map +1 -1
  65. package/docs/events.md +494 -0
  66. package/docs/guards.md +376 -0
  67. package/docs/guide.md +309 -1
  68. package/docs/request-lifecycle.md +444 -0
  69. package/docs/validation.md +407 -0
  70. package/docs/zh/events.md +494 -0
  71. package/docs/zh/guards.md +376 -0
  72. package/docs/zh/guide.md +309 -1
  73. package/docs/zh/request-lifecycle.md +444 -0
  74. package/docs/zh/validation.md +407 -0
  75. package/package.json +1 -1
  76. package/src/di/decorators.ts +46 -0
  77. package/src/di/index.ts +10 -1
  78. package/src/di/module-registry.ts +39 -0
  79. package/src/events/decorators.ts +103 -0
  80. package/src/events/event-module.ts +272 -0
  81. package/src/events/index.ts +32 -0
  82. package/src/events/service.ts +352 -0
  83. package/src/events/types.ts +223 -0
  84. package/src/index.ts +133 -1
  85. package/src/security/filter.ts +88 -8
  86. package/src/security/guards/builtin/auth-guard.ts +68 -0
  87. package/src/security/guards/builtin/index.ts +3 -0
  88. package/src/security/guards/builtin/roles-guard.ts +165 -0
  89. package/src/security/guards/decorators.ts +124 -0
  90. package/src/security/guards/execution-context.ts +152 -0
  91. package/src/security/guards/guard-registry.ts +164 -0
  92. package/src/security/guards/index.ts +7 -0
  93. package/src/security/guards/reflector.ts +99 -0
  94. package/src/security/guards/types.ts +144 -0
  95. package/src/security/index.ts +1 -0
  96. package/src/security/security-module.ts +72 -2
  97. package/src/validation/class-validator.ts +322 -0
  98. package/src/validation/custom-validator.ts +289 -0
  99. package/src/validation/errors.ts +50 -2
  100. package/src/validation/index.ts +103 -1
  101. package/src/validation/rules/array.ts +118 -0
  102. package/src/validation/rules/common.ts +286 -0
  103. package/src/validation/rules/conditional.ts +52 -0
  104. package/src/validation/rules/index.ts +51 -0
  105. package/src/validation/rules/object.ts +86 -0
  106. package/src/validation/types.ts +61 -1
  107. package/tests/auth/auth-decorators.test.ts +241 -0
  108. package/tests/auth/oauth2-service.test.ts +318 -0
  109. package/tests/cache/cache-decorators-extended.test.ts +272 -0
  110. package/tests/cache/cache-interceptors.test.ts +534 -0
  111. package/tests/cache/cache-service-proxy.test.ts +246 -0
  112. package/tests/cache/memory-cache-store.test.ts +155 -0
  113. package/tests/cache/redis-cache-store.test.ts +199 -0
  114. package/tests/config/config-center-integration.test.ts +334 -0
  115. package/tests/config/config-module-extended.test.ts +165 -0
  116. package/tests/controller/param-binder.test.ts +333 -0
  117. package/tests/di/global-module.test.ts +487 -0
  118. package/tests/error/error-handler.test.ts +166 -57
  119. package/tests/error/i18n-extended.test.ts +105 -0
  120. package/tests/events/event-decorators.test.ts +173 -0
  121. package/tests/events/event-emitter.test.ts +373 -0
  122. package/tests/events/event-listener-scanner.test.ts +114 -0
  123. package/tests/events/event-module.test.ts +204 -0
  124. package/tests/extensions/logger-module.test.ts +158 -0
  125. package/tests/files/file-storage.test.ts +136 -0
  126. package/tests/interceptor/base-interceptor.test.ts +605 -0
  127. package/tests/interceptor/builtin/cache-interceptor.test.ts +233 -86
  128. package/tests/interceptor/builtin/log-interceptor.test.ts +469 -0
  129. package/tests/interceptor/builtin/permission-interceptor.test.ts +219 -120
  130. package/tests/interceptor/interceptor-chain.test.ts +241 -189
  131. package/tests/interceptor/interceptor-metadata.test.ts +221 -0
  132. package/tests/microservice/circuit-breaker.test.ts +221 -0
  133. package/tests/microservice/service-client-decorators.test.ts +86 -0
  134. package/tests/microservice/service-client-interceptors.test.ts +274 -0
  135. package/tests/microservice/service-registry-decorators.test.ts +147 -0
  136. package/tests/microservice/tracer.test.ts +213 -0
  137. package/tests/microservice/tracing-collectors.test.ts +168 -0
  138. package/tests/middleware/builtin/middleware-builtin-extended.test.ts +237 -0
  139. package/tests/middleware/builtin/rate-limit.test.ts +257 -0
  140. package/tests/middleware/middleware-decorators.test.ts +222 -0
  141. package/tests/middleware/middleware-pipeline.test.ts +160 -0
  142. package/tests/queue/queue-decorators.test.ts +139 -0
  143. package/tests/queue/queue-service.test.ts +191 -0
  144. package/tests/request/body-parser-extended.test.ts +291 -0
  145. package/tests/request/request-wrapper.test.ts +319 -0
  146. package/tests/router/router-decorators.test.ts +260 -0
  147. package/tests/router/router-extended.test.ts +298 -0
  148. package/tests/security/guards/guards-integration.test.ts +371 -0
  149. package/tests/security/guards/guards.test.ts +775 -0
  150. package/tests/security/guards/reflector.test.ts +188 -0
  151. package/tests/security/security-filter.test.ts +182 -0
  152. package/tests/security/security-module-extended.test.ts +133 -0
  153. package/tests/security/security-module.test.ts +2 -2
  154. package/tests/session/memory-session-store.test.ts +172 -0
  155. package/tests/session/session-decorators.test.ts +163 -0
  156. package/tests/swagger/ui.test.ts +212 -0
  157. package/tests/validation/class-validator.test.ts +349 -0
  158. package/tests/validation/custom-validator.test.ts +335 -0
  159. 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
+ });