@hazeljs/gateway 0.2.0-beta.41
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/LICENSE +192 -0
- package/README.md +255 -0
- package/dist/__tests__/canary-engine.test.d.ts +2 -0
- package/dist/__tests__/canary-engine.test.d.ts.map +1 -0
- package/dist/__tests__/canary-engine.test.js +133 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +174 -0
- package/dist/__tests__/from-config.test.d.ts +2 -0
- package/dist/__tests__/from-config.test.d.ts.map +1 -0
- package/dist/__tests__/from-config.test.js +67 -0
- package/dist/__tests__/gateway-metrics.test.d.ts +2 -0
- package/dist/__tests__/gateway-metrics.test.d.ts.map +1 -0
- package/dist/__tests__/gateway-metrics.test.js +82 -0
- package/dist/__tests__/gateway-module.test.d.ts +2 -0
- package/dist/__tests__/gateway-module.test.d.ts.map +1 -0
- package/dist/__tests__/gateway-module.test.js +91 -0
- package/dist/__tests__/gateway.test.d.ts +2 -0
- package/dist/__tests__/gateway.test.d.ts.map +1 -0
- package/dist/__tests__/gateway.test.js +257 -0
- package/dist/__tests__/hazel-integration.test.d.ts +2 -0
- package/dist/__tests__/hazel-integration.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-integration.test.js +92 -0
- package/dist/__tests__/route-matcher.test.d.ts +2 -0
- package/dist/__tests__/route-matcher.test.d.ts.map +1 -0
- package/dist/__tests__/route-matcher.test.js +67 -0
- package/dist/__tests__/service-proxy.test.d.ts +2 -0
- package/dist/__tests__/service-proxy.test.d.ts.map +1 -0
- package/dist/__tests__/service-proxy.test.js +110 -0
- package/dist/__tests__/traffic-mirror.test.d.ts +2 -0
- package/dist/__tests__/traffic-mirror.test.d.ts.map +1 -0
- package/dist/__tests__/traffic-mirror.test.js +70 -0
- package/dist/__tests__/version-router.test.d.ts +2 -0
- package/dist/__tests__/version-router.test.d.ts.map +1 -0
- package/dist/__tests__/version-router.test.js +136 -0
- package/dist/canary/canary-engine.d.ts +107 -0
- package/dist/canary/canary-engine.d.ts.map +1 -0
- package/dist/canary/canary-engine.js +334 -0
- package/dist/decorators/index.d.ts +74 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +170 -0
- package/dist/gateway.d.ts +67 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +310 -0
- package/dist/gateway.module.d.ts +67 -0
- package/dist/gateway.module.d.ts.map +1 -0
- package/dist/gateway.module.js +82 -0
- package/dist/hazel-integration.d.ts +24 -0
- package/dist/hazel-integration.d.ts.map +1 -0
- package/dist/hazel-integration.js +70 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/metrics/gateway-metrics.d.ts +64 -0
- package/dist/metrics/gateway-metrics.d.ts.map +1 -0
- package/dist/metrics/gateway-metrics.js +159 -0
- package/dist/middleware/traffic-mirror.d.ts +19 -0
- package/dist/middleware/traffic-mirror.d.ts.map +1 -0
- package/dist/middleware/traffic-mirror.js +60 -0
- package/dist/proxy/service-proxy.d.ts +68 -0
- package/dist/proxy/service-proxy.d.ts.map +1 -0
- package/dist/proxy/service-proxy.js +211 -0
- package/dist/routing/route-matcher.d.ts +31 -0
- package/dist/routing/route-matcher.d.ts.map +1 -0
- package/dist/routing/route-matcher.js +112 -0
- package/dist/routing/version-router.d.ts +36 -0
- package/dist/routing/version-router.d.ts.map +1 -0
- package/dist/routing/version-router.js +136 -0
- package/dist/types/index.d.ts +217 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +17 -0
- package/package.json +74 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
require("reflect-metadata");
|
|
13
|
+
const decorators_1 = require("../decorators");
|
|
14
|
+
describe('Gateway Decorators', () => {
|
|
15
|
+
it('should collect gateway config from class', () => {
|
|
16
|
+
let TestGateway = class TestGateway {
|
|
17
|
+
};
|
|
18
|
+
TestGateway = __decorate([
|
|
19
|
+
(0, decorators_1.Gateway)({
|
|
20
|
+
discovery: { cacheEnabled: true },
|
|
21
|
+
metrics: { enabled: true },
|
|
22
|
+
})
|
|
23
|
+
], TestGateway);
|
|
24
|
+
const { config } = (0, decorators_1.collectRouteDefinitions)(TestGateway);
|
|
25
|
+
expect(config.discovery?.cacheEnabled).toBe(true);
|
|
26
|
+
expect(config.metrics?.enabled).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it('should collect route definitions from properties', () => {
|
|
29
|
+
let TestGateway = class TestGateway {
|
|
30
|
+
};
|
|
31
|
+
__decorate([
|
|
32
|
+
(0, decorators_1.Route)('/api/users/**'),
|
|
33
|
+
(0, decorators_1.ServiceRoute)('user-service'),
|
|
34
|
+
__metadata("design:type", Object)
|
|
35
|
+
], TestGateway.prototype, "userService", void 0);
|
|
36
|
+
TestGateway = __decorate([
|
|
37
|
+
(0, decorators_1.Gateway)({})
|
|
38
|
+
], TestGateway);
|
|
39
|
+
const { routes } = (0, decorators_1.collectRouteDefinitions)(TestGateway);
|
|
40
|
+
expect(routes).toHaveLength(1);
|
|
41
|
+
expect(routes[0].route?.path).toBe('/api/users/**');
|
|
42
|
+
expect(routes[0].serviceRoute?.serviceName).toBe('user-service');
|
|
43
|
+
});
|
|
44
|
+
it('should collect canary config', () => {
|
|
45
|
+
let TestGateway = class TestGateway {
|
|
46
|
+
};
|
|
47
|
+
__decorate([
|
|
48
|
+
(0, decorators_1.Route)('/api/orders/**'),
|
|
49
|
+
(0, decorators_1.ServiceRoute)('order-service'),
|
|
50
|
+
(0, decorators_1.Canary)({
|
|
51
|
+
stable: { version: 'v1', weight: 90 },
|
|
52
|
+
canary: { version: 'v2', weight: 10 },
|
|
53
|
+
promotion: {
|
|
54
|
+
strategy: 'error-rate',
|
|
55
|
+
errorThreshold: 5,
|
|
56
|
+
evaluationWindow: '5m',
|
|
57
|
+
autoPromote: true,
|
|
58
|
+
autoRollback: true,
|
|
59
|
+
steps: [10, 25, 50, 75, 100],
|
|
60
|
+
stepInterval: '10m',
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
__metadata("design:type", Object)
|
|
64
|
+
], TestGateway.prototype, "orderService", void 0);
|
|
65
|
+
TestGateway = __decorate([
|
|
66
|
+
(0, decorators_1.Gateway)({})
|
|
67
|
+
], TestGateway);
|
|
68
|
+
const { routes } = (0, decorators_1.collectRouteDefinitions)(TestGateway);
|
|
69
|
+
expect(routes[0].canary).toBeDefined();
|
|
70
|
+
expect(routes[0].canary.stable.version).toBe('v1');
|
|
71
|
+
expect(routes[0].canary.canary.version).toBe('v2');
|
|
72
|
+
expect(routes[0].canary.promotion.steps).toEqual([10, 25, 50, 75, 100]);
|
|
73
|
+
});
|
|
74
|
+
it('should collect version route config', () => {
|
|
75
|
+
let TestGateway = class TestGateway {
|
|
76
|
+
};
|
|
77
|
+
__decorate([
|
|
78
|
+
(0, decorators_1.Route)('/api/payments/**'),
|
|
79
|
+
(0, decorators_1.ServiceRoute)('payment-service'),
|
|
80
|
+
(0, decorators_1.VersionRoute)({
|
|
81
|
+
header: 'X-API-Version',
|
|
82
|
+
routes: {
|
|
83
|
+
v1: { weight: 100 },
|
|
84
|
+
v2: { weight: 0, allowExplicit: true },
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
__metadata("design:type", Object)
|
|
88
|
+
], TestGateway.prototype, "paymentService", void 0);
|
|
89
|
+
TestGateway = __decorate([
|
|
90
|
+
(0, decorators_1.Gateway)({})
|
|
91
|
+
], TestGateway);
|
|
92
|
+
const { routes } = (0, decorators_1.collectRouteDefinitions)(TestGateway);
|
|
93
|
+
expect(routes[0].versionRoute).toBeDefined();
|
|
94
|
+
expect(routes[0].versionRoute.header).toBe('X-API-Version');
|
|
95
|
+
expect(routes[0].versionRoute.routes['v1'].weight).toBe(100);
|
|
96
|
+
expect(routes[0].versionRoute.routes['v2'].allowExplicit).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
it('should collect traffic policy', () => {
|
|
99
|
+
let TestGateway = class TestGateway {
|
|
100
|
+
};
|
|
101
|
+
__decorate([
|
|
102
|
+
(0, decorators_1.Route)('/api/search/**'),
|
|
103
|
+
(0, decorators_1.ServiceRoute)('search-service'),
|
|
104
|
+
(0, decorators_1.TrafficPolicy)({
|
|
105
|
+
mirror: { service: 'search-v2', percentage: 10 },
|
|
106
|
+
timeout: 3000,
|
|
107
|
+
}),
|
|
108
|
+
__metadata("design:type", Object)
|
|
109
|
+
], TestGateway.prototype, "searchService", void 0);
|
|
110
|
+
TestGateway = __decorate([
|
|
111
|
+
(0, decorators_1.Gateway)({})
|
|
112
|
+
], TestGateway);
|
|
113
|
+
const { routes } = (0, decorators_1.collectRouteDefinitions)(TestGateway);
|
|
114
|
+
expect(routes[0].trafficPolicy).toBeDefined();
|
|
115
|
+
expect(routes[0].trafficPolicy.mirror?.service).toBe('search-v2');
|
|
116
|
+
expect(routes[0].trafficPolicy.mirror?.percentage).toBe(10);
|
|
117
|
+
expect(routes[0].trafficPolicy.timeout).toBe(3000);
|
|
118
|
+
});
|
|
119
|
+
it('should collect circuit breaker and rate limit overrides', () => {
|
|
120
|
+
let TestGateway = class TestGateway {
|
|
121
|
+
};
|
|
122
|
+
__decorate([
|
|
123
|
+
(0, decorators_1.Route)('/api/critical/**'),
|
|
124
|
+
(0, decorators_1.ServiceRoute)('critical-service'),
|
|
125
|
+
(0, decorators_1.GatewayCircuitBreaker)({ failureThreshold: 3 }),
|
|
126
|
+
(0, decorators_1.GatewayRateLimit)({ strategy: 'sliding-window', max: 50, window: 60000 }),
|
|
127
|
+
__metadata("design:type", Object)
|
|
128
|
+
], TestGateway.prototype, "criticalService", void 0);
|
|
129
|
+
TestGateway = __decorate([
|
|
130
|
+
(0, decorators_1.Gateway)({})
|
|
131
|
+
], TestGateway);
|
|
132
|
+
const { routes } = (0, decorators_1.collectRouteDefinitions)(TestGateway);
|
|
133
|
+
expect(routes[0].circuitBreaker?.failureThreshold).toBe(3);
|
|
134
|
+
expect(routes[0].rateLimit?.max).toBe(50);
|
|
135
|
+
});
|
|
136
|
+
it('should handle multiple routes', () => {
|
|
137
|
+
let TestGateway = class TestGateway {
|
|
138
|
+
};
|
|
139
|
+
__decorate([
|
|
140
|
+
(0, decorators_1.Route)('/api/a/**'),
|
|
141
|
+
(0, decorators_1.ServiceRoute)('service-a'),
|
|
142
|
+
__metadata("design:type", Object)
|
|
143
|
+
], TestGateway.prototype, "serviceA", void 0);
|
|
144
|
+
__decorate([
|
|
145
|
+
(0, decorators_1.Route)('/api/b/**'),
|
|
146
|
+
(0, decorators_1.ServiceRoute)('service-b'),
|
|
147
|
+
__metadata("design:type", Object)
|
|
148
|
+
], TestGateway.prototype, "serviceB", void 0);
|
|
149
|
+
__decorate([
|
|
150
|
+
(0, decorators_1.Route)('/api/c/**'),
|
|
151
|
+
(0, decorators_1.ServiceRoute)('service-c'),
|
|
152
|
+
__metadata("design:type", Object)
|
|
153
|
+
], TestGateway.prototype, "serviceC", void 0);
|
|
154
|
+
TestGateway = __decorate([
|
|
155
|
+
(0, decorators_1.Gateway)({})
|
|
156
|
+
], TestGateway);
|
|
157
|
+
const { routes } = (0, decorators_1.collectRouteDefinitions)(TestGateway);
|
|
158
|
+
expect(routes).toHaveLength(3);
|
|
159
|
+
});
|
|
160
|
+
it('should accept string shorthand for ServiceRoute', () => {
|
|
161
|
+
let TestGateway = class TestGateway {
|
|
162
|
+
};
|
|
163
|
+
__decorate([
|
|
164
|
+
(0, decorators_1.Route)('/api/simple/**'),
|
|
165
|
+
(0, decorators_1.ServiceRoute)('simple-service'),
|
|
166
|
+
__metadata("design:type", Object)
|
|
167
|
+
], TestGateway.prototype, "simpleService", void 0);
|
|
168
|
+
TestGateway = __decorate([
|
|
169
|
+
(0, decorators_1.Gateway)({})
|
|
170
|
+
], TestGateway);
|
|
171
|
+
const { routes } = (0, decorators_1.collectRouteDefinitions)(TestGateway);
|
|
172
|
+
expect(routes[0].serviceRoute?.serviceName).toBe('simple-service');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"from-config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/from-config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const gateway_1 = require("../gateway");
|
|
4
|
+
describe('GatewayServer.fromConfig', () => {
|
|
5
|
+
const config = {
|
|
6
|
+
discovery: { cacheEnabled: false },
|
|
7
|
+
resilience: {
|
|
8
|
+
defaultTimeout: 5000,
|
|
9
|
+
defaultCircuitBreaker: { failureThreshold: 5, resetTimeout: 30000 },
|
|
10
|
+
},
|
|
11
|
+
routes: [
|
|
12
|
+
{
|
|
13
|
+
path: '/api/users/**',
|
|
14
|
+
serviceName: 'user-service',
|
|
15
|
+
serviceConfig: {
|
|
16
|
+
serviceName: 'user-service',
|
|
17
|
+
stripPrefix: '/api/users',
|
|
18
|
+
addPrefix: '/users',
|
|
19
|
+
},
|
|
20
|
+
circuitBreaker: { failureThreshold: 10 },
|
|
21
|
+
rateLimit: { strategy: 'sliding-window', max: 100, window: 60000 },
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
path: '/api/orders/**',
|
|
25
|
+
serviceName: 'order-service',
|
|
26
|
+
canary: {
|
|
27
|
+
stable: { version: 'v1', weight: 90 },
|
|
28
|
+
canary: { version: 'v2', weight: 10 },
|
|
29
|
+
promotion: {
|
|
30
|
+
strategy: 'error-rate',
|
|
31
|
+
errorThreshold: 5,
|
|
32
|
+
evaluationWindow: '5m',
|
|
33
|
+
autoPromote: true,
|
|
34
|
+
autoRollback: true,
|
|
35
|
+
steps: [10, 25, 50, 75, 100],
|
|
36
|
+
stepInterval: '10m',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
it('should create a gateway with all routes from config', () => {
|
|
43
|
+
const gateway = gateway_1.GatewayServer.fromConfig(config);
|
|
44
|
+
const routes = gateway.getRoutes();
|
|
45
|
+
expect(routes).toContain('/api/users/**');
|
|
46
|
+
expect(routes).toContain('/api/orders/**');
|
|
47
|
+
expect(routes).toHaveLength(2);
|
|
48
|
+
});
|
|
49
|
+
it('should set up canary engine for routes with canary config', () => {
|
|
50
|
+
const gateway = gateway_1.GatewayServer.fromConfig(config);
|
|
51
|
+
const canaryEngine = gateway.getCanaryEngine('/api/orders/**');
|
|
52
|
+
expect(canaryEngine).toBeDefined();
|
|
53
|
+
// No canary for users route
|
|
54
|
+
const noCanary = gateway.getCanaryEngine('/api/users/**');
|
|
55
|
+
expect(noCanary).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
it('should create a working gateway with empty routes', () => {
|
|
58
|
+
const emptyConfig = {
|
|
59
|
+
routes: [],
|
|
60
|
+
};
|
|
61
|
+
const gateway = gateway_1.GatewayServer.fromConfig(emptyConfig);
|
|
62
|
+
expect(gateway.getRoutes()).toHaveLength(0);
|
|
63
|
+
});
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
// Clean up to avoid state leaks between tests
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-metrics.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/gateway-metrics.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const gateway_metrics_1 = require("../metrics/gateway-metrics");
|
|
4
|
+
describe('GatewayMetrics', () => {
|
|
5
|
+
let metrics;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
metrics = new gateway_metrics_1.GatewayMetrics(60000);
|
|
8
|
+
});
|
|
9
|
+
describe('recordSuccess / recordFailure', () => {
|
|
10
|
+
it('should record success for a route', () => {
|
|
11
|
+
metrics.recordSuccess('/api/users', 50);
|
|
12
|
+
metrics.recordSuccess('/api/users', 100);
|
|
13
|
+
const snapshot = metrics.getRouteMetrics('/api/users');
|
|
14
|
+
expect(snapshot).toBeDefined();
|
|
15
|
+
expect(snapshot.totalCalls).toBe(2);
|
|
16
|
+
expect(snapshot.successCalls).toBe(2);
|
|
17
|
+
expect(snapshot.failureCalls).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
it('should record failure for a route', () => {
|
|
20
|
+
metrics.recordFailure('/api/orders', 30, 'Timeout');
|
|
21
|
+
metrics.recordFailure('/api/orders', 50, '502');
|
|
22
|
+
const snapshot = metrics.getRouteMetrics('/api/orders');
|
|
23
|
+
expect(snapshot).toBeDefined();
|
|
24
|
+
expect(snapshot.totalCalls).toBe(2);
|
|
25
|
+
expect(snapshot.failureCalls).toBe(2);
|
|
26
|
+
});
|
|
27
|
+
it('should record success with version', () => {
|
|
28
|
+
metrics.recordSuccess('/api/users', 50, 'v1');
|
|
29
|
+
metrics.recordSuccess('/api/users', 80, 'v2');
|
|
30
|
+
const v1 = metrics.getVersionMetrics('/api/users', 'v1');
|
|
31
|
+
const v2 = metrics.getVersionMetrics('/api/users', 'v2');
|
|
32
|
+
expect(v1?.successCalls).toBe(1);
|
|
33
|
+
expect(v2?.successCalls).toBe(1);
|
|
34
|
+
});
|
|
35
|
+
it('should record failure with version', () => {
|
|
36
|
+
metrics.recordFailure('/api/users', 100, 'Error', 'v1');
|
|
37
|
+
const v1 = metrics.getVersionMetrics('/api/users', 'v1');
|
|
38
|
+
expect(v1?.failureCalls).toBe(1);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('getVersionErrorRate', () => {
|
|
42
|
+
it('should return 0 for non-existent version', () => {
|
|
43
|
+
expect(metrics.getVersionErrorRate('/api/x', 'v1')).toBe(0);
|
|
44
|
+
});
|
|
45
|
+
it('should return error rate for version with failures', () => {
|
|
46
|
+
metrics.recordSuccess('/api/x', 10, 'v1');
|
|
47
|
+
metrics.recordFailure('/api/x', 10, 'err', 'v1');
|
|
48
|
+
const rate = metrics.getVersionErrorRate('/api/x', 'v1');
|
|
49
|
+
expect(rate).toBe(50);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('getSnapshot', () => {
|
|
53
|
+
it('should return full snapshot with aggregated metrics', () => {
|
|
54
|
+
metrics.recordSuccess('/a', 50);
|
|
55
|
+
metrics.recordSuccess('/b', 100);
|
|
56
|
+
const snapshot = metrics.getSnapshot();
|
|
57
|
+
expect(snapshot.timestamp).toBeDefined();
|
|
58
|
+
expect(snapshot.totalRoutes).toBe(2);
|
|
59
|
+
expect(snapshot.routes).toHaveLength(2);
|
|
60
|
+
expect(snapshot.aggregated.totalCalls).toBe(2);
|
|
61
|
+
expect(snapshot.aggregated.successCalls).toBe(2);
|
|
62
|
+
});
|
|
63
|
+
it('should return empty aggregated when no routes', () => {
|
|
64
|
+
const snapshot = metrics.getSnapshot();
|
|
65
|
+
expect(snapshot.totalRoutes).toBe(0);
|
|
66
|
+
expect(snapshot.routes).toHaveLength(0);
|
|
67
|
+
expect(snapshot.aggregated.totalCalls).toBe(0);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('reset', () => {
|
|
71
|
+
it('should reset all metrics to zero', () => {
|
|
72
|
+
metrics.recordSuccess('/a', 50);
|
|
73
|
+
metrics.recordSuccess('/a', 100, 'v1');
|
|
74
|
+
metrics.reset();
|
|
75
|
+
const routeSnapshot = metrics.getRouteMetrics('/a');
|
|
76
|
+
const versionSnapshot = metrics.getVersionMetrics('/a', 'v1');
|
|
77
|
+
expect(routeSnapshot?.totalCalls).toBe(0);
|
|
78
|
+
expect(routeSnapshot?.successCalls).toBe(0);
|
|
79
|
+
expect(versionSnapshot?.totalCalls).toBe(0);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-module.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/gateway-module.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const gateway_module_1 = require("../gateway.module");
|
|
4
|
+
describe('GatewayModule', () => {
|
|
5
|
+
const sampleConfig = {
|
|
6
|
+
discovery: { cacheEnabled: true },
|
|
7
|
+
routes: [
|
|
8
|
+
{
|
|
9
|
+
path: '/api/users/**',
|
|
10
|
+
serviceName: 'user-service',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
path: '/api/orders/**',
|
|
14
|
+
serviceName: 'order-service',
|
|
15
|
+
canary: {
|
|
16
|
+
stable: { version: 'v1', weight: 90 },
|
|
17
|
+
canary: { version: 'v2', weight: 10 },
|
|
18
|
+
promotion: {
|
|
19
|
+
strategy: 'error-rate',
|
|
20
|
+
errorThreshold: 5,
|
|
21
|
+
evaluationWindow: '5m',
|
|
22
|
+
autoPromote: true,
|
|
23
|
+
autoRollback: true,
|
|
24
|
+
steps: [10, 25, 50, 75, 100],
|
|
25
|
+
stepInterval: '10m',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
describe('forRoot', () => {
|
|
32
|
+
it('should store options with default configKey', () => {
|
|
33
|
+
gateway_module_1.GatewayModule.forRoot({});
|
|
34
|
+
const opts = gateway_module_1.GatewayModule.getOptions();
|
|
35
|
+
expect(opts.configKey).toBe('gateway');
|
|
36
|
+
});
|
|
37
|
+
it('should allow custom configKey', () => {
|
|
38
|
+
gateway_module_1.GatewayModule.forRoot({ configKey: 'myGateway' });
|
|
39
|
+
const opts = gateway_module_1.GatewayModule.getOptions();
|
|
40
|
+
expect(opts.configKey).toBe('myGateway');
|
|
41
|
+
});
|
|
42
|
+
it('should accept direct config', () => {
|
|
43
|
+
gateway_module_1.GatewayModule.forRoot({ config: sampleConfig });
|
|
44
|
+
const opts = gateway_module_1.GatewayModule.getOptions();
|
|
45
|
+
expect(opts.config).toBe(sampleConfig);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('resolveConfig', () => {
|
|
49
|
+
it('should return direct config when provided', () => {
|
|
50
|
+
gateway_module_1.GatewayModule.forRoot({ config: sampleConfig });
|
|
51
|
+
const resolved = gateway_module_1.GatewayModule.resolveConfig();
|
|
52
|
+
expect(resolved).toBe(sampleConfig);
|
|
53
|
+
expect(resolved.routes).toHaveLength(2);
|
|
54
|
+
});
|
|
55
|
+
it('should read from configService when no direct config', () => {
|
|
56
|
+
gateway_module_1.GatewayModule.forRoot({ configKey: 'gateway' });
|
|
57
|
+
const mockConfigService = {
|
|
58
|
+
get: (key) => {
|
|
59
|
+
if (key === 'gateway')
|
|
60
|
+
return sampleConfig;
|
|
61
|
+
return undefined;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const resolved = gateway_module_1.GatewayModule.resolveConfig(mockConfigService);
|
|
65
|
+
expect(resolved.routes).toHaveLength(2);
|
|
66
|
+
expect(resolved.routes[0].serviceName).toBe('user-service');
|
|
67
|
+
});
|
|
68
|
+
it('should throw when no config and no configService', () => {
|
|
69
|
+
gateway_module_1.GatewayModule.forRoot({ configKey: 'gateway' });
|
|
70
|
+
expect(() => gateway_module_1.GatewayModule.resolveConfig()).toThrow('No config provided');
|
|
71
|
+
});
|
|
72
|
+
it('should throw when configService returns undefined', () => {
|
|
73
|
+
gateway_module_1.GatewayModule.forRoot({ configKey: 'missing' });
|
|
74
|
+
const mockConfigService = {
|
|
75
|
+
get: (_key) => undefined,
|
|
76
|
+
};
|
|
77
|
+
expect(() => gateway_module_1.GatewayModule.resolveConfig(mockConfigService)).toThrow('No configuration found at key "missing"');
|
|
78
|
+
});
|
|
79
|
+
it('should throw when config has no routes array', () => {
|
|
80
|
+
gateway_module_1.GatewayModule.forRoot({ configKey: 'gateway' });
|
|
81
|
+
const mockConfigService = {
|
|
82
|
+
get: (key) => {
|
|
83
|
+
if (key === 'gateway')
|
|
84
|
+
return { discovery: {} };
|
|
85
|
+
return undefined;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
expect(() => gateway_module_1.GatewayModule.resolveConfig(mockConfigService)).toThrow('missing a "routes" array');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/gateway.test.ts"],"names":[],"mappings":""}
|