Files
portal/app/tests/Frontend/AdminIncidentsViewApi.spec.ts
T

151 lines
5.3 KiB
TypeScript

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<typeof import('../../resources/js/api/admin')>();
return {
...orig,
listAdminIncidents: vi.fn(),
};
});
const adminApi = await import('../../resources/js/api/admin');
beforeEach(() => {
vi.clearAllMocks();
});
function makeApiIncident(overrides: Partial<ApiAdminIncident> = {}): 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 виден + rowsState пустой', 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).toBe(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');
});
});