diff --git a/tests-ui/tests/components/common/DeviceInfo.test.ts b/tests-ui/tests/components/common/DeviceInfo.test.ts new file mode 100644 index 000000000..37fad2be7 --- /dev/null +++ b/tests-ui/tests/components/common/DeviceInfo.test.ts @@ -0,0 +1,162 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' + +import DeviceInfo from '@/components/common/DeviceInfo.vue' +import type { DeviceStats } from '@/schemas/apiSchema' + +describe('DeviceInfo', () => { + const validDevice: DeviceStats = { + name: 'NVIDIA GeForce RTX 4090', + type: 'cuda', + index: 0, + vram_total: 24564224000, + vram_free: 20564224000, + torch_vram_total: 24564224000, + torch_vram_free: 20564224000 + } + + const createWrapper = (device: DeviceStats | undefined) => { + return mount(DeviceInfo, { + props: { device } as any + }) + } + + describe('Normal operation', () => { + it('should render device information correctly with valid device', () => { + const wrapper = createWrapper(validDevice) + + expect(wrapper.text()).toContain('NVIDIA GeForce RTX 4090') + expect(wrapper.text()).toContain('cuda') + expect(wrapper.text()).toContain('22.9 GB') // vram_total formatted + expect(wrapper.text()).toContain('19.1 GB') // vram_free formatted + }) + + it('should display all device columns', () => { + const wrapper = createWrapper(validDevice) + const headers = wrapper.findAll('.font-medium') + + expect(headers).toHaveLength(6) + expect(headers[0].text()).toBe('Name') + expect(headers[1].text()).toBe('Type') + expect(headers[2].text()).toBe('VRAM Total') + expect(headers[3].text()).toBe('VRAM Free') + expect(headers[4].text()).toBe('Torch VRAM Total') + expect(headers[5].text()).toBe('Torch VRAM Free') + }) + }) + + describe('Sentry Issue CLOUD-FRONTEND-STAGING-13: undefined device prop', () => { + it('should throw TypeError when device prop is undefined', () => { + // This test reproduces the exact Sentry error + expect(() => { + createWrapper(undefined as any) + }).toThrow() + }) + + it('should throw TypeError when accessing undefined device properties', () => { + // Test the specific error: Cannot read properties of undefined (reading 'name') + expect(() => { + const wrapper = mount(DeviceInfo, { + props: { device: undefined as any } + }) + // This will trigger the error when Vue tries to render the template + wrapper.html() + }).toThrow(TypeError) + }) + + it('should fail when formatValue tries to access undefined device fields', () => { + // Simulate the exact scenario from the stack trace + const mockDeviceColumns = [ + { field: 'name', header: 'Name' }, + { field: 'type', header: 'Type' }, + { field: 'vram_total', header: 'VRAM Total' } + ] + + expect(() => { + const undefinedDevice = undefined as any + mockDeviceColumns.forEach((col) => { + // This simulates: formatValue(props.device[col.field], col.field) + // where props.device is undefined + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + undefinedDevice[col.field] // This should throw + }) + }).toThrow(TypeError) + }) + }) + + describe('Edge cases that could lead to undefined device', () => { + it('should handle device with missing required fields', () => { + const incompleteDevice = { + name: 'Test Device' + // Missing required fields: type, index, vram_total, etc. + } as any + + expect(() => { + createWrapper(incompleteDevice) + }).toThrow() + }) + + it('should handle device with null values', () => { + const deviceWithNulls = { + name: null, + type: null, + index: 0, + vram_total: null, + vram_free: null, + torch_vram_total: null, + torch_vram_free: null + } as any + + const wrapper = createWrapper(deviceWithNulls) + // The component should render but may show null values + expect(wrapper.exists()).toBe(true) + }) + }) + + describe('SystemStatsPanel integration scenarios', () => { + it('should fail when devices array is empty and accessing devices[0]', () => { + // This simulates the scenario where props.stats.devices[0] is undefined + // because the devices array is empty + const emptyDevicesArray: DeviceStats[] = [] + + expect(() => { + const deviceFromEmptyArray = emptyDevicesArray[0] // undefined + createWrapper(deviceFromEmptyArray) + }).toThrow() + }) + + it('should fail when SystemStats API returns malformed data', () => { + // Simulate API returning data that doesn't match expected schema + const malformedApiResponse = { + system: { + /* valid system data */ + }, + devices: null // This should be an array but API returned null + } + + expect(() => { + const deviceFromMalformedData = malformedApiResponse.devices?.[0] + createWrapper(deviceFromMalformedData) + }).toThrow() + }) + }) + + describe('formatValue function edge cases', () => { + it('should handle undefined values in VRAM fields', () => { + const deviceWithUndefinedVram = { + name: 'Test Device', + type: 'cuda', + index: 0, + vram_total: undefined, + vram_free: undefined, + torch_vram_total: undefined, + torch_vram_free: undefined + } as any + + // The component should render but formatValue might fail + expect(() => { + createWrapper(deviceWithUndefinedVram) + }).toThrow() + }) + }) +}) diff --git a/tests-ui/tests/components/common/SystemStatsPanel.test.ts b/tests-ui/tests/components/common/SystemStatsPanel.test.ts new file mode 100644 index 000000000..ea2999d88 --- /dev/null +++ b/tests-ui/tests/components/common/SystemStatsPanel.test.ts @@ -0,0 +1,216 @@ +import { mount } from '@vue/test-utils' +import TabView from 'primevue/tabview' +import { describe, expect, it, vi } from 'vitest' + +import DeviceInfo from '@/components/common/DeviceInfo.vue' +import SystemStatsPanel from '@/components/common/SystemStatsPanel.vue' +import type { SystemStats } from '@/schemas/apiSchema' + +// Mock vue-i18n +vi.mock('vue-i18n', () => ({ + useI18n: vi.fn(() => ({ + t: vi.fn((key: string) => key) + })) +})) + +describe('SystemStatsPanel', () => { + const mockSystemStats: SystemStats = { + system: { + os: 'Windows 11', + python_version: '3.11.7', + embedded_python: false, + comfyui_version: '1.26.7', + pytorch_version: '2.1.2', + argv: ['--auto-launch'], + ram_total: 17179869184, + ram_free: 8589934592 + }, + devices: [ + { + name: 'NVIDIA GeForce RTX 4090', + type: 'cuda', + index: 0, + vram_total: 24564224000, + vram_free: 20564224000, + torch_vram_total: 24564224000, + torch_vram_free: 20564224000 + }, + { + name: 'Intel UHD Graphics', + type: 'cpu', + index: 1, + vram_total: 0, + vram_free: 0, + torch_vram_total: 0, + torch_vram_free: 0 + } + ] + } + + const createWrapper = (stats: SystemStats) => { + return mount(SystemStatsPanel, { + props: { stats }, + global: { + components: { + DeviceInfo, + TabView + } + } + }) + } + + describe('Normal operation', () => { + it('should render system information correctly', () => { + const wrapper = createWrapper(mockSystemStats) + + expect(wrapper.text()).toContain('Windows 11') + expect(wrapper.text()).toContain('3.11.7') + expect(wrapper.text()).toContain('1.26.7') + }) + + it('should render single device without tabs when only one device', () => { + const singleDeviceStats = { + ...mockSystemStats, + devices: [mockSystemStats.devices[0]] + } + + const wrapper = createWrapper(singleDeviceStats) + expect(wrapper.findComponent(TabView).exists()).toBe(false) + expect(wrapper.findComponent(DeviceInfo).exists()).toBe(true) + }) + + it('should render multiple devices with tabs', () => { + const wrapper = createWrapper(mockSystemStats) + expect(wrapper.findComponent(TabView).exists()).toBe(true) + }) + }) + + describe('Sentry Issue CLOUD-FRONTEND-STAGING-13: Edge cases leading to undefined device', () => { + it('should fail when devices array is empty and accessing devices[0]', () => { + const emptyDevicesStats: SystemStats = { + ...mockSystemStats, + devices: [] + } + + expect(() => { + createWrapper(emptyDevicesStats) + }).toThrow() + }) + + it('should fail when devices array is undefined', () => { + const undefinedDevicesStats = { + ...mockSystemStats, + devices: undefined as any + } + + expect(() => { + createWrapper(undefinedDevicesStats) + }).toThrow() + }) + + it('should fail when devices array contains undefined elements', () => { + const statsWithUndefinedDevice: SystemStats = { + ...mockSystemStats, + devices: [ + mockSystemStats.devices[0], + undefined as any, // This simulates corrupted data + mockSystemStats.devices[1] + ] + } + + expect(() => { + createWrapper(statsWithUndefinedDevice) + }).toThrow() + }) + + it('should fail when API returns malformed SystemStats structure', () => { + // Simulate various API response corruption scenarios + const malformedStats = [ + // Missing devices property + { + system: mockSystemStats.system + // devices property missing entirely + } as any, + + // Devices is not an array + { + system: mockSystemStats.system, + devices: 'not-an-array' + } as any, + + // Devices is null + { + system: mockSystemStats.system, + devices: null + } as any + ] + + malformedStats.forEach((stats, index) => { + expect( + () => { + createWrapper(stats) + }, + `Malformed stats scenario ${index + 1} should throw` + ).toThrow() + }) + }) + }) + + describe('Device selection logic edge cases', () => { + it('should fail when v-else condition tries to access devices[0] on empty array', () => { + // This tests the specific template logic: + const emptyDevicesStats: SystemStats = { + ...mockSystemStats, + devices: [] + } + + // The v-else condition should trigger since devices.length > 1 is false + // But devices[0] will be undefined + expect(() => { + createWrapper(emptyDevicesStats) + }).toThrow() + }) + + it('should handle concurrent modification of devices array', () => { + // Simulate race condition where devices array gets modified after component creation + const mutableStats = { ...mockSystemStats } + const wrapper = createWrapper(mutableStats) + + // Simulate external modification (e.g., store update) + mutableStats.devices = [] + + expect(wrapper.exists()).toBe(true) // Component should still exist + }) + }) + + describe('Integration with systemStatsStore scenarios', () => { + it('should fail when store returns partial/corrupted data', () => { + // Simulate systemStatsStore.systemStats returning incomplete data + const partialStats = { + system: undefined, + devices: undefined + } as any + + expect(() => { + createWrapper(partialStats) + }).toThrow() + }) + + it('should fail when API response parsing fails', () => { + // Simulate JSON parsing errors leading to unexpected structure + const corruptedStats = { + system: mockSystemStats.system, + devices: [ + { + name: 'Device 1' + // Missing required fields + } as any + ] + } + + expect(() => { + createWrapper(corruptedStats) + }).toThrow() + }) + }) +})