/** * Unit tests for src/services/UsageService.ts */ import { Pool } from 'pg'; import { UsageService } from '../../../src/services/UsageService'; jest.mock('pg'); const MockPool = Pool as jest.MockedClass; function makePool(): jest.Mocked { const pool = new MockPool() as jest.Mocked; pool.query = jest.fn(); return pool; } const TENANT_ID = 'org-abc-123'; const DATE = '2026-04-07'; describe('UsageService', () => { let service: UsageService; let pool: jest.Mocked; beforeEach(() => { jest.clearAllMocks(); pool = makePool(); service = new UsageService(pool); }); describe('getDailyUsage()', () => { it('should return usage summary with real api call count', async () => { pool.query = jest.fn() .mockResolvedValueOnce({ rows: [{ count: '42' }], rowCount: 1 }) .mockResolvedValueOnce({ rows: [{ count: '5' }], rowCount: 1 }); const result = await service.getDailyUsage(TENANT_ID, DATE); expect(result.tenantId).toBe(TENANT_ID); expect(result.date).toBe(DATE); expect(result.apiCalls).toBe(42); expect(result.agentCount).toBe(5); }); it('should default apiCalls to 0 when no usage row exists', async () => { pool.query = jest.fn() .mockResolvedValueOnce({ rows: [{ count: '0' }], rowCount: 1 }) .mockResolvedValueOnce({ rows: [{ count: '3' }], rowCount: 1 }); const result = await service.getDailyUsage(TENANT_ID, DATE); expect(result.apiCalls).toBe(0); }); it('should handle missing count row gracefully (defaults to 0)', async () => { pool.query = jest.fn() .mockResolvedValueOnce({ rows: [], rowCount: 0 }) .mockResolvedValueOnce({ rows: [{ count: '2' }], rowCount: 1 }); const result = await service.getDailyUsage(TENANT_ID, DATE); expect(result.apiCalls).toBe(0); }); it('should query usage_events with correct tenant and date params', async () => { pool.query = jest.fn() .mockResolvedValueOnce({ rows: [{ count: '10' }], rowCount: 1 }) .mockResolvedValueOnce({ rows: [{ count: '0' }], rowCount: 1 }); await service.getDailyUsage(TENANT_ID, DATE); expect(pool.query).toHaveBeenNthCalledWith( 1, expect.stringContaining('usage_events'), [TENANT_ID, DATE], ); }); }); describe('getActiveAgentCount()', () => { it('should return the count of non-decommissioned agents', async () => { pool.query = jest.fn().mockResolvedValue({ rows: [{ count: '7' }], rowCount: 1 }); const count = await service.getActiveAgentCount(TENANT_ID); expect(count).toBe(7); }); it('should return 0 when no agents exist for tenant', async () => { pool.query = jest.fn().mockResolvedValue({ rows: [{ count: '0' }], rowCount: 1 }); const count = await service.getActiveAgentCount(TENANT_ID); expect(count).toBe(0); }); it('should exclude decommissioned agents (query contains status check)', async () => { pool.query = jest.fn().mockResolvedValue({ rows: [{ count: '3' }], rowCount: 1 }); await service.getActiveAgentCount(TENANT_ID); expect(pool.query).toHaveBeenCalledWith( expect.stringContaining('decommissioned'), [TENANT_ID], ); }); it('should handle missing count row gracefully', async () => { pool.query = jest.fn().mockResolvedValue({ rows: [], rowCount: 0 }); const count = await service.getActiveAgentCount(TENANT_ID); expect(count).toBe(0); }); }); });