/** * 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); }); });