From f2db41c0c0d513a3a8df48c2141ab5ebe1400a14 Mon Sep 17 00:00:00 2001 From: CSenshi Date: Sun, 17 Aug 2025 22:01:12 +0400 Subject: [PATCH 1/2] feat(health-indicator): add Redis client dependency and integration tests --- packages/health-indicator/package.json | 4 +- .../src/lib/health.indicator.int.spec.ts | 91 +++++++++++++++---- pnpm-lock.yaml | 81 +++++++++++++++++ 3 files changed, 157 insertions(+), 19 deletions(-) diff --git a/packages/health-indicator/package.json b/packages/health-indicator/package.json index f97f344..49ea0c4 100644 --- a/packages/health-indicator/package.json +++ b/packages/health-indicator/package.json @@ -59,8 +59,10 @@ "redis": "^5.0.0" }, "devDependencies": { + "@nestjs-redis/client": "0.10.1", "@nestjs/testing": "^11.0.0", - "redis": "^5.0.0" + "redis": "^5.0.0", + "supertest": "^7.1.4" }, "engines": { "node": ">=18.0.0", diff --git a/packages/health-indicator/src/lib/health.indicator.int.spec.ts b/packages/health-indicator/src/lib/health.indicator.int.spec.ts index e4a8148..04bce1f 100644 --- a/packages/health-indicator/src/lib/health.indicator.int.spec.ts +++ b/packages/health-indicator/src/lib/health.indicator.int.spec.ts @@ -1,8 +1,15 @@ -import { HealthIndicatorService } from '@nestjs/terminus'; +import { Controller, Get, Module } from '@nestjs/common'; +import { + HealthCheck, + HealthCheckResult, + HealthCheckService, + TerminusModule, +} from '@nestjs/terminus'; import { Test, TestingModule } from '@nestjs/testing'; +import { InjectRedis, Redis, RedisModule } from '@nestjs-redis/client'; import { createClient } from 'redis'; +import request from 'supertest'; import { RedisHealthIndicator } from './health.indicator'; -import { Redis } from './interfaces'; // These tests require a running Redis instance const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379'; @@ -13,22 +20,8 @@ describe('RedisHealthIndicator Integration Tests', () => { beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [ - RedisHealthIndicator, - { - provide: HealthIndicatorService, - useValue: { - check: jest.fn().mockReturnValue({ - up: jest.fn().mockImplementation((data) => ({ - redis: { status: 'up', ...data }, - })), - down: jest.fn().mockImplementation((data) => ({ - redis: { status: 'down', ...data }, - })), - }), - }, - }, - ], + imports: [TerminusModule], + providers: [RedisHealthIndicator], }).compile(); healthIndicator = module.get(RedisHealthIndicator); @@ -85,4 +78,66 @@ describe('RedisHealthIndicator Integration Tests', () => { } }, 15000); }); + + describe('Full RedisModule Integration', () => { + it('should integrate with the full RedisModule', async () => { + @Controller('health') + class HealthController { + constructor( + private health: HealthCheckService, + private redis: RedisHealthIndicator, + @InjectRedis() private readonly redisClient: Redis, + ) {} + + @Get() + @HealthCheck() + check(): Promise { + return this.health.check([ + () => this.redis.isHealthy('redis', { client: this.redisClient }), + ]); + } + } + + @Module({ + imports: [ + RedisModule.forRoot({ + type: 'client', + options: { url: process.env.REDIS_URL }, + }), + TerminusModule, + ], + controllers: [HealthController], + providers: [RedisHealthIndicator], + }) + class HealthModule {} + + @Module({ + imports: [HealthModule], + controllers: [], + providers: [], + }) + class AppModule {} + + const module: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + const app = module.createNestApplication(); + await app.init(); + + request(app.getHttpServer()) + .get('/health') + .expect(200) + .expect({ + data: { + status: 'ok', + info: { redis: { status: 'up' } }, + error: {}, + details: { redis: { status: 'up' } }, + }, + }); + + await app.close(); + }); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b8c409..d6cc72d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,12 +155,18 @@ importers: specifier: ^2.3.0 version: 2.8.1 devDependencies: + '@nestjs-redis/client': + specifier: 0.10.1 + version: 0.10.1(@nestjs/common@11.1.5(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.5)(redis@5.6.0) '@nestjs/testing': specifier: ^11.0.0 version: 11.1.5(@nestjs/common@11.1.5(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.5)(@nestjs/platform-express@11.1.5) redis: specifier: ^5.0.0 version: 5.6.0 + supertest: + specifier: ^7.1.4 + version: 7.1.4 packages/kit: dependencies: @@ -1283,6 +1289,10 @@ packages: '@nestjs/platform-socket.io': optional: true + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1393,6 +1403,9 @@ packages: '@nx/workspace@21.3.6': resolution: {integrity: sha512-ypCo/3/EHGtjPdufzZiGuaXjfrSIctztR959lrAj+KH7K9r5n465E6XTFGpOnFjPnwr0w2VfiYDAA5BkgOkGfQ==} + '@paralleldrive/cuid2@2.2.2': + resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@phenomnomnominal/tsquery@5.0.1': resolution: {integrity: sha512-3nVv+e2FQwsW8Aw6qTU6f+1rfcJ3hrcnvH/mu9i8YhxO+9sqbOfpL8m6PbET5+xKOlz/VSbp0RoYWYCtIsnmuA==} peerDependencies: @@ -2008,6 +2021,9 @@ packages: array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} @@ -2311,6 +2327,9 @@ packages: resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} engines: {node: '>= 6'} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -2363,6 +2382,9 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + core-js-compat@3.44.0: resolution: {integrity: sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==} @@ -2477,6 +2499,9 @@ packages: engines: {node: '>= 4.0.0'} hasBin: true + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2819,6 +2844,10 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -4295,6 +4324,14 @@ packages: resolution: {integrity: sha512-or9w505RhhY66+uoe5YOC5QO/bRuATaoim3XTh+pGKx5VMWi/HDhMKuCjDLsLJouU2zg9Hf1nLPcNW7IHv80kQ==} engines: {node: '>=18'} + superagent@10.2.3: + resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==} + engines: {node: '>=14.18.0'} + + supertest@7.1.4: + resolution: {integrity: sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==} + engines: {node: '>=14.18.0'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -6061,6 +6098,8 @@ snapshots: optionalDependencies: '@nestjs/platform-socket.io': 11.1.6(@nestjs/common@11.1.5(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@11.1.6)(rxjs@7.8.2) + '@noble/hashes@1.8.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6305,6 +6344,10 @@ snapshots: - '@swc/core' - debug + '@paralleldrive/cuid2@2.2.2': + dependencies: + '@noble/hashes': 1.8.0 + '@phenomnomnominal/tsquery@5.0.1(typescript@5.8.3)': dependencies: esquery: 1.6.0 @@ -6961,6 +7004,8 @@ snapshots: array-timsort@1.0.3: {} + asap@2.0.6: {} + asn1@0.2.6: dependencies: safer-buffer: 2.1.2 @@ -7330,6 +7375,8 @@ snapshots: has-own-prop: 2.0.0 repeat-string: 1.6.1 + component-emitter@1.3.1: {} + compressible@2.0.18: dependencies: mime-db: 1.54.0 @@ -7379,6 +7426,8 @@ snapshots: cookie@0.7.2: {} + cookiejar@2.1.4: {} + core-js-compat@3.44.0: dependencies: browserslist: 4.25.1 @@ -7474,6 +7523,11 @@ snapshots: transitivePeerDependencies: - supports-color + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + diff-sequences@29.6.3: {} diff@4.0.2: {} @@ -7911,6 +7965,12 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.2.2 + dezalgo: 1.0.4 + once: 1.4.0 + forwarded@0.2.0: {} fresh@0.5.2: {} @@ -9775,6 +9835,27 @@ snapshots: dependencies: '@tokenizer/token': 0.3.0 + superagent@10.2.3: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.1 + fast-safe-stringify: 2.1.1 + form-data: 4.0.4 + formidable: 3.5.4 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.14.0 + transitivePeerDependencies: + - supports-color + + supertest@7.1.4: + dependencies: + methods: 1.1.2 + superagent: 10.2.3 + transitivePeerDependencies: + - supports-color + supports-color@7.2.0: dependencies: has-flag: 4.0.0 From 1f324b0456ebcbf213080d4fdbc3c806ce560411 Mon Sep 17 00:00:00 2001 From: CSenshi Date: Sun, 17 Aug 2025 23:15:26 +0400 Subject: [PATCH 2/2] fix(health-indicator): update RedisHealthIndicator to use HealthIndicatorService directly --- .../health-indicator/src/lib/health.indicator.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/health-indicator/src/lib/health.indicator.ts b/packages/health-indicator/src/lib/health.indicator.ts index 50fbb11..0b8084b 100644 --- a/packages/health-indicator/src/lib/health.indicator.ts +++ b/packages/health-indicator/src/lib/health.indicator.ts @@ -7,9 +7,17 @@ import { Redis } from './interfaces'; @Injectable() export class RedisHealthIndicator { - constructor( - private readonly healthIndicatorService: HealthIndicatorService, - ) {} + /** + * TODO + * + * This is workaround, this should be DI but for some reason + * HealthIndicatorService is not injected after building the package. + * + * Reference (how it should be): https://docs.nestjs.com/recipes/terminus#custom-health-indicator + * + * ToDo: Fix this issue in the future. + */ + private healthIndicatorService = new HealthIndicatorService(); async isHealthy( key: string,