diff --git a/package-lock.json b/package-lock.json index e9a1d87..0273419 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6202,9 +6202,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.defaults": { diff --git a/tests/unit/controllers/HealthDetailedController.test.ts b/tests/unit/controllers/HealthDetailedController.test.ts index 29d68d3..bb3697d 100644 --- a/tests/unit/controllers/HealthDetailedController.test.ts +++ b/tests/unit/controllers/HealthDetailedController.test.ts @@ -11,8 +11,7 @@ import express, { Application } from 'express'; import request from 'supertest'; -import { Pool, PoolClient } from 'pg'; -import { HealthDetailedController, HealthDetailedDeps } from '../../../src/controllers/HealthDetailedController'; +import { HealthDetailedController, HealthDetailedDeps, DbProbe } from '../../../src/controllers/HealthDetailedController'; // ── fetch mock ──────────────────────────────────────────────────────────────── @@ -22,23 +21,19 @@ global.fetch = mockFetch; // ── Helpers ──────────────────────────────────────────────────────────────────── -function makePoolClient(latencyMs = 0, error?: Error): jest.Mocked> { +/** + * Creates a mock DbProbe. When `error` is provided, checkLiveness() rejects + * with that error (simulates unreachable DB). Otherwise it resolves after + * `latencyMs` ms (0 by default — Date.now mocking handles degraded scenarios). + */ +function makeDbProbe(error?: Error, latencyMs = 0): DbProbe { return { - query: error + checkLiveness: error ? jest.fn().mockRejectedValue(error) - : jest.fn().mockImplementation(() => - new Promise((resolve) => setTimeout(() => resolve({ rows: [], rowCount: 0 }), latencyMs)), + : jest.fn().mockImplementation( + () => new Promise((resolve) => setTimeout(() => resolve(), latencyMs)), ), - release: jest.fn(), - } as unknown as jest.Mocked>; -} - -function makePool(connectError?: Error, queryLatencyMs = 0, queryError?: Error): jest.Mocked { - return { - connect: connectError - ? jest.fn().mockRejectedValue(connectError) - : jest.fn().mockResolvedValue(makePoolClient(queryLatencyMs, queryError)), - } as unknown as jest.Mocked; + }; } function makeRedisClient(pingError?: Error, latencyMs = 0): { ping(): Promise } { @@ -67,7 +62,7 @@ beforeEach(() => { describe('GET /health/detailed — all services healthy', () => { it('returns 200 with overall status "healthy" when postgres and redis respond quickly', async () => { const app = buildApp({ - pool: makePool(undefined, 10), + dbProbe: makeDbProbe(undefined, 10), redisClient: makeRedisClient(undefined, 5), }); @@ -81,7 +76,7 @@ describe('GET /health/detailed — all services healthy', () => { it('includes version and uptime in the response body', async () => { const app = buildApp({ - pool: makePool(), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(), }); @@ -93,7 +88,7 @@ describe('GET /health/detailed — all services healthy', () => { it('includes latencyMs for each service', async () => { const app = buildApp({ - pool: makePool(), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(), }); @@ -146,7 +141,7 @@ describe('GET /health/detailed — degraded scenario', () => { try { const app = buildApp({ - pool: makePool(undefined, 0), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(undefined, 0), }); @@ -177,7 +172,7 @@ describe('GET /health/detailed — degraded scenario', () => { try { const app = buildApp({ - pool: makePool(undefined, 0), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(undefined, 0), }); @@ -195,7 +190,7 @@ describe('GET /health/detailed — degraded scenario', () => { describe('GET /health/detailed — unreachable scenarios', () => { it('returns 503 when postgres connect() throws', async () => { const app = buildApp({ - pool: makePool(new Error('ECONNREFUSED')), + dbProbe: makeDbProbe(new Error('ECONNREFUSED')), redisClient: makeRedisClient(), }); @@ -208,7 +203,7 @@ describe('GET /health/detailed — unreachable scenarios', () => { it('returns 503 when redis ping() throws', async () => { const app = buildApp({ - pool: makePool(), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(new Error('Redis ECONNREFUSED')), }); @@ -221,7 +216,7 @@ describe('GET /health/detailed — unreachable scenarios', () => { it('returns 503 when both postgres and redis are unreachable', async () => { const app = buildApp({ - pool: makePool(new Error('PG down')), + dbProbe: makeDbProbe(new Error('PG down')), redisClient: makeRedisClient(new Error('Redis down')), }); @@ -237,7 +232,7 @@ describe('GET /health/detailed — unreachable scenarios', () => { describe('GET /health/detailed — optional services omitted when not configured', () => { it('does not include vault in services when vaultAddr is not provided', async () => { const app = buildApp({ - pool: makePool(), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(), }); @@ -248,7 +243,7 @@ describe('GET /health/detailed — optional services omitted when not configured it('does not include opa in services when opaUrl is not provided', async () => { const app = buildApp({ - pool: makePool(), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(), }); @@ -263,7 +258,7 @@ describe('GET /health/detailed — Vault and OPA probes', () => { mockFetch.mockResolvedValue(new Response(null, { status: 200 })); const app = buildApp({ - pool: makePool(), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(), vaultAddr: 'http://vault:8200', }); @@ -278,7 +273,7 @@ describe('GET /health/detailed — Vault and OPA probes', () => { mockFetch.mockRejectedValue(new Error('Network failure')); const app = buildApp({ - pool: makePool(), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(), vaultAddr: 'http://vault:8200', }); @@ -292,7 +287,7 @@ describe('GET /health/detailed — Vault and OPA probes', () => { mockFetch.mockResolvedValue(new Response('{}', { status: 200 })); const app = buildApp({ - pool: makePool(), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(), opaUrl: 'http://opa:8181', }); @@ -307,7 +302,7 @@ describe('GET /health/detailed — Vault and OPA probes', () => { mockFetch.mockResolvedValue(new Response(null, { status: 503 })); const app = buildApp({ - pool: makePool(), + dbProbe: makeDbProbe(), redisClient: makeRedisClient(), opaUrl: 'http://opa:8181', }); diff --git a/tests/unit/services/EventPublisher.test.ts b/tests/unit/services/EventPublisher.test.ts index 80104f2..cf06368 100644 --- a/tests/unit/services/EventPublisher.test.ts +++ b/tests/unit/services/EventPublisher.test.ts @@ -19,7 +19,13 @@ function makePool(queryImpl?: jest.Mock): jest.Mocked { } function makeWorker(): jest.Mocked { - const worker = new MockWorker({} as never) as jest.Mocked; + // WebhookDeliveryWorker(pool, vaultClient, redisClient, redisUrl) — pass all required args + const worker = new MockWorker( + {} as never, + null, + {} as never, + 'redis://localhost', + ) as jest.Mocked; worker.enqueue = jest.fn().mockResolvedValue(undefined); return worker; } diff --git a/tests/unit/services/MarketplaceService.test.ts b/tests/unit/services/MarketplaceService.test.ts index 91f0276..d99c1e5 100644 --- a/tests/unit/services/MarketplaceService.test.ts +++ b/tests/unit/services/MarketplaceService.test.ts @@ -27,8 +27,8 @@ function makeAgent(overrides: Partial = {}): IAgent { isPublic: true, createdAt: new Date('2026-01-01'), updatedAt: new Date('2026-01-02'), - did: null, - didCreatedAt: null, + did: undefined, + didCreatedAt: undefined, ...overrides, }; } @@ -62,7 +62,7 @@ describe('MarketplaceService', () => { agentRepo.findPublicAgents = jest.fn().mockResolvedValue({ agents: [agent], total: 1 }); const result = await service.listPublicAgents(BASE_FILTERS); - const card = result.data[0] as Record; + const card = result.data[0] as unknown as Record; expect(card['email']).toBeUndefined(); expect(card['organizationId']).toBeUndefined(); @@ -79,7 +79,7 @@ describe('MarketplaceService', () => { }); it('should return null DID document when agent has no DID', async () => { - const agent = makeAgent({ did: null }); + const agent = makeAgent({ did: undefined }); agentRepo.findPublicAgents = jest.fn().mockResolvedValue({ agents: [agent], total: 1 }); const result = await service.listPublicAgents(BASE_FILTERS); diff --git a/tests/unit/services/OIDCTrustPolicyService.test.ts b/tests/unit/services/OIDCTrustPolicyService.test.ts index b20dc8d..7978d25 100644 --- a/tests/unit/services/OIDCTrustPolicyService.test.ts +++ b/tests/unit/services/OIDCTrustPolicyService.test.ts @@ -52,7 +52,7 @@ describe('OIDCTrustPolicyService', () => { const result = await service.createTrustPolicy({ provider: 'github', repository: 'acme/my-repo', - branch: null, + branch: undefined, agentId: 'agent-001', }); @@ -66,7 +66,7 @@ describe('OIDCTrustPolicyService', () => { service.createTrustPolicy({ provider: 'gitlab' as never, repository: 'acme/my-repo', - branch: null, + branch: undefined, agentId: 'agent-001', }), ).rejects.toThrow(ValidationError); @@ -77,7 +77,7 @@ describe('OIDCTrustPolicyService', () => { service.createTrustPolicy({ provider: 'github', repository: 'no-slash-here', - branch: null, + branch: undefined, agentId: 'agent-001', }), ).rejects.toThrow(ValidationError); @@ -88,7 +88,7 @@ describe('OIDCTrustPolicyService', () => { service.createTrustPolicy({ provider: 'github', repository: 'acme/my-repo', - branch: null, + branch: undefined, agentId: '', }), ).rejects.toThrow(ValidationError); @@ -101,7 +101,7 @@ describe('OIDCTrustPolicyService', () => { service.createTrustPolicy({ provider: 'github', repository: 'acme/my-repo', - branch: null, + branch: undefined, agentId: 'nonexistent', }), ).rejects.toThrow(ValidationError);