import { describe, it, expect } from 'vitest'; import { mapApiReportJob, uiTypeToApi } from '../../resources/js/composables/reportsMapper'; import type { ApiReportJob } from '../../resources/js/api/reports'; function makeApi(overrides: Partial = {}): ApiReportJob { return { id: 1, type: 'deals_export', parameters: { format: 'csv', date_from: '2026-04-01', date_to: '2026-04-30' }, status: 'done', file_path: 'reports/1/1.csv', file_size: 2048, generation_seconds: 2, error_message: null, created_at: '2026-04-15T10:00:00Z', finished_at: '2026-04-15T10:01:00Z', expires_at: '2026-05-15T10:01:00Z', is_expired: false, retry_count: 0, retry_max: 3, ...overrides, }; } describe('reportsMapper', () => { it('маппит status pending → queued', () => { const ui = mapApiReportJob(makeApi({ status: 'pending' })); expect(ui.status).toBe('queued'); }); it('маппит status processing → running', () => { const ui = mapApiReportJob(makeApi({ status: 'processing' })); expect(ui.status).toBe('running'); }); it('done и failed остаются как есть', () => { expect(mapApiReportJob(makeApi({ status: 'done' })).status).toBe('done'); expect(mapApiReportJob(makeApi({ status: 'failed' })).status).toBe('failed'); }); it('title включает тип + period (один месяц)', () => { const ui = mapApiReportJob(makeApi()); expect(ui.title).toBe('Сделки · детально · апр 2026'); }); it('title для разных месяцев показывает диапазон', () => { const ui = mapApiReportJob( makeApi({ parameters: { format: 'csv', date_from: '2026-03-01', date_to: '2026-04-30' } }), ); expect(ui.title).toBe('Сделки · детально · мар 2026 — апр 2026'); }); it('format берётся из parameters.format', () => { const ui = mapApiReportJob( makeApi({ parameters: { format: 'xlsx', date_from: '2026-04-01', date_to: '2026-04-30' } }), ); expect(ui.format).toBe('xlsx'); }); it('sizeText форматирует bytes (KB / MB)', () => { expect(mapApiReportJob(makeApi({ file_size: 500 })).sizeText).toBe('500 B'); expect(mapApiReportJob(makeApi({ file_size: 2048 })).sizeText).toBe('2.0 KB'); expect(mapApiReportJob(makeApi({ file_size: 1572864 })).sizeText).toBe('1.5 MB'); }); it('sizeText null если file_size null', () => { expect(mapApiReportJob(makeApi({ file_size: null })).sizeText).toBeNull(); }); it('attempt = retry_count + 1', () => { expect(mapApiReportJob(makeApi({ retry_count: 0 })).attempt).toBe(1); expect(mapApiReportJob(makeApi({ retry_count: 2 })).attempt).toBe(3); }); it('error из error_message', () => { expect(mapApiReportJob(makeApi({ error_message: 'boom' })).error).toBe('boom'); }); it('timeText для pending = «в очереди»', () => { expect(mapApiReportJob(makeApi({ status: 'pending', file_path: null })).timeText).toBe('в очереди'); }); it('timeText для processing включает «в работе»', () => { const ui = mapApiReportJob( makeApi({ status: 'processing', created_at: new Date(Date.now() - 5000).toISOString() }), ); expect(ui.timeText).toContain('в работе'); }); it('timeText для done показывает relative «N мин назад»', () => { const ui = mapApiReportJob( makeApi({ status: 'done', finished_at: new Date(Date.now() - 10 * 60_000).toISOString(), }), ); expect(ui.timeText).toBe('10 мин назад'); }); it('timeText для finished меньше минуты = «только что»', () => { const ui = mapApiReportJob( makeApi({ status: 'done', finished_at: new Date(Date.now() - 30_000).toISOString() }), ); expect(ui.timeText).toBe('только что'); }); it('uiTypeToApi маппит slug правильно', () => { expect(uiTypeToApi('deals')).toBe('deals_export'); expect(uiTypeToApi('managers')).toBe('managers_summary'); expect(uiTypeToApi('sources')).toBe('sources_summary'); expect(uiTypeToApi('billing')).toBe('billing_summary'); }); it('progress=50 для running, null для остальных', () => { expect(mapApiReportJob(makeApi({ status: 'processing' })).progress).toBe(50); expect(mapApiReportJob(makeApi({ status: 'done' })).progress).toBeNull(); expect(mapApiReportJob(makeApi({ status: 'pending' })).progress).toBeNull(); expect(mapApiReportJob(makeApi({ status: 'failed' })).progress).toBeNull(); }); });