const { AiValidationJobService } = require('../src/services/aiValidationJobService'); const waitFor = async (predicate, timeoutMs = 1000) => { const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { const result = predicate(); if (result) return result; await new Promise(resolve => setTimeout(resolve, 5)); } throw new Error('Timed out waiting for validation job'); }; describe('AiValidationJobService', () => { test('returns immediately and exposes the completed result through polling', async () => { const service = new AiValidationJobService(); const job = await service.createJob('test', async () => ({ status: 'passed' })); expect(job).toMatchObject({ type: 'test', status: 'queued' }); const completed = await waitFor(() => { const current = service.getJob(job.id, 'test'); return current?.status === 'completed' ? current : null; }); expect(completed.result).toEqual({ status: 'passed' }); }); test('runs Codex jobs one at a time and reports failures', async () => { const service = new AiValidationJobService(); let releaseFirst; const first = await service.createJob('first', () => new Promise(resolve => { releaseFirst = resolve; })); const second = await service.createJob('second', async () => { throw new Error('Codex fejlede'); }); await waitFor(() => service.getJob(first.id)?.status === 'running'); expect(service.getJob(second.id)).toMatchObject({ status: 'queued', queuePosition: 1 }); releaseFirst({ ok: true }); const failed = await waitFor(() => { const current = service.getJob(second.id); return current?.status === 'failed' ? current : null; }); expect(failed.error).toBe('Codex fejlede'); }); test('aggregates subscription usage and includes every AI feature', async () => { const service = new AiValidationJobService(); service.db = { pool: { execute: jest.fn().mockResolvedValue([[ { feature_type: 'risk_review', status: 'completed', model: 'gpt-5.6-terra', reasoning_effort: 'medium', usage_payload: JSON.stringify({ inputTokens: 100, cachedInputTokens: 40, outputTokens: 20 }), created_at: '2026-07-22T10:00:00.000Z', started_at: '2026-07-22T10:00:00.000Z', completed_at: '2026-07-22T10:00:10.000Z' }, { feature_type: 'project-flow', status: 'failed', model: 'gpt-5.6-terra', reasoning_effort: 'medium', usage_payload: null, created_at: '2026-07-22T09:00:00.000Z', started_at: '2026-07-22T09:00:00.000Z', completed_at: '2026-07-22T09:00:03.000Z' } ]]) } }; const usage = await service.getSubscriptionUsage(30); expect(usage.features).toHaveLength(11); expect(usage.summary).toMatchObject({ runs: 2, completedRuns: 1, failedRuns: 1, inputTokens: 100, cachedInputTokens: 40, outputTokens: 20, totalTokens: 120 }); expect(usage.features.find(feature => feature.key === 'risk_review')).toMatchObject({ runs: 1, totalTokens: 120, averageDurationSeconds: 10 }); expect(usage.features.find(feature => feature.key === 'final_review')).toMatchObject({ runs: 1, failedRuns: 1 }); expect(usage.features.find(feature => feature.key === 'excel_review')).toMatchObject({ runs: 0 }); }); });