259 lines
9.2 KiB
TypeScript
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);
|
|
});
|
|
});
|