@uploadista/server 0.0.18-beta.16 → 0.0.18-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,570 +0,0 @@
1
- /**
2
- * Tests for Health Check Service
3
- *
4
- * Covers:
5
- * - Liveness response creation
6
- * - Health status aggregation
7
- * - Individual component health checks
8
- * - Circuit breaker integration
9
- * - Dead letter queue integration
10
- * - Readiness and components checks
11
- */
12
-
13
- import { it } from "@effect/vitest";
14
- import type {
15
- ComponentHealth,
16
- HealthCheckConfig,
17
- HealthComponents,
18
- HealthStatus,
19
- } from "@uploadista/core/types";
20
- import { Effect, Layer } from "effect";
21
- import { describe, expect, vi, beforeEach } from "vitest";
22
- import {
23
- aggregateHealthStatus,
24
- checkEventBroadcasterHealth,
25
- checkKvStoreHealth,
26
- checkStorageHealth,
27
- createLivenessResponse,
28
- getServerUptime,
29
- getTimestamp,
30
- performComponentsCheck,
31
- performReadinessCheck,
32
- } from "../../src/core/health-check-service";
33
-
34
- describe("Health Check Service", () => {
35
- describe("getServerUptime", () => {
36
- it("should return a positive number", () => {
37
- const uptime = getServerUptime();
38
-
39
- expect(typeof uptime).toBe("number");
40
- expect(uptime).toBeGreaterThanOrEqual(0);
41
- });
42
-
43
- it("should increase over time", async () => {
44
- const uptime1 = getServerUptime();
45
- await new Promise((resolve) => setTimeout(resolve, 10));
46
- const uptime2 = getServerUptime();
47
-
48
- expect(uptime2).toBeGreaterThan(uptime1);
49
- });
50
- });
51
-
52
- describe("getTimestamp", () => {
53
- it("should return a valid ISO 8601 timestamp", () => {
54
- const timestamp = getTimestamp();
55
-
56
- // Verify it's a valid date string
57
- const parsedDate = new Date(timestamp);
58
- expect(parsedDate.toISOString()).toBe(timestamp);
59
- });
60
-
61
- it("should return current time", () => {
62
- const before = Date.now();
63
- const timestamp = getTimestamp();
64
- const after = Date.now();
65
-
66
- const timestampMs = new Date(timestamp).getTime();
67
- expect(timestampMs).toBeGreaterThanOrEqual(before);
68
- expect(timestampMs).toBeLessThanOrEqual(after);
69
- });
70
- });
71
-
72
- describe("createLivenessResponse", () => {
73
- it("should return healthy status", () => {
74
- const response = createLivenessResponse();
75
-
76
- expect(response.status).toBe("healthy");
77
- });
78
-
79
- it("should include timestamp", () => {
80
- const response = createLivenessResponse();
81
-
82
- expect(response.timestamp).toBeDefined();
83
- const parsedDate = new Date(response.timestamp);
84
- expect(parsedDate.toISOString()).toBe(response.timestamp);
85
- });
86
-
87
- it("should include uptime", () => {
88
- const response = createLivenessResponse();
89
-
90
- expect(response.uptime).toBeDefined();
91
- expect(typeof response.uptime).toBe("number");
92
- expect(response.uptime).toBeGreaterThanOrEqual(0);
93
- });
94
-
95
- it("should include version when configured", () => {
96
- const config: HealthCheckConfig = { version: "1.2.3" };
97
- const response = createLivenessResponse(config);
98
-
99
- expect(response.version).toBe("1.2.3");
100
- });
101
-
102
- it("should not include version when not configured", () => {
103
- const response = createLivenessResponse();
104
-
105
- expect(response.version).toBeUndefined();
106
- });
107
-
108
- it("should not include components", () => {
109
- const response = createLivenessResponse();
110
-
111
- expect(response.components).toBeUndefined();
112
- });
113
- });
114
-
115
- describe("aggregateHealthStatus", () => {
116
- it("should return healthy when all components are healthy", () => {
117
- const components: HealthComponents = {
118
- storage: {
119
- status: "healthy",
120
- latency: 10,
121
- lastCheck: getTimestamp(),
122
- },
123
- kvStore: {
124
- status: "healthy",
125
- latency: 5,
126
- lastCheck: getTimestamp(),
127
- },
128
- };
129
-
130
- const status = aggregateHealthStatus(components);
131
-
132
- expect(status).toBe("healthy");
133
- });
134
-
135
- it("should return unhealthy when storage is unhealthy", () => {
136
- const components: HealthComponents = {
137
- storage: {
138
- status: "unhealthy",
139
- latency: 10,
140
- lastCheck: getTimestamp(),
141
- message: "Connection failed",
142
- },
143
- kvStore: {
144
- status: "healthy",
145
- latency: 5,
146
- lastCheck: getTimestamp(),
147
- },
148
- };
149
-
150
- const status = aggregateHealthStatus(components);
151
-
152
- expect(status).toBe("unhealthy");
153
- });
154
-
155
- it("should return unhealthy when KV store is unhealthy", () => {
156
- const components: HealthComponents = {
157
- storage: {
158
- status: "healthy",
159
- latency: 10,
160
- lastCheck: getTimestamp(),
161
- },
162
- kvStore: {
163
- status: "unhealthy",
164
- latency: 5000,
165
- lastCheck: getTimestamp(),
166
- message: "Timeout",
167
- },
168
- };
169
-
170
- const status = aggregateHealthStatus(components);
171
-
172
- expect(status).toBe("unhealthy");
173
- });
174
-
175
- it("should return degraded when storage is degraded", () => {
176
- const components: HealthComponents = {
177
- storage: {
178
- status: "degraded",
179
- latency: 500,
180
- lastCheck: getTimestamp(),
181
- message: "High latency",
182
- },
183
- kvStore: {
184
- status: "healthy",
185
- latency: 5,
186
- lastCheck: getTimestamp(),
187
- },
188
- };
189
-
190
- const status = aggregateHealthStatus(components);
191
-
192
- expect(status).toBe("degraded");
193
- });
194
-
195
- it("should return degraded when optional component is degraded", () => {
196
- const components: HealthComponents = {
197
- storage: {
198
- status: "healthy",
199
- latency: 10,
200
- lastCheck: getTimestamp(),
201
- },
202
- circuitBreaker: {
203
- status: "degraded",
204
- openCircuits: 2,
205
- totalCircuits: 5,
206
- },
207
- };
208
-
209
- const status = aggregateHealthStatus(components);
210
-
211
- expect(status).toBe("degraded");
212
- });
213
-
214
- it("should return healthy with no components", () => {
215
- const components: HealthComponents = {};
216
-
217
- const status = aggregateHealthStatus(components);
218
-
219
- expect(status).toBe("healthy");
220
- });
221
-
222
- it("should prioritize unhealthy over degraded", () => {
223
- const components: HealthComponents = {
224
- storage: {
225
- status: "unhealthy",
226
- latency: 10000,
227
- lastCheck: getTimestamp(),
228
- },
229
- kvStore: {
230
- status: "degraded",
231
- latency: 500,
232
- lastCheck: getTimestamp(),
233
- },
234
- eventBroadcaster: {
235
- status: "healthy",
236
- latency: 5,
237
- lastCheck: getTimestamp(),
238
- },
239
- };
240
-
241
- const status = aggregateHealthStatus(components);
242
-
243
- expect(status).toBe("unhealthy");
244
- });
245
-
246
- it("should handle all component types", () => {
247
- const components: HealthComponents = {
248
- storage: {
249
- status: "healthy",
250
- latency: 10,
251
- lastCheck: getTimestamp(),
252
- },
253
- kvStore: {
254
- status: "healthy",
255
- latency: 5,
256
- lastCheck: getTimestamp(),
257
- },
258
- eventBroadcaster: {
259
- status: "healthy",
260
- latency: 3,
261
- lastCheck: getTimestamp(),
262
- },
263
- circuitBreaker: {
264
- status: "healthy",
265
- openCircuits: 0,
266
- totalCircuits: 3,
267
- },
268
- deadLetterQueue: {
269
- status: "healthy",
270
- pendingItems: 0,
271
- exhaustedItems: 0,
272
- },
273
- };
274
-
275
- const status = aggregateHealthStatus(components);
276
-
277
- expect(status).toBe("healthy");
278
- });
279
- });
280
-
281
- describe("checkStorageHealth", () => {
282
- it.effect("should return healthy status", () =>
283
- Effect.gen(function* () {
284
- const config: HealthCheckConfig = {};
285
- const health = yield* checkStorageHealth(config);
286
-
287
- expect(health.status).toBe("healthy");
288
- }),
289
- );
290
-
291
- it.effect("should include latency", () =>
292
- Effect.gen(function* () {
293
- const config: HealthCheckConfig = {};
294
- const health = yield* checkStorageHealth(config);
295
-
296
- expect(typeof health.latency).toBe("number");
297
- expect(health.latency).toBeGreaterThanOrEqual(0);
298
- }),
299
- );
300
-
301
- it.effect("should include lastCheck timestamp", () =>
302
- Effect.gen(function* () {
303
- const config: HealthCheckConfig = {};
304
- const health = yield* checkStorageHealth(config);
305
-
306
- expect(health.lastCheck).toBeDefined();
307
- const parsedDate = new Date(health.lastCheck!);
308
- expect(parsedDate.toISOString()).toBe(health.lastCheck);
309
- }),
310
- );
311
-
312
- it.effect("should include message", () =>
313
- Effect.gen(function* () {
314
- const config: HealthCheckConfig = {};
315
- const health = yield* checkStorageHealth(config);
316
-
317
- expect(health.message).toBeDefined();
318
- expect(typeof health.message).toBe("string");
319
- }),
320
- );
321
- });
322
-
323
- describe("checkKvStoreHealth", () => {
324
- it.effect("should return healthy status", () =>
325
- Effect.gen(function* () {
326
- const config: HealthCheckConfig = {};
327
- const health = yield* checkKvStoreHealth(config);
328
-
329
- expect(health.status).toBe("healthy");
330
- }),
331
- );
332
-
333
- it.effect("should include latency", () =>
334
- Effect.gen(function* () {
335
- const config: HealthCheckConfig = {};
336
- const health = yield* checkKvStoreHealth(config);
337
-
338
- expect(typeof health.latency).toBe("number");
339
- expect(health.latency).toBeGreaterThanOrEqual(0);
340
- }),
341
- );
342
-
343
- it.effect("should include lastCheck timestamp", () =>
344
- Effect.gen(function* () {
345
- const config: HealthCheckConfig = {};
346
- const health = yield* checkKvStoreHealth(config);
347
-
348
- expect(health.lastCheck).toBeDefined();
349
- }),
350
- );
351
- });
352
-
353
- describe("checkEventBroadcasterHealth", () => {
354
- it.effect("should return healthy status", () =>
355
- Effect.gen(function* () {
356
- const config: HealthCheckConfig = {};
357
- const health = yield* checkEventBroadcasterHealth(config);
358
-
359
- expect(health.status).toBe("healthy");
360
- }),
361
- );
362
-
363
- it.effect("should include latency", () =>
364
- Effect.gen(function* () {
365
- const config: HealthCheckConfig = {};
366
- const health = yield* checkEventBroadcasterHealth(config);
367
-
368
- expect(typeof health.latency).toBe("number");
369
- expect(health.latency).toBeGreaterThanOrEqual(0);
370
- }),
371
- );
372
-
373
- it.effect("should include lastCheck timestamp", () =>
374
- Effect.gen(function* () {
375
- const config: HealthCheckConfig = {};
376
- const health = yield* checkEventBroadcasterHealth(config);
377
-
378
- expect(health.lastCheck).toBeDefined();
379
- }),
380
- );
381
- });
382
-
383
- describe("performReadinessCheck", () => {
384
- it.effect("should return healthy status by default", () =>
385
- Effect.gen(function* () {
386
- const response = yield* performReadinessCheck();
387
-
388
- expect(response.status).toBe("healthy");
389
- }),
390
- );
391
-
392
- it.effect("should include timestamp and uptime", () =>
393
- Effect.gen(function* () {
394
- const response = yield* performReadinessCheck();
395
-
396
- expect(response.timestamp).toBeDefined();
397
- expect(response.uptime).toBeDefined();
398
- expect(typeof response.uptime).toBe("number");
399
- }),
400
- );
401
-
402
- it.effect("should check storage when enabled", () =>
403
- Effect.gen(function* () {
404
- const config: HealthCheckConfig = { checkStorage: true };
405
- const response = yield* performReadinessCheck(config);
406
-
407
- expect(response.components?.storage).toBeDefined();
408
- expect(response.components?.storage?.status).toBe("healthy");
409
- }),
410
- );
411
-
412
- it.effect("should skip storage when disabled", () =>
413
- Effect.gen(function* () {
414
- const config: HealthCheckConfig = { checkStorage: false };
415
- const response = yield* performReadinessCheck(config);
416
-
417
- expect(response.components?.storage).toBeUndefined();
418
- }),
419
- );
420
-
421
- it.effect("should check KV store when enabled", () =>
422
- Effect.gen(function* () {
423
- const config: HealthCheckConfig = { checkKvStore: true };
424
- const response = yield* performReadinessCheck(config);
425
-
426
- expect(response.components?.kvStore).toBeDefined();
427
- expect(response.components?.kvStore?.status).toBe("healthy");
428
- }),
429
- );
430
-
431
- it.effect("should skip KV store when disabled", () =>
432
- Effect.gen(function* () {
433
- const config: HealthCheckConfig = { checkKvStore: false };
434
- const response = yield* performReadinessCheck(config);
435
-
436
- expect(response.components?.kvStore).toBeUndefined();
437
- }),
438
- );
439
-
440
- it.effect("should check event broadcaster when enabled", () =>
441
- Effect.gen(function* () {
442
- const config: HealthCheckConfig = { checkEventBroadcaster: true };
443
- const response = yield* performReadinessCheck(config);
444
-
445
- expect(response.components?.eventBroadcaster).toBeDefined();
446
- expect(response.components?.eventBroadcaster?.status).toBe("healthy");
447
- }),
448
- );
449
-
450
- it.effect("should include version when configured", () =>
451
- Effect.gen(function* () {
452
- const config: HealthCheckConfig = { version: "2.0.0" };
453
- const response = yield* performReadinessCheck(config);
454
-
455
- expect(response.version).toBe("2.0.0");
456
- }),
457
- );
458
-
459
- it.effect("should check all components when all enabled", () =>
460
- Effect.gen(function* () {
461
- const config: HealthCheckConfig = {
462
- checkStorage: true,
463
- checkKvStore: true,
464
- checkEventBroadcaster: true,
465
- };
466
- const response = yield* performReadinessCheck(config);
467
-
468
- expect(response.components?.storage).toBeDefined();
469
- expect(response.components?.kvStore).toBeDefined();
470
- expect(response.components?.eventBroadcaster).toBeDefined();
471
- }),
472
- );
473
- });
474
-
475
- describe("performComponentsCheck", () => {
476
- it.effect("should return healthy status by default", () =>
477
- Effect.gen(function* () {
478
- const response = yield* performComponentsCheck();
479
-
480
- expect(response.status).toBe("healthy");
481
- }),
482
- );
483
-
484
- it.effect("should include timestamp and uptime", () =>
485
- Effect.gen(function* () {
486
- const response = yield* performComponentsCheck();
487
-
488
- expect(response.timestamp).toBeDefined();
489
- expect(response.uptime).toBeDefined();
490
- }),
491
- );
492
-
493
- it.effect("should check storage when enabled", () =>
494
- Effect.gen(function* () {
495
- const config: HealthCheckConfig = { checkStorage: true };
496
- const response = yield* performComponentsCheck(config);
497
-
498
- expect(response.components?.storage).toBeDefined();
499
- }),
500
- );
501
-
502
- it.effect("should check KV store when enabled", () =>
503
- Effect.gen(function* () {
504
- const config: HealthCheckConfig = { checkKvStore: true };
505
- const response = yield* performComponentsCheck(config);
506
-
507
- expect(response.components?.kvStore).toBeDefined();
508
- }),
509
- );
510
-
511
- it.effect("should check event broadcaster when enabled", () =>
512
- Effect.gen(function* () {
513
- const config: HealthCheckConfig = { checkEventBroadcaster: true };
514
- const response = yield* performComponentsCheck(config);
515
-
516
- expect(response.components?.eventBroadcaster).toBeDefined();
517
- }),
518
- );
519
-
520
- it.effect("should not include circuit breaker when not configured", () =>
521
- Effect.gen(function* () {
522
- // Without providing CircuitBreakerStoreService, it should not be included
523
- const response = yield* performComponentsCheck();
524
-
525
- // Circuit breaker is only included when the service is available
526
- // Since we're not providing it, it should be undefined
527
- expect(response.components?.circuitBreaker).toBeUndefined();
528
- }),
529
- );
530
-
531
- it.effect("should not include DLQ when not configured", () =>
532
- Effect.gen(function* () {
533
- // Without providing DeadLetterQueueService, it should not be included
534
- const response = yield* performComponentsCheck();
535
-
536
- // DLQ is only included when the service is available
537
- expect(response.components?.deadLetterQueue).toBeUndefined();
538
- }),
539
- );
540
-
541
- it.effect("should include version when configured", () =>
542
- Effect.gen(function* () {
543
- const config: HealthCheckConfig = { version: "3.0.0" };
544
- const response = yield* performComponentsCheck(config);
545
-
546
- expect(response.version).toBe("3.0.0");
547
- }),
548
- );
549
- });
550
-
551
- describe("Health Status Values", () => {
552
- it("should only allow valid health status values", () => {
553
- const validStatuses: HealthStatus[] = ["healthy", "degraded", "unhealthy"];
554
-
555
- // Test aggregation with each valid status
556
- for (const status of validStatuses) {
557
- const components: HealthComponents = {
558
- storage: {
559
- status,
560
- latency: 10,
561
- lastCheck: getTimestamp(),
562
- },
563
- };
564
-
565
- const result = aggregateHealthStatus(components);
566
- expect(validStatuses).toContain(result);
567
- }
568
- });
569
- });
570
- });