Files
ITWorx-Pulse-Public/apps/web/tests/unit/ExploreMetricsPage.test.tsx
T
2026-09-30 01:14:48 +00:00

50 lines
4.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 { ExploreMetricsPage } from '../../src/ExploreMetricsPage';
import { resetOperatorContextForTests, setOperatorCustomRange } from '../../src/operatorContext';
const metric = (name: string, maxRangeSeconds = 2_592_000) => ({ semanticName: name, description: 'Goedgekeurde metric voor operators', unit: 'percent', valueKind: 'gauge', defaultAggregation: 'avg', allowedVisualizations: ['timeseries'], limits: { maxRangeSeconds, maxSeries: 20, maxPoints: 4000, timeoutSeconds: 5 } });
const catalog = { version: 'v1', metrics: [metric('host.cpu.utilization'), metric('host.memory.utilization', 3600), metric('storage.pool.utilization'), metric('service.availability'), metric('service.response_time')], bindings: ['host.cpu.utilization', 'host.memory.utilization', 'storage.pool.utilization', 'service.availability', 'service.response_time'].map((semanticName) => ({ semanticName, state: 'supported' })) };
const response = { status: 'success', data: { result: [{ metric: { host: 'tower' }, values: [[1_777_000_000, '42'], [1_777_000_060, '43']] }] }, provenance: { source: 'fixture', metric: 'test', catalogVersion: 'v1', cacheKey: 'safe' }, sourceObservedAt: '2026-09-01T10:00:00Z', receivedAt: '2026-09-01T10:00:01Z', freshness: 'fresh', cacheHit: false };
afterEach(() => { cleanup(); vi.unstubAllGlobals(); resetOperatorContextForTests(); window.localStorage.clear(); window.history.replaceState({}, '', '/explore'); });
function mockAPI() {
const calls: Array<Record<string, unknown>> = [];
vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
if (url.endsWith('/api/v1/metrics/catalog')) return new Response(JSON.stringify(catalog), { status: 200, headers: { 'Content-Type': 'application/json' } });
if (url.includes('/api/v1/incidents') || url.includes('/api/v1/alerts')) return new Response(JSON.stringify({ items: [] }), { status: 200, headers: { 'Content-Type': 'application/json' } });
if (url.endsWith('/api/v1/metrics/query-range')) { calls.push(JSON.parse(String(init?.body))); return new Response(JSON.stringify(response), { status: 200, headers: { 'Content-Type': 'application/json' } }); }
return new Response('', { status: 404 });
}));
return calls;
}
describe('ExploreMetricsPage', () => {
it('finds and opens a semantic metric without exposing PromQL', async () => {
const calls = mockAPI(); const user = userEvent.setup();
render(<ExploreMetricsPage />);
await user.type(await screen.findByPlaceholderText('CPU, opslag, responstijd…'), 'cpu');
await user.click(screen.getByRole('button', { name: /host.cpu.utilization/ }));
await waitFor(() => expect(calls).toHaveLength(1));
expect(calls[0]).toMatchObject({ metric: 'host.cpu.utilization', maxSeries: 20, maxPoints: 4000 });
expect(document.body).not.toHaveTextContent('rate(');
});
it('caps compare at four metrics and blocks an over-budget range locally', async () => {
const calls = mockAPI(); const user = userEvent.setup();
render(<ExploreMetricsPage />);
await user.click(await screen.findByRole('tab', { name: 'Vergelijken' }));
for (const name of ['host.cpu.utilization', 'storage.pool.utilization', 'service.availability', 'service.response_time']) await user.click(screen.getByRole('button', { name: new RegExp(name.replaceAll('.', '\\.')) }));
expect(screen.getByText('Maximaal vier metrics kunnen tegelijk worden vergeleken.')).toBeVisible();
expect(screen.getByRole('button', { name: /host.memory.utilization/ })).not.toHaveAttribute('aria-pressed', 'true');
await user.click(screen.getByRole('button', { name: /host.cpu.utilization/ }));
setOperatorCustomRange('2026-09-01T00:00', '2026-09-02T00:00');
await user.click(screen.getByRole('button', { name: /host.memory.utilization/ }));
await waitFor(() => expect(screen.getByText('Serverbudget overschreden')).toBeVisible());
expect(calls.some((call) => call.metric === 'host.memory.utilization')).toBe(false);
});
});