import { describe, it, expect, vi, beforeEach } from 'vitest' // Mock dependencies before importing the controller vi.mock('#providers/mongodb_provider', () => ({ getMongoDb: vi.fn(), })) vi.mock('#services/exchange_index_checker', () => ({ verifyCwmpExchangesIndexes: vi.fn(), })) vi.mock('#services/exchange_store_service', () => ({ ExchangeStoreService: { getInstance: vi.fn(), }, })) import { getMongoDb } from '#providers/mongodb_provider' import { verifyCwmpExchangesIndexes } from '#services/exchange_index_checker' import { ExchangeStoreService } from '#services/exchange_store_service' import HealthController from '#controllers/health_controller' describe('HealthController.checkTr069Exchanges', () => { let controller: HealthController let mockResponse: any let mockContext: any beforeEach(() => { vi.clearAllMocks() controller = new HealthController() mockResponse = { status: vi.fn().mockReturnThis(), json: vi.fn().mockReturnThis(), } mockContext = { response: mockResponse, } }) it('returns healthy status when MongoDB is connected with low latency and all indexes present', async () => { const mockDb = { command: vi.fn().mockResolvedValue({ ok: 1 }), collection: vi.fn().mockReturnValue({ estimatedDocumentCount: vi.fn().mockResolvedValue(500), }), } vi.mocked(getMongoDb).mockReturnValue(mockDb as any) vi.mocked(verifyCwmpExchangesIndexes).mockResolvedValue({ collection: 'cwmp_exchanges', allPresent: true, indexes: [ { name: 'idx_deviceId_timestamp', description: 'Compound index', exists: true }, { name: 'idx_deviceId_sequenceNumber', description: 'Unique index', exists: true }, { name: 'idx_createdAt_ttl', description: 'TTL index', exists: true }, ], }) // Mock collStats command mockDb.command.mockImplementation((cmd: any) => { if (cmd.ping) return Promise.resolve({ ok: 1 }) if (cmd.collStats) return Promise.resolve({ size: 1048576 }) // 1MB return Promise.resolve({}) }) vi.mocked(ExchangeStoreService.getInstance).mockReturnValue({ activeConnectionCount: 3, } as any) await controller.checkTr069Exchanges(mockContext) expect(mockResponse.status).toHaveBeenCalledWith(200) expect(mockResponse.json).toHaveBeenCalledWith( expect.objectContaining({ status: 'healthy', mongodb: expect.objectContaining({ connected: true, latency_ms: expect.any(Number), collections: { cwmp_exchanges: { count: 500, size_mb: expect.any(Number), }, }, indexes: expect.objectContaining({ allPresent: true, }), }), sse: { active_connections: 3, }, timestamp: expect.any(String), }) ) }) it('returns unhealthy status when MongoDB is unreachable', async () => { vi.mocked(getMongoDb).mockImplementation(() => { throw new Error('MongoDB is not connected') }) vi.mocked(ExchangeStoreService.getInstance).mockReturnValue({ activeConnectionCount: 0, } as any) await controller.checkTr069Exchanges(mockContext) expect(mockResponse.status).toHaveBeenCalledWith(503) expect(mockResponse.json).toHaveBeenCalledWith( expect.objectContaining({ status: 'unhealthy', mongodb: expect.objectContaining({ connected: false, latency_ms: null, collections: null, indexes: null, }), }) ) }) it('returns degraded status when MongoDB latency is >= 100ms', async () => { const mockDb = { command: vi.fn().mockImplementation((cmd: any) => { if (cmd.ping) { // Simulate a delay >= 100ms by manipulating Date.now return new Promise((resolve) => setTimeout(() => resolve({ ok: 1 }), 105)) } if (cmd.collStats) return Promise.resolve({ size: 2097152 }) return Promise.resolve({}) }), collection: vi.fn().mockReturnValue({ estimatedDocumentCount: vi.fn().mockResolvedValue(1000), }), } vi.mocked(getMongoDb).mockReturnValue(mockDb as any) vi.mocked(verifyCwmpExchangesIndexes).mockResolvedValue({ collection: 'cwmp_exchanges', allPresent: true, indexes: [], }) vi.mocked(ExchangeStoreService.getInstance).mockReturnValue({ activeConnectionCount: 5, } as any) await controller.checkTr069Exchanges(mockContext) // The test may be flaky due to timing, so we check both possible outcomes const jsonCall = mockResponse.json.mock.calls[0][0] expect(jsonCall.mongodb.connected).toBe(true) // Latency >= 100ms would be degraded, otherwise healthy if (jsonCall.mongodb.latency_ms >= 100) { expect(jsonCall.status).toBe('degraded') expect(mockResponse.status).toHaveBeenCalledWith(200) } else { expect(jsonCall.status).toBe('healthy') } }) it('returns degraded status when indexes are missing', async () => { const mockDb = { command: vi.fn().mockImplementation((cmd: any) => { if (cmd.ping) return Promise.resolve({ ok: 1 }) if (cmd.collStats) return Promise.resolve({ size: 512000 }) return Promise.resolve({}) }), collection: vi.fn().mockReturnValue({ estimatedDocumentCount: vi.fn().mockResolvedValue(200), }), } vi.mocked(getMongoDb).mockReturnValue(mockDb as any) vi.mocked(verifyCwmpExchangesIndexes).mockResolvedValue({ collection: 'cwmp_exchanges', allPresent: false, indexes: [ { name: 'idx_deviceId_timestamp', description: 'Compound index', exists: true }, { name: 'idx_deviceId_sequenceNumber', description: 'Unique index', exists: false }, { name: 'idx_createdAt_ttl', description: 'TTL index', exists: true }, ], }) vi.mocked(ExchangeStoreService.getInstance).mockReturnValue({ activeConnectionCount: 1, } as any) await controller.checkTr069Exchanges(mockContext) expect(mockResponse.status).toHaveBeenCalledWith(200) expect(mockResponse.json).toHaveBeenCalledWith( expect.objectContaining({ status: 'degraded', mongodb: expect.objectContaining({ connected: true, indexes: expect.objectContaining({ allPresent: false, }), }), }) ) }) it('reports SSE active connection count from ExchangeStoreService', async () => { const mockDb = { command: vi.fn().mockImplementation((cmd: any) => { if (cmd.ping) return Promise.resolve({ ok: 1 }) if (cmd.collStats) return Promise.resolve({ size: 0 }) return Promise.resolve({}) }), collection: vi.fn().mockReturnValue({ estimatedDocumentCount: vi.fn().mockResolvedValue(0), }), } vi.mocked(getMongoDb).mockReturnValue(mockDb as any) vi.mocked(verifyCwmpExchangesIndexes).mockResolvedValue({ collection: 'cwmp_exchanges', allPresent: true, indexes: [], }) vi.mocked(ExchangeStoreService.getInstance).mockReturnValue({ activeConnectionCount: 42, } as any) await controller.checkTr069Exchanges(mockContext) const jsonCall = mockResponse.json.mock.calls[0][0] expect(jsonCall.sse.active_connections).toBe(42) }) it('handles collStats failure gracefully (size defaults to 0)', async () => { const mockDb = { command: vi.fn().mockImplementation((cmd: any) => { if (cmd.ping) return Promise.resolve({ ok: 1 }) if (cmd.collStats) return Promise.reject(new Error('ns not found')) return Promise.resolve({}) }), collection: vi.fn().mockReturnValue({ estimatedDocumentCount: vi.fn().mockResolvedValue(0), }), } vi.mocked(getMongoDb).mockReturnValue(mockDb as any) vi.mocked(verifyCwmpExchangesIndexes).mockResolvedValue({ collection: 'cwmp_exchanges', allPresent: true, indexes: [], }) vi.mocked(ExchangeStoreService.getInstance).mockReturnValue({ activeConnectionCount: 0, } as any) await controller.checkTr069Exchanges(mockContext) expect(mockResponse.status).toHaveBeenCalledWith(200) const jsonCall = mockResponse.json.mock.calls[0][0] expect(jsonCall.mongodb.collections.cwmp_exchanges.size_mb).toBe(0) }) it('includes a valid ISO 8601 timestamp in the response', async () => { const mockDb = { command: vi.fn().mockImplementation((cmd: any) => { if (cmd.ping) return Promise.resolve({ ok: 1 }) if (cmd.collStats) return Promise.resolve({ size: 0 }) return Promise.resolve({}) }), collection: vi.fn().mockReturnValue({ estimatedDocumentCount: vi.fn().mockResolvedValue(0), }), } vi.mocked(getMongoDb).mockReturnValue(mockDb as any) vi.mocked(verifyCwmpExchangesIndexes).mockResolvedValue({ collection: 'cwmp_exchanges', allPresent: true, indexes: [], }) vi.mocked(ExchangeStoreService.getInstance).mockReturnValue({ activeConnectionCount: 0, } as any) await controller.checkTr069Exchanges(mockContext) const jsonCall = mockResponse.json.mock.calls[0][0] const date = new Date(jsonCall.timestamp) expect(date.getTime()).not.toBeNaN() }) })