@hazeljs/discovery 0.2.0-alpha.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 (78) hide show
  1. package/LICENSE +192 -0
  2. package/README.md +450 -0
  3. package/dist/__tests__/consul-backend.test.d.ts +6 -0
  4. package/dist/__tests__/consul-backend.test.d.ts.map +1 -0
  5. package/dist/__tests__/consul-backend.test.js +300 -0
  6. package/dist/__tests__/decorators.test.d.ts +5 -0
  7. package/dist/__tests__/decorators.test.d.ts.map +1 -0
  8. package/dist/__tests__/decorators.test.js +72 -0
  9. package/dist/__tests__/discovery-client.test.d.ts +5 -0
  10. package/dist/__tests__/discovery-client.test.d.ts.map +1 -0
  11. package/dist/__tests__/discovery-client.test.js +142 -0
  12. package/dist/__tests__/kubernetes-backend.test.d.ts +6 -0
  13. package/dist/__tests__/kubernetes-backend.test.d.ts.map +1 -0
  14. package/dist/__tests__/kubernetes-backend.test.js +261 -0
  15. package/dist/__tests__/load-balancer-strategies.test.d.ts +5 -0
  16. package/dist/__tests__/load-balancer-strategies.test.d.ts.map +1 -0
  17. package/dist/__tests__/load-balancer-strategies.test.js +234 -0
  18. package/dist/__tests__/memory-backend.test.d.ts +5 -0
  19. package/dist/__tests__/memory-backend.test.d.ts.map +1 -0
  20. package/dist/__tests__/memory-backend.test.js +246 -0
  21. package/dist/__tests__/redis-backend.test.d.ts +6 -0
  22. package/dist/__tests__/redis-backend.test.d.ts.map +1 -0
  23. package/dist/__tests__/redis-backend.test.js +280 -0
  24. package/dist/__tests__/service-client.test.d.ts +5 -0
  25. package/dist/__tests__/service-client.test.d.ts.map +1 -0
  26. package/dist/__tests__/service-client.test.js +216 -0
  27. package/dist/__tests__/service-registry.test.d.ts +5 -0
  28. package/dist/__tests__/service-registry.test.d.ts.map +1 -0
  29. package/dist/__tests__/service-registry.test.js +65 -0
  30. package/dist/backends/consul-backend.d.ts +115 -0
  31. package/dist/backends/consul-backend.d.ts.map +1 -0
  32. package/dist/backends/consul-backend.js +259 -0
  33. package/dist/backends/kubernetes-backend.d.ts +103 -0
  34. package/dist/backends/kubernetes-backend.d.ts.map +1 -0
  35. package/dist/backends/kubernetes-backend.js +153 -0
  36. package/dist/backends/memory-backend.d.ts +21 -0
  37. package/dist/backends/memory-backend.d.ts.map +1 -0
  38. package/dist/backends/memory-backend.js +86 -0
  39. package/dist/backends/redis-backend.d.ts +76 -0
  40. package/dist/backends/redis-backend.d.ts.map +1 -0
  41. package/dist/backends/redis-backend.js +220 -0
  42. package/dist/backends/registry-backend.d.ts +39 -0
  43. package/dist/backends/registry-backend.d.ts.map +1 -0
  44. package/dist/backends/registry-backend.js +5 -0
  45. package/dist/client/discovery-client.d.ts +49 -0
  46. package/dist/client/discovery-client.d.ts.map +1 -0
  47. package/dist/client/discovery-client.js +123 -0
  48. package/dist/client/service-client.d.ts +48 -0
  49. package/dist/client/service-client.d.ts.map +1 -0
  50. package/dist/client/service-client.js +155 -0
  51. package/dist/decorators/inject-service-client.decorator.d.ts +16 -0
  52. package/dist/decorators/inject-service-client.decorator.d.ts.map +1 -0
  53. package/dist/decorators/inject-service-client.decorator.js +24 -0
  54. package/dist/decorators/service-registry.decorator.d.ts +11 -0
  55. package/dist/decorators/service-registry.decorator.d.ts.map +1 -0
  56. package/dist/decorators/service-registry.decorator.js +20 -0
  57. package/dist/index.d.ts +21 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +51 -0
  60. package/dist/load-balancer/strategies.d.ts +82 -0
  61. package/dist/load-balancer/strategies.d.ts.map +1 -0
  62. package/dist/load-balancer/strategies.js +209 -0
  63. package/dist/registry/service-registry.d.ts +51 -0
  64. package/dist/registry/service-registry.d.ts.map +1 -0
  65. package/dist/registry/service-registry.js +159 -0
  66. package/dist/types/index.d.ts +61 -0
  67. package/dist/types/index.d.ts.map +1 -0
  68. package/dist/types/index.js +14 -0
  69. package/dist/utils/filter.d.ts +10 -0
  70. package/dist/utils/filter.d.ts.map +1 -0
  71. package/dist/utils/filter.js +39 -0
  72. package/dist/utils/logger.d.ts +21 -0
  73. package/dist/utils/logger.d.ts.map +1 -0
  74. package/dist/utils/logger.js +34 -0
  75. package/dist/utils/validation.d.ts +36 -0
  76. package/dist/utils/validation.d.ts.map +1 -0
  77. package/dist/utils/validation.js +109 -0
  78. package/package.json +81 -0
@@ -0,0 +1,300 @@
1
+ "use strict";
2
+ /**
3
+ * Consul Backend Tests
4
+ * Uses mocked Consul client
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const consul_backend_1 = require("../backends/consul-backend");
8
+ const types_1 = require("../types");
9
+ const logger_1 = require("../utils/logger");
10
+ // Suppress console logs during tests
11
+ beforeAll(() => {
12
+ logger_1.DiscoveryLogger.setLogger({
13
+ debug: jest.fn(),
14
+ info: jest.fn(),
15
+ warn: jest.fn(),
16
+ error: jest.fn(),
17
+ });
18
+ });
19
+ afterAll(() => {
20
+ logger_1.DiscoveryLogger.resetLogger();
21
+ });
22
+ function createMockConsul() {
23
+ return {
24
+ agent: {
25
+ service: {
26
+ register: jest.fn().mockResolvedValue(undefined),
27
+ deregister: jest.fn().mockResolvedValue(undefined),
28
+ list: jest.fn().mockResolvedValue({}),
29
+ },
30
+ check: {
31
+ pass: jest.fn().mockResolvedValue(undefined),
32
+ fail: jest.fn().mockResolvedValue(undefined),
33
+ warn: jest.fn().mockResolvedValue(undefined),
34
+ list: jest.fn().mockResolvedValue({}),
35
+ },
36
+ },
37
+ health: {
38
+ service: jest.fn().mockResolvedValue([]),
39
+ },
40
+ catalog: {
41
+ service: {
42
+ list: jest.fn().mockResolvedValue({}),
43
+ },
44
+ },
45
+ };
46
+ }
47
+ function createInstance(id, name = 'test-service', overrides = {}) {
48
+ return {
49
+ id,
50
+ name,
51
+ host: 'localhost',
52
+ port: 3000,
53
+ status: types_1.ServiceStatus.UP,
54
+ lastHeartbeat: new Date('2025-01-01T00:00:00Z'),
55
+ registeredAt: new Date('2025-01-01T00:00:00Z'),
56
+ tags: ['web'],
57
+ metadata: { version: '1.0' },
58
+ zone: 'us-east-1',
59
+ ...overrides,
60
+ };
61
+ }
62
+ describe('ConsulRegistryBackend', () => {
63
+ let consul;
64
+ let backend;
65
+ beforeEach(() => {
66
+ jest.useFakeTimers();
67
+ consul = createMockConsul();
68
+ backend = new consul_backend_1.ConsulRegistryBackend(consul);
69
+ });
70
+ afterEach(async () => {
71
+ await backend.close();
72
+ jest.useRealTimers();
73
+ });
74
+ describe('constructor', () => {
75
+ it('should use default TTL of 30s', () => {
76
+ expect(backend).toBeDefined();
77
+ });
78
+ it('should accept custom config', () => {
79
+ const b = new consul_backend_1.ConsulRegistryBackend(consul, { ttl: '60s' });
80
+ expect(b).toBeDefined();
81
+ });
82
+ it('should throw on invalid TTL format', () => {
83
+ expect(() => {
84
+ new consul_backend_1.ConsulRegistryBackend(consul, { ttl: 'invalid' });
85
+ }).toThrow();
86
+ });
87
+ });
88
+ describe('register', () => {
89
+ it('should call consul agent service register', async () => {
90
+ const instance = createInstance('svc-1', 'my-service');
91
+ await backend.register(instance);
92
+ expect(consul.agent.service.register).toHaveBeenCalledWith(expect.objectContaining({
93
+ id: 'svc-1',
94
+ name: 'my-service',
95
+ address: 'localhost',
96
+ port: 3000,
97
+ }));
98
+ });
99
+ it('should start TTL check interval', async () => {
100
+ const instance = createInstance('svc-1');
101
+ await backend.register(instance);
102
+ // Advance timers past the TTL check interval (2/3 of 30s = 20s)
103
+ jest.advanceTimersByTime(21000);
104
+ expect(consul.agent.check.pass).toHaveBeenCalledWith('service:svc-1');
105
+ });
106
+ });
107
+ describe('deregister', () => {
108
+ it('should call consul agent service deregister', async () => {
109
+ const instance = createInstance('svc-1');
110
+ await backend.register(instance);
111
+ await backend.deregister('svc-1');
112
+ expect(consul.agent.service.deregister).toHaveBeenCalledWith('svc-1');
113
+ });
114
+ it('should stop TTL check interval', async () => {
115
+ const instance = createInstance('svc-1');
116
+ await backend.register(instance);
117
+ await backend.deregister('svc-1');
118
+ jest.clearAllMocks();
119
+ jest.advanceTimersByTime(30000);
120
+ // TTL check should NOT have been called after deregister
121
+ expect(consul.agent.check.pass).not.toHaveBeenCalled();
122
+ });
123
+ });
124
+ describe('heartbeat', () => {
125
+ it('should pass the TTL check', async () => {
126
+ await backend.heartbeat('svc-1');
127
+ expect(consul.agent.check.pass).toHaveBeenCalledWith('service:svc-1');
128
+ });
129
+ it('should not throw when consul fails', async () => {
130
+ consul.agent.check.pass.mockRejectedValueOnce(new Error('Consul error'));
131
+ await expect(backend.heartbeat('svc-1')).resolves.not.toThrow();
132
+ });
133
+ });
134
+ describe('getInstances', () => {
135
+ it('should return instances from consul health service', async () => {
136
+ consul.health.service.mockResolvedValueOnce([
137
+ {
138
+ Service: {
139
+ ID: 'svc-1',
140
+ Service: 'my-service',
141
+ Address: '10.0.0.1',
142
+ Port: 8080,
143
+ Meta: { zone: 'us-east-1', registeredAt: '2025-01-01T00:00:00Z' },
144
+ Tags: ['web'],
145
+ },
146
+ Checks: [{ Status: 'passing' }],
147
+ },
148
+ {
149
+ Service: {
150
+ ID: 'svc-2',
151
+ Service: 'my-service',
152
+ Address: '10.0.0.2',
153
+ Port: 8080,
154
+ Meta: { zone: 'us-west-1' },
155
+ Tags: ['api'],
156
+ },
157
+ Checks: [{ Status: 'passing' }],
158
+ },
159
+ ]);
160
+ const instances = await backend.getInstances('my-service');
161
+ expect(instances).toHaveLength(2);
162
+ expect(instances[0].id).toBe('svc-1');
163
+ expect(instances[0].host).toBe('10.0.0.1');
164
+ });
165
+ it('should set status based on check results', async () => {
166
+ consul.health.service.mockResolvedValueOnce([
167
+ {
168
+ Service: {
169
+ ID: 'svc-1',
170
+ Service: 'my-service',
171
+ Address: '10.0.0.1',
172
+ Port: 8080,
173
+ },
174
+ Checks: [{ Status: 'critical' }],
175
+ },
176
+ {
177
+ Service: {
178
+ ID: 'svc-2',
179
+ Service: 'my-service',
180
+ Address: '10.0.0.2',
181
+ Port: 8080,
182
+ },
183
+ Checks: [{ Status: 'warning' }],
184
+ },
185
+ ]);
186
+ const instances = await backend.getInstances('my-service');
187
+ expect(instances[0].status).toBe(types_1.ServiceStatus.DOWN);
188
+ expect(instances[1].status).toBe(types_1.ServiceStatus.STARTING);
189
+ });
190
+ it('should filter instances', async () => {
191
+ consul.health.service.mockResolvedValueOnce([
192
+ {
193
+ Service: {
194
+ ID: 'svc-1',
195
+ Service: 'my-service',
196
+ Address: '10.0.0.1',
197
+ Port: 8080,
198
+ Meta: { zone: 'us-east-1' },
199
+ },
200
+ Checks: [],
201
+ },
202
+ {
203
+ Service: {
204
+ ID: 'svc-2',
205
+ Service: 'my-service',
206
+ Address: '10.0.0.2',
207
+ Port: 8080,
208
+ Meta: { zone: 'us-west-1' },
209
+ },
210
+ Checks: [],
211
+ },
212
+ ]);
213
+ const instances = await backend.getInstances('my-service', { zone: 'us-east-1' });
214
+ expect(instances).toHaveLength(1);
215
+ expect(instances[0].id).toBe('svc-1');
216
+ });
217
+ it('should return empty array on error', async () => {
218
+ consul.health.service.mockRejectedValueOnce(new Error('Consul error'));
219
+ const instances = await backend.getInstances('my-service');
220
+ expect(instances).toEqual([]);
221
+ });
222
+ });
223
+ describe('getInstance', () => {
224
+ it('should return a specific instance', async () => {
225
+ consul.agent.service.list.mockResolvedValueOnce({
226
+ 'svc-1': {
227
+ ID: 'svc-1',
228
+ Service: 'my-service',
229
+ Address: '10.0.0.1',
230
+ Port: 8080,
231
+ Meta: { zone: 'us-east-1' },
232
+ Tags: ['web'],
233
+ },
234
+ });
235
+ consul.agent.check.list.mockResolvedValueOnce({
236
+ 'service:svc-1': { Status: 'passing' },
237
+ });
238
+ const instance = await backend.getInstance('svc-1');
239
+ expect(instance).toBeDefined();
240
+ expect(instance.id).toBe('svc-1');
241
+ expect(instance.status).toBe(types_1.ServiceStatus.UP);
242
+ });
243
+ it('should return null for non-existent instance', async () => {
244
+ consul.agent.service.list.mockResolvedValueOnce({});
245
+ const instance = await backend.getInstance('non-existent');
246
+ expect(instance).toBeNull();
247
+ });
248
+ it('should return null on error', async () => {
249
+ consul.agent.service.list.mockRejectedValueOnce(new Error('fail'));
250
+ const instance = await backend.getInstance('svc-1');
251
+ expect(instance).toBeNull();
252
+ });
253
+ });
254
+ describe('getAllServices', () => {
255
+ it('should return service names from catalog', async () => {
256
+ consul.catalog.service.list.mockResolvedValueOnce({
257
+ 'service-a': [],
258
+ 'service-b': [],
259
+ consul: [],
260
+ });
261
+ const services = await backend.getAllServices();
262
+ expect(services).toEqual(['service-a', 'service-b', 'consul']);
263
+ });
264
+ it('should return empty array on error', async () => {
265
+ consul.catalog.service.list.mockRejectedValueOnce(new Error('fail'));
266
+ const services = await backend.getAllServices();
267
+ expect(services).toEqual([]);
268
+ });
269
+ });
270
+ describe('updateStatus', () => {
271
+ it('should pass check for UP status', async () => {
272
+ await backend.updateStatus('svc-1', types_1.ServiceStatus.UP);
273
+ expect(consul.agent.check.pass).toHaveBeenCalledWith('service:svc-1');
274
+ });
275
+ it('should fail check for DOWN status', async () => {
276
+ await backend.updateStatus('svc-1', types_1.ServiceStatus.DOWN);
277
+ expect(consul.agent.check.fail).toHaveBeenCalledWith('service:svc-1');
278
+ });
279
+ it('should warn check for STARTING status', async () => {
280
+ await backend.updateStatus('svc-1', types_1.ServiceStatus.STARTING);
281
+ expect(consul.agent.check.warn).toHaveBeenCalledWith('service:svc-1');
282
+ });
283
+ it('should not throw on error', async () => {
284
+ consul.agent.check.pass.mockRejectedValueOnce(new Error('fail'));
285
+ await expect(backend.updateStatus('svc-1', types_1.ServiceStatus.UP)).resolves.not.toThrow();
286
+ });
287
+ });
288
+ describe('close', () => {
289
+ it('should stop all TTL check intervals', async () => {
290
+ const i1 = createInstance('svc-1');
291
+ const i2 = createInstance('svc-2');
292
+ await backend.register(i1);
293
+ await backend.register(i2);
294
+ await backend.close();
295
+ jest.clearAllMocks();
296
+ jest.advanceTimersByTime(60000);
297
+ expect(consul.agent.check.pass).not.toHaveBeenCalled();
298
+ });
299
+ });
300
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Decorators Tests
3
+ */
4
+ import 'reflect-metadata';
5
+ //# sourceMappingURL=decorators.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,kBAAkB,CAAC"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ /**
3
+ * Decorators Tests
4
+ */
5
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
6
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
7
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
8
+ 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;
9
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
10
+ };
11
+ var __metadata = (this && this.__metadata) || function (k, v) {
12
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
13
+ };
14
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
15
+ return function (target, key) { decorator(target, key, paramIndex); }
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ require("reflect-metadata");
19
+ const service_registry_decorator_1 = require("../decorators/service-registry.decorator");
20
+ const inject_service_client_decorator_1 = require("../decorators/inject-service-client.decorator");
21
+ describe('Decorators', () => {
22
+ describe('ServiceRegistry', () => {
23
+ it('should attach metadata to class', () => {
24
+ const config = {
25
+ name: 'test-service',
26
+ port: 3000,
27
+ zone: 'us-east-1',
28
+ };
29
+ let TestService = class TestService {
30
+ };
31
+ TestService = __decorate([
32
+ (0, service_registry_decorator_1.ServiceRegistry)(config)
33
+ ], TestService);
34
+ const metadata = (0, service_registry_decorator_1.getServiceRegistryMetadata)(TestService);
35
+ expect(metadata).toEqual(config);
36
+ });
37
+ it('should return undefined for class without decorator', () => {
38
+ class TestService {
39
+ }
40
+ const metadata = (0, service_registry_decorator_1.getServiceRegistryMetadata)(TestService);
41
+ expect(metadata).toBeUndefined();
42
+ });
43
+ });
44
+ describe('InjectServiceClient', () => {
45
+ it('should attach metadata to parameter', () => {
46
+ let TestService = class TestService {
47
+ constructor(_userClient, _orderClient) { }
48
+ };
49
+ TestService = __decorate([
50
+ __param(0, (0, inject_service_client_decorator_1.InjectServiceClient)('user-service')),
51
+ __param(1, (0, inject_service_client_decorator_1.InjectServiceClient)('order-service', { timeout: 5000 })),
52
+ __metadata("design:paramtypes", [Object, Object])
53
+ ], TestService);
54
+ const metadata = (0, inject_service_client_decorator_1.getInjectServiceClientMetadata)(TestService);
55
+ expect(metadata).toBeDefined();
56
+ expect(metadata?.[0]).toEqual({
57
+ serviceName: 'user-service',
58
+ });
59
+ expect(metadata?.[1]).toEqual({
60
+ serviceName: 'order-service',
61
+ timeout: 5000,
62
+ });
63
+ });
64
+ it('should return undefined for class without decorator', () => {
65
+ class TestService {
66
+ constructor(_client) { }
67
+ }
68
+ const metadata = (0, inject_service_client_decorator_1.getInjectServiceClientMetadata)(TestService);
69
+ expect(metadata).toBeUndefined();
70
+ });
71
+ });
72
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Discovery Client Tests
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=discovery-client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/discovery-client.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ /**
3
+ * Discovery Client Tests
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const discovery_client_1 = require("../client/discovery-client");
7
+ const memory_backend_1 = require("../backends/memory-backend");
8
+ const types_1 = require("../types");
9
+ describe('DiscoveryClient', () => {
10
+ let client;
11
+ let backend;
12
+ beforeEach(() => {
13
+ backend = new memory_backend_1.MemoryRegistryBackend();
14
+ client = new discovery_client_1.DiscoveryClient({}, backend);
15
+ });
16
+ const createInstance = (id, name = 'test-service') => ({
17
+ id,
18
+ name,
19
+ host: 'localhost',
20
+ port: 3000,
21
+ status: types_1.ServiceStatus.UP,
22
+ lastHeartbeat: new Date(),
23
+ registeredAt: new Date(),
24
+ });
25
+ describe('getInstances', () => {
26
+ it('should return instances from backend', async () => {
27
+ const instance1 = createInstance('1', 'service-a');
28
+ const instance2 = createInstance('2', 'service-a');
29
+ await backend.register(instance1);
30
+ await backend.register(instance2);
31
+ const instances = await client.getInstances('service-a');
32
+ expect(instances).toHaveLength(2);
33
+ });
34
+ it('should apply filters', async () => {
35
+ const instance1 = { ...createInstance('1'), zone: 'us-east-1' };
36
+ const instance2 = { ...createInstance('2'), zone: 'us-west-1' };
37
+ await backend.register(instance1);
38
+ await backend.register(instance2);
39
+ const instances = await client.getInstances('test-service', {
40
+ zone: 'us-east-1',
41
+ });
42
+ expect(instances).toHaveLength(1);
43
+ expect(instances[0].id).toBe('1');
44
+ });
45
+ it('should use cache when enabled', async () => {
46
+ const clientWithCache = new discovery_client_1.DiscoveryClient({ cacheEnabled: true, cacheTTL: 1000 }, backend);
47
+ const instance = createInstance('1');
48
+ await backend.register(instance);
49
+ const first = await clientWithCache.getInstances('test-service');
50
+ expect(first).toHaveLength(1);
51
+ // Remove from backend
52
+ await backend.deregister('1');
53
+ // Should still return cached result
54
+ const second = await clientWithCache.getInstances('test-service');
55
+ expect(second).toHaveLength(1);
56
+ });
57
+ it('should refresh cache after TTL expires', async () => {
58
+ const clientWithCache = new discovery_client_1.DiscoveryClient({ cacheEnabled: true, cacheTTL: 50 }, backend);
59
+ const instance = createInstance('1');
60
+ await backend.register(instance);
61
+ await clientWithCache.getInstances('test-service');
62
+ await backend.deregister('1');
63
+ // Wait for cache to expire
64
+ await new Promise((resolve) => setTimeout(resolve, 60));
65
+ const instances = await clientWithCache.getInstances('test-service');
66
+ expect(instances).toHaveLength(0);
67
+ });
68
+ });
69
+ describe('getInstance', () => {
70
+ it('should return a single instance using load balancing', async () => {
71
+ const instance1 = createInstance('1');
72
+ const instance2 = createInstance('2');
73
+ await backend.register(instance1);
74
+ await backend.register(instance2);
75
+ const instance = await client.getInstance('test-service');
76
+ expect(instance).toBeDefined();
77
+ expect(['1', '2']).toContain(instance?.id);
78
+ });
79
+ it('should return null when no instances available', async () => {
80
+ const instance = await client.getInstance('non-existent');
81
+ expect(instance).toBeNull();
82
+ });
83
+ it('should use specified load balancing strategy', async () => {
84
+ const instance1 = createInstance('1');
85
+ const instance2 = createInstance('2');
86
+ await backend.register(instance1);
87
+ await backend.register(instance2);
88
+ // Round robin should cycle
89
+ const first = await client.getInstance('test-service', 'round-robin');
90
+ const second = await client.getInstance('test-service', 'round-robin');
91
+ const third = await client.getInstance('test-service', 'round-robin');
92
+ expect(first?.id).toBe('1');
93
+ expect(second?.id).toBe('2');
94
+ expect(third?.id).toBe('1');
95
+ });
96
+ it('should apply filters', async () => {
97
+ const instance1 = { ...createInstance('1'), zone: 'us-east-1' };
98
+ const instance2 = { ...createInstance('2'), zone: 'us-west-1' };
99
+ await backend.register(instance1);
100
+ await backend.register(instance2);
101
+ const instance = await client.getInstance('test-service', 'round-robin', {
102
+ zone: 'us-east-1',
103
+ });
104
+ expect(instance?.id).toBe('1');
105
+ });
106
+ });
107
+ describe('getAllServices', () => {
108
+ it('should return all service names', async () => {
109
+ await backend.register(createInstance('1', 'service-a'));
110
+ await backend.register(createInstance('2', 'service-b'));
111
+ const services = await client.getAllServices();
112
+ expect(services.sort()).toEqual(['service-a', 'service-b']);
113
+ });
114
+ });
115
+ describe('clearCache', () => {
116
+ it('should clear cache for specific service', () => {
117
+ const clientWithCache = new discovery_client_1.DiscoveryClient({ cacheEnabled: true }, backend);
118
+ clientWithCache.clearCache('service-a');
119
+ // Should not throw
120
+ expect(true).toBe(true);
121
+ });
122
+ it('should clear all cache when no service specified', () => {
123
+ const clientWithCache = new discovery_client_1.DiscoveryClient({ cacheEnabled: true }, backend);
124
+ clientWithCache.clearCache();
125
+ // Should not throw
126
+ expect(true).toBe(true);
127
+ });
128
+ });
129
+ describe('getBackend', () => {
130
+ it('should return the backend instance', () => {
131
+ const backend = client.getBackend();
132
+ expect(backend).toBeInstanceOf(memory_backend_1.MemoryRegistryBackend);
133
+ });
134
+ });
135
+ describe('getLoadBalancerFactory', () => {
136
+ it('should return the load balancer factory', () => {
137
+ const factory = client.getLoadBalancerFactory();
138
+ expect(factory).toBeDefined();
139
+ expect(factory.get('round-robin')).toBeDefined();
140
+ });
141
+ });
142
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Kubernetes Backend Tests
3
+ * Uses mocked @kubernetes/client-node API
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=kubernetes-backend.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kubernetes-backend.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/kubernetes-backend.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}