Pulse/frontend-modern/src/api/__tests__/charts.test.ts
2025-12-12 21:31:11 +00:00

259 lines
9.2 KiB
TypeScript

/**
* Tests for Charts API types and interface
*/
import { describe, expect, it } from 'vitest';
import type { ChartData, ChartsResponse, TimeRange, MetricPoint, ChartStats } from '@/api/charts';
// Note: We test the types and interfaces here since the actual API calls
// require a running backend. Integration tests should cover the full flow.
describe('Charts API Types', () => {
describe('TimeRange', () => {
it('supports all expected time range values', () => {
const validRanges: TimeRange[] = ['5m', '15m', '30m', '1h', '4h', '12h', '24h', '7d'];
validRanges.forEach(range => {
// This is a compile-time check - if it compiles, the types are correct
const r: TimeRange = range;
expect(r).toBe(range);
});
});
});
describe('MetricPoint', () => {
it('has required timestamp and value properties', () => {
const point: MetricPoint = {
timestamp: 1733700000000,
value: 45.7,
};
expect(point.timestamp).toBe(1733700000000);
expect(point.value).toBe(45.7);
});
});
describe('ChartData', () => {
it('supports all metric types', () => {
const data: ChartData = {
cpu: [{ timestamp: 1000, value: 50 }],
memory: [{ timestamp: 1000, value: 60 }],
disk: [{ timestamp: 1000, value: 70 }],
diskread: [{ timestamp: 1000, value: 1000000 }],
diskwrite: [{ timestamp: 1000, value: 500000 }],
netin: [{ timestamp: 1000, value: 2000000 }],
netout: [{ timestamp: 1000, value: 1500000 }],
};
expect(data.cpu).toHaveLength(1);
expect(data.memory![0].value).toBe(60);
});
it('allows partial data (all fields optional)', () => {
const cpuOnly: ChartData = {
cpu: [{ timestamp: 1000, value: 25 }],
};
expect(cpuOnly.cpu).toBeDefined();
expect(cpuOnly.memory).toBeUndefined();
expect(cpuOnly.disk).toBeUndefined();
});
it('allows empty arrays for metrics', () => {
const emptyData: ChartData = {
cpu: [],
memory: [],
};
expect(emptyData.cpu).toHaveLength(0);
});
});
describe('ChartsResponse', () => {
it('includes all required fields', () => {
const response: ChartsResponse = {
data: {},
nodeData: {},
storageData: {},
timestamp: Date.now(),
stats: { oldestDataTimestamp: Date.now() - 3600000 },
};
expect(response.data).toBeDefined();
expect(response.nodeData).toBeDefined();
expect(response.timestamp).toBeGreaterThan(0);
});
it('supports optional dockerData field', () => {
const response: ChartsResponse = {
data: {},
nodeData: {},
storageData: {},
dockerData: {
'container-abc123': {
cpu: [{ timestamp: 1000, value: 10 }],
memory: [{ timestamp: 1000, value: 20 }],
},
},
timestamp: Date.now(),
stats: { oldestDataTimestamp: Date.now() },
};
expect(response.dockerData).toBeDefined();
expect(response.dockerData!['container-abc123'].cpu).toHaveLength(1);
});
it('supports optional dockerHostData field', () => {
const response: ChartsResponse = {
data: {},
nodeData: {},
storageData: {},
dockerHostData: {
'host-1': {
cpu: [{ timestamp: 1000, value: 30 }],
memory: [{ timestamp: 1000, value: 40 }],
},
},
timestamp: Date.now(),
stats: { oldestDataTimestamp: Date.now() },
};
expect(response.dockerHostData).toBeDefined();
expect(response.dockerHostData!['host-1'].cpu![0].value).toBe(30);
});
it('supports optional guestTypes field for VM/container type mapping', () => {
const response: ChartsResponse = {
data: {
'vm-100': { cpu: [{ timestamp: 1000, value: 50 }] },
'ct-200': { cpu: [{ timestamp: 1000, value: 30 }] },
},
nodeData: {},
storageData: {},
guestTypes: {
'vm-100': 'vm',
'ct-200': 'container',
},
timestamp: Date.now(),
stats: { oldestDataTimestamp: Date.now() },
};
expect(response.guestTypes!['vm-100']).toBe('vm');
expect(response.guestTypes!['ct-200']).toBe('container');
});
it('contains all data sources for comprehensive monitoring', () => {
const comprehensiveResponse: ChartsResponse = {
// VM and container metrics (legacy format)
data: {
'pve1/qemu/100': {
cpu: [{ timestamp: 1000, value: 45 }],
memory: [{ timestamp: 1000, value: 55 }],
disk: [{ timestamp: 1000, value: 30 }],
},
'pve1/lxc/200': {
cpu: [{ timestamp: 1000, value: 15 }],
},
},
// Node metrics
nodeData: {
'pve1': {
cpu: [{ timestamp: 1000, value: 35 }],
memory: [{ timestamp: 1000, value: 65 }],
},
},
// Storage metrics
storageData: {
'local-zfs': {
disk: [{ timestamp: 1000, value: 50 }],
},
},
// Docker container metrics
dockerData: {
'abc123': {
cpu: [{ timestamp: 1000, value: 5 }],
memory: [{ timestamp: 1000, value: 128 }],
},
},
// Docker host metrics
dockerHostData: {
'docker-host-1': {
cpu: [{ timestamp: 1000, value: 25 }],
},
},
// Guest type mapping
guestTypes: {
'pve1/qemu/100': 'vm',
'pve1/lxc/200': 'container',
},
timestamp: 1733700000000,
stats: {
oldestDataTimestamp: 1733696400000, // 1 hour ago
},
};
expect(Object.keys(comprehensiveResponse.data)).toHaveLength(2);
expect(Object.keys(comprehensiveResponse.nodeData)).toHaveLength(1);
expect(Object.keys(comprehensiveResponse.dockerData!)).toHaveLength(1);
expect(Object.keys(comprehensiveResponse.dockerHostData!)).toHaveLength(1);
});
});
describe('ChartStats', () => {
it('contains oldest data timestamp for data availability', () => {
const stats: ChartStats = {
oldestDataTimestamp: 1733696400000,
};
expect(stats.oldestDataTimestamp).toBe(1733696400000);
});
it('can be used to determine available data range', () => {
const now = Date.now();
const oneHourAgo = now - 3600000;
const stats: ChartStats = {
oldestDataTimestamp: oneHourAgo,
};
const availableRangeMs = now - stats.oldestDataTimestamp;
expect(availableRangeMs).toBeCloseTo(3600000, -2); // Allow 100ms tolerance
});
});
});
describe('Time Range to Milliseconds Conversion', () => {
// This tests the concept matching metricsHistory.ts timeRangeToMs
function timeRangeToMs(range: TimeRange): number {
switch (range) {
case '5m': return 5 * 60 * 1000;
case '15m': return 15 * 60 * 1000;
case '30m': return 30 * 60 * 1000;
case '1h': return 60 * 60 * 1000;
case '4h': return 4 * 60 * 60 * 1000;
case '12h': return 12 * 60 * 60 * 1000;
case '24h': return 24 * 60 * 60 * 1000;
case '7d': return 7 * 24 * 60 * 60 * 1000;
default: return 60 * 60 * 1000;
}
}
const expectedValues: [TimeRange, number][] = [
['5m', 300000],
['15m', 900000],
['30m', 1800000],
['1h', 3600000],
['4h', 14400000],
['12h', 43200000],
['24h', 86400000],
['7d', 604800000],
];
it.each(expectedValues)('converts %s to %d ms', (range, expectedMs) => {
expect(timeRangeToMs(range)).toBe(expectedMs);
});
it('defaults to 1 hour for unknown range', () => {
// @ts-expect-error - Testing invalid input
expect(timeRangeToMs('invalid')).toBe(3600000);
});
});