import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount, flushPromises } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; import AdminBillingView from '../../resources/js/views/admin/AdminBillingView.vue'; import type { ApiAdminBillingTenant } from '../../resources/js/api/admin'; vi.mock('../../resources/js/api/admin', async (importOriginal) => { const orig = await importOriginal(); return { ...orig, listAdminBilling: vi.fn(), }; }); const adminApi = await import('../../resources/js/api/admin'); beforeEach(() => { vi.clearAllMocks(); }); function makeApiBillingTenant(overrides: Partial = {}): ApiAdminBillingTenant { return { id: 1, subdomain: 'test', organization_name: 'Test ООО', contact_email: 'admin@test.io', status: 'active', balance_rub: '14250.00', tariff_id: 1, tariff_name: 'Команда', mrr_rub: '990.00', monthly_topups_rub: '30000.00', monthly_charges_rub: '25400.00', last_payment_at: '2026-05-04T10:23:00Z', chargeback_unrecovered_rub: '0.00', ...overrides, }; } const mountView = () => mount(AdminBillingView, { global: { plugins: [createVuetify()] }, }); describe('AdminBillingView ↔ GET /api/admin/billing integration', () => { it('listAdminBilling вызывается на mount', async () => { vi.mocked(adminApi.listAdminBilling).mockResolvedValueOnce({ tenants: [], summary: { total_mrr_rub: '0', monthly_revenue_rub: '0', overdue_count: 0, refunds_count_30d: 0, }, }); mountView(); await flushPromises(); expect(adminApi.listAdminBilling).toHaveBeenCalledTimes(1); }); it('успех — replace rowsState + summary; numbers конвертируются из string', async () => { vi.mocked(adminApi.listAdminBilling).mockResolvedValueOnce({ tenants: [ makeApiBillingTenant({ id: 100, organization_name: 'Окна Москва', balance_rub: '14250.50' }), makeApiBillingTenant({ id: 101, organization_name: 'Просрочник', balance_rub: '-200.00', status: 'active', }), ], summary: { total_mrr_rub: '1248600.00', monthly_revenue_rub: '1318400.00', overdue_count: 5, refunds_count_30d: 3, }, }); const wrapper = mountView(); await flushPromises(); const vm = wrapper.vm as unknown as { rowsState: Array<{ id: number; name: string; balance_rub: number; status: string }>; summary: { total_mrr_rub: number; overdue_count: number; refunds_count_30d: number }; }; expect(vm.rowsState).toHaveLength(2); expect(vm.rowsState[0].balance_rub).toBe(14250.5); expect(vm.rowsState[1].status).toBe('overdue'); // balance<0 → derive expect(vm.summary.total_mrr_rub).toBe(1248600); expect(vm.summary.overdue_count).toBe(5); expect(vm.summary.refunds_count_30d).toBe(3); }); it('reject → fetchError=true + alert виден + MOCK fallback остаётся', async () => { vi.mocked(adminApi.listAdminBilling).mockRejectedValueOnce(new Error('500')); const wrapper = mountView(); await flushPromises(); const vm = wrapper.vm as unknown as { fetchError: boolean; rowsState: unknown[] }; expect(vm.fetchError).toBe(true); expect(vm.rowsState.length).toBeGreaterThan(0); expect(wrapper.find('[data-testid="fetch-error-alert"]').exists()).toBe(true); }); it('reload-btn вызывает listAdminBilling второй раз', async () => { vi.mocked(adminApi.listAdminBilling).mockResolvedValue({ tenants: [], summary: { total_mrr_rub: '0', monthly_revenue_rub: '0', overdue_count: 0, refunds_count_30d: 0, }, }); const wrapper = mountView(); await flushPromises(); expect(adminApi.listAdminBilling).toHaveBeenCalledTimes(1); await wrapper.find('[data-testid="reload-btn"]').trigger('click'); await flushPromises(); expect(adminApi.listAdminBilling).toHaveBeenCalledTimes(2); }); });