Public source validation / validate (push) Failing after 3m8s
127 lines
8.1 KiB
TypeScript
127 lines
8.1 KiB
TypeScript
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { AlertRulesPage } from '../../src/AlertRulesPage';
|
|
|
|
afterEach(() => { cleanup(); vi.unstubAllGlobals(); window.history.replaceState({}, '', '/alerts'); });
|
|
|
|
describe('begeleide alertregelbewerking', () => {
|
|
it('houdt de werkruimte gesloten wanneer het regelscontract toegang weigert', async () => {
|
|
vi.stubGlobal('fetch', vi.fn((input: RequestInfo | URL) => {
|
|
if (String(input).includes('/alert-rules?')) return Promise.resolve(new Response('{}', { status: 403 }));
|
|
return Promise.resolve(new Response(JSON.stringify({ metrics: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
}));
|
|
|
|
render(<AlertRulesPage />);
|
|
|
|
expect(await screen.findByRole('heading', { name: 'Geen toegang tot alertregels' })).toBeVisible();
|
|
expect(screen.queryByRole('navigation', { name: 'Werkruimte voor meldingen' })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole('heading', { name: 'Actieve en recente meldingen' })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('laadt de metriccatalogus en blokkeert een ongeldige regel', async () => {
|
|
const fetchMock = vi.fn((input: RequestInfo | URL) => {
|
|
const url = String(input);
|
|
if (url.includes('/metrics/catalog')) return Promise.resolve(new Response(JSON.stringify({ metrics: [
|
|
{ semanticName: 'host.cpu.utilization', unit: 'percent', defaultAggregation: 'avg' },
|
|
] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
if (url.includes('/alert-silences') || url.includes('/maintenance-windows') || url.includes('/alerts?')) return Promise.resolve(new Response(JSON.stringify({ items: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
return Promise.resolve(new Response(JSON.stringify({ items: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
});
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
const user = userEvent.setup();
|
|
|
|
render(<AlertRulesPage />);
|
|
|
|
await user.click((await screen.findByText('Alertregels')).closest('button')!);
|
|
const save = await screen.findByRole('button', { name: 'Regel opslaan' });
|
|
expect(save).toBeDisabled();
|
|
const metric = screen.getByRole('combobox', { name: /Meting/ }) as HTMLSelectElement;
|
|
expect(metric).toHaveTextContent('CPU-gebruik van de host (%)');
|
|
expect(screen.queryByText('host.cpu.utilization')).not.toBeInTheDocument();
|
|
|
|
await user.type(document.querySelector<HTMLInputElement>('#alert-rule-name')!, 'Hoge hostbelasting');
|
|
await user.selectOptions(metric, 'host.cpu.utilization');
|
|
expect(save).toBeEnabled();
|
|
|
|
const technical = screen.getByText('Technische regelgegevens').closest('details');
|
|
expect(technical).not.toHaveAttribute('open');
|
|
expect(screen.getByLabelText('Host niet bereikbaar')).not.toBeChecked();
|
|
await user.click(screen.getByLabelText('Host niet bereikbaar'));
|
|
expect(screen.getByLabelText('Host niet bereikbaar')).toBeChecked();
|
|
await user.click(screen.getByText('Stiltes en onderhoud').closest('button')!);
|
|
expect(document.querySelector('#silence-matcher')).toHaveTextContent('Kritiek');
|
|
expect(document.querySelector('#maintenance-selector')).toHaveTextContent('Host');
|
|
expect(window.location.search).toBe('?section=controls');
|
|
});
|
|
|
|
it('laat bestaande niet-metrische regels met typeafhankelijke validatie bewerken', async () => {
|
|
const eventRule = {
|
|
id: '81111111-1111-4111-8111-111111111111', schemaVersion: 1, name: 'Container bevindt zich in een herstartlus', enabled: true, severity: 'degraded', scope: {},
|
|
condition: { inputType: 'event', operator: '>=', threshold: 3, aggregation: 'count', windowSeconds: 900 }, evaluationIntervalSeconds: 30, pendingSeconds: 0, resolveSeconds: 900, cooldownSeconds: 0,
|
|
unknownBehavior: 'become-unknown', groupBy: [], suppressWhen: ['host.unreachable'], message: { titleKey: 'alerts.restart.title', bodyKey: 'alerts.restart.body' }, revision: 1, currentVersion: 1,
|
|
};
|
|
vi.stubGlobal('fetch', vi.fn((input: RequestInfo | URL) => {
|
|
const url = String(input);
|
|
if (url.includes('/metrics/catalog')) return Promise.resolve(new Response(JSON.stringify({ metrics: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
if (url.includes('/alert-rules?')) return Promise.resolve(new Response(JSON.stringify({ items: [eventRule] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
return Promise.resolve(new Response(JSON.stringify({ items: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
}));
|
|
|
|
render(<AlertRulesPage />);
|
|
|
|
await userEvent.setup().click((await screen.findByText('Alertregels')).closest('button')!);
|
|
expect(await screen.findByDisplayValue('Container bevindt zich in een herstartlus')).toBeInTheDocument();
|
|
expect(screen.getByRole('combobox', { name: /Signaalbron/ })).toHaveValue('event');
|
|
expect(screen.queryByRole('combobox', { name: /Meting/ })).not.toBeInTheDocument();
|
|
expect(screen.getByRole('button', { name: 'Regel opslaan' })).toBeEnabled();
|
|
});
|
|
|
|
it('zet kritieke actieve meldingen vooraan en bewaart confirmatie, revisie en idempotentie', async () => {
|
|
const alerts = Array.from({ length: 25 }, (_, index) => ({
|
|
id: `alert-${String(index + 1).padStart(2, '0')}`,
|
|
state: index === 1 ? 'acknowledged' : 'firing',
|
|
retainedState: 'firing',
|
|
ruleName: `Melding ${String(index + 1).padStart(2, '0')}`,
|
|
severity: index % 5 === 0 ? 'critical' : 'attention',
|
|
entityName: 'Tower',
|
|
reason: 'threshold_exceeded',
|
|
revision: index + 1,
|
|
updatedAt: new Date(Date.UTC(2026, 7, 21, 12, 0, 0) - index * 60_000).toISOString(),
|
|
}));
|
|
const fetchMock = vi.fn((input: RequestInfo | URL, init?: RequestInit) => {
|
|
const url = String(input);
|
|
if (url.includes('/metrics/catalog')) return Promise.resolve(new Response(JSON.stringify({ metrics: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
if (url.includes('/alert-rules?')) return Promise.resolve(new Response(JSON.stringify({ items: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
if (url.includes('/alerts?')) return Promise.resolve(new Response(JSON.stringify({ items: alerts }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
if (init?.method === 'POST') return Promise.resolve(new Response(JSON.stringify({}), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
return Promise.resolve(new Response(JSON.stringify({ alert: alerts[0] }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
});
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
const confirm = vi.fn(() => false);
|
|
vi.stubGlobal('confirm', confirm);
|
|
const user = userEvent.setup();
|
|
|
|
render(<AlertRulesPage />);
|
|
|
|
const critical = (await screen.findByText('Kritiek actief')).closest('button')!;
|
|
expect(screen.getByText('Actief').closest('button')).toHaveAttribute('aria-pressed', 'true');
|
|
await waitFor(() => expect(document.querySelectorAll('.alert-operation-list-items li')).toHaveLength(20));
|
|
expect(document.querySelector('.alert-operation-list-items li')).toHaveTextContent('Kritiek');
|
|
await user.click(critical);
|
|
await waitFor(() => expect(document.querySelectorAll('.alert-operation-list-items li')).toHaveLength(5));
|
|
|
|
const acknowledge = screen.getAllByRole('button', { name: 'Erkennen' })[0];
|
|
await user.click(acknowledge);
|
|
expect(confirm).toHaveBeenCalledWith('Deze melding erkennen? De evaluatie en geschiedenis blijven behouden.');
|
|
expect(fetchMock.mock.calls.some(([, init]) => init?.method === 'POST')).toBe(false);
|
|
|
|
confirm.mockReturnValue(true);
|
|
await user.click(acknowledge);
|
|
const operation = fetchMock.mock.calls.find(([, init]) => init?.method === 'POST');
|
|
expect(operation?.[1]?.headers).toMatchObject({ 'If-Match': '1' });
|
|
expect((operation?.[1]?.headers as Record<string, string>)['Idempotency-Key']).toBeTruthy();
|
|
});
|
|
});
|