fix(vv): resolve all 6 V&V issues — field trial unblocked
All findings from the inaugural LeadValidator audit resolved and confirmed. Release gate: PASS. VV_ISSUE_002 (BLOCKER): 15 OpenAPI specs verified present covering all 20 route groups (46 endpoints documented in docs/openapi/) VV_ISSUE_003 (MAJOR): Remove any types from src/db/pool.ts — replaced pool.query shim with unknown[] + Object.defineProperty, zero any types, eslint-disable suppressions removed VV_ISSUE_004 (MAJOR): Remove raw Pool from ScaffoldController and HealthDetailedController — injected AgentRepository/CredentialRepository and DbProbe interface respectively; added CredentialRepository.findActiveClientId() VV_ISSUE_005 (MAJOR): Add unit tests for 5 untested services — ComplianceStatusStore, EventPublisher, MarketplaceService, OIDCTrustPolicyService, UsageService VV_ISSUE_006 (MAJOR): Add integration tests for 7 missing route groups — analytics, billing, tiers, webhooks, marketplace, oidc-trust-policies, oidc-token-exchange VV_ISSUE_001 (MINOR): Create missing design.md and tasks.md in 4 OpenSpec archives — all archives now complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
116
tests/unit/services/UsageService.test.ts
Normal file
116
tests/unit/services/UsageService.test.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 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<typeof Pool>;
|
||||
|
||||
function makePool(): jest.Mocked<Pool> {
|
||||
const pool = new MockPool() as jest.Mocked<Pool>;
|
||||
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<Pool>;
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user