import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount, flushPromises } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; import AdminIncidentsView from '../../resources/js/views/admin/AdminIncidentsView.vue'; import type { ApiAdminIncident } from '../../resources/js/api/admin'; vi.mock('../../resources/js/api/admin', async (importOriginal) => { const orig = await importOriginal(); return { ...orig, listAdminIncidents: vi.fn(), }; }); const adminApi = await import('../../resources/js/api/admin'); beforeEach(() => { vi.clearAllMocks(); }); function makeApiIncident(overrides: Partial = {}): ApiAdminIncident { return { id: 1, incident_id: 'INC-2026-0509-0001', type: 'service_outage', severity: 'medium', summary: 'Test incident', started_at: new Date().toISOString(), detected_at: new Date().toISOString(), resolved_at: null, status: 'investigating', affected_tenants_count: 0, affected_users_count: null, rkn_notified: false, rkn_notified_at: null, rkn_deadline_at: null, ...overrides, }; } const mountView = () => mount(AdminIncidentsView, { global: { plugins: [createVuetify()] }, }); describe('AdminIncidentsView ↔ GET /api/admin/incidents integration', () => { it('listAdminIncidents вызывается на mount', async () => { vi.mocked(adminApi.listAdminIncidents).mockResolvedValueOnce({ incidents: [], total: 0, limit: 100, offset: 0, summary: { open: 0, investigating: 0, rkn_pending: 0, total_unresolved: 0 }, }); mountView(); await flushPromises(); expect(adminApi.listAdminIncidents).toHaveBeenCalledTimes(1); }); it('успех — replace rowsState + summary', async () => { vi.mocked(adminApi.listAdminIncidents).mockResolvedValueOnce({ incidents: [ makeApiIncident({ id: 100, type: 'data_breach', severity: 'critical', summary: 'PDN leak', rkn_notified: false, rkn_deadline_at: '2026-05-10T00:00:00Z', }), makeApiIncident({ id: 101, type: 'service_outage', summary: 'API timeout', status: 'investigating', }), ], total: 2, limit: 100, offset: 0, summary: { open: 0, investigating: 2, rkn_pending: 1, total_unresolved: 2 }, }); const wrapper = mountView(); await flushPromises(); const vm = wrapper.vm as unknown as { rowsState: Array<{ id: number; title: string; category: string; rkn_deadline_at: string | null }>; stats: { open: number; investigating: number; rkn_pending: number }; }; expect(vm.rowsState).toHaveLength(2); expect(vm.rowsState[0].title).toBe('PDN leak'); expect(vm.rowsState[0].category).toBe('data_breach'); expect(vm.rowsState[0].rkn_deadline_at).toBe('2026-05-10T00:00:00Z'); expect(vm.stats.investigating).toBe(2); expect(vm.stats.rkn_pending).toBe(1); }); it('reject → fetchError=true + alert виден + MOCK fallback', async () => { vi.mocked(adminApi.listAdminIncidents).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 вызывает listAdminIncidents второй раз', async () => { vi.mocked(adminApi.listAdminIncidents).mockResolvedValue({ incidents: [], total: 0, limit: 100, offset: 0, summary: { open: 0, investigating: 0, rkn_pending: 0, total_unresolved: 0 }, }); const wrapper = mountView(); await flushPromises(); expect(adminApi.listAdminIncidents).toHaveBeenCalledTimes(1); await wrapper.find('[data-testid="reload-btn"]').trigger('click'); await flushPromises(); expect(adminApi.listAdminIncidents).toHaveBeenCalledTimes(2); }); it('РКН pending chip виден для data_breach без rkn_notified', async () => { vi.mocked(adminApi.listAdminIncidents).mockResolvedValueOnce({ incidents: [ makeApiIncident({ id: 200, type: 'data_breach', summary: 'breach', rkn_notified: false, }), ], total: 1, limit: 100, offset: 0, summary: { open: 0, investigating: 1, rkn_pending: 1, total_unresolved: 1 }, }); const wrapper = mountView(); await flushPromises(); expect(wrapper.text()).toContain('РКН pending'); }); });