import { flushPromises, mount } from '@vue/test-utils' import { describe, expect, it, vi } from 'vitest' import Server from '../Server.vue' import { rpcClient } from '@/api/rpc-client' vi.mock('@/stores/app', () => ({ useAppStore: () => ({ packages: {} }), })) vi.mock('@/api/rpc-client', () => ({ rpcClient: { call: vi.fn(), vpnStatus: vi.fn(), dnsStatus: vi.fn(), diskStatus: vi.fn(), }, })) function deferred() { let resolve!: (value: T) => void let reject!: (reason?: unknown) => void const promise = new Promise((res, rej) => { resolve = res reject = rej }) return { promise, resolve, reject } } function mountServer(options: { renderTorServices?: boolean } = {}) { return mount(Server, { global: { stubs: { QuickActionsCard: true, TorServicesCard: options.renderTorServices ? false : true, ServerModals: true, FipsNetworkCard: true, }, }, }) } describe('Server network refresh states', () => { it('keeps network overview visible while refresh is pending', async () => { vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => { if (request.method === 'network.diagnostics') { return Promise.resolve({ tor_connected: true, wifi_count: 2, wifi_ssid: 'Lab WiFi' }) } if (request.method === 'router.list-forwards') { return Promise.resolve({ forwards: [{}, {}] }) } if (request.method === 'network.list-interfaces') { return Promise.resolve({ interfaces: [] }) } if (request.method === 'tor.list-services') { return Promise.resolve({ services: [], tor_running: false }) } if (request.method === 'vpn.list-peers') { return Promise.resolve({ peers: [] }) } if (request.method === 'fips.status') { return Promise.resolve({ installed: false, service_active: false, key_present: false }) } return Promise.resolve({}) }) vi.mocked(rpcClient.vpnStatus).mockResolvedValue({ connected: true, provider: 'wireguard', ip_address: '10.0.0.2/32', wg_ip: '10.0.0.1/24' } as never) vi.mocked(rpcClient.dnsStatus).mockResolvedValue({ provider: 'cloudflare', resolv_conf_servers: ['1.1.1.1'], doh_enabled: true } as never) vi.mocked(rpcClient.diskStatus).mockResolvedValue({ encrypted: false, warnings: [] } as never) const wrapper = mountServer() await flushPromises() expect(wrapper.text()).toContain('Lab WiFi') expect(wrapper.text()).toContain('2 rules') const pendingDiagnostics = deferred<{ tor_connected: boolean; wifi_count: number; wifi_ssid: string }>() vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => { if (request.method === 'network.diagnostics') return pendingDiagnostics.promise if (request.method === 'router.list-forwards') return Promise.reject(new Error('offline')) return Promise.resolve({}) }) vi.mocked(rpcClient.vpnStatus).mockRejectedValueOnce(new Error('offline') as never) vi.mocked(rpcClient.dnsStatus).mockRejectedValueOnce(new Error('offline') as never) const refresh = (wrapper.vm as unknown as { loadNetworkData: () => Promise }).loadNetworkData() await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('Lab WiFi') expect(wrapper.text()).toContain('2 rules') expect(wrapper.text()).toContain('Refreshing network...') pendingDiagnostics.reject(new Error('offline')) await refresh await flushPromises() expect(wrapper.text()).toContain('Lab WiFi') expect(wrapper.text()).toContain('2 rules') }) it('keeps network interfaces visible while refresh is pending or fails', async () => { vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => { if (request.method === 'network.list-interfaces') { return Promise.resolve({ interfaces: [{ name: 'eth0', type: 'ethernet', state: 'up', mac: '00:11:22:33:44:55', ipv4: ['192.168.1.10'] }], }) } if (request.method === 'network.diagnostics') { return Promise.resolve({ tor_connected: false }) } if (request.method === 'router.list-forwards') { return Promise.resolve({ forwards: [] }) } if (request.method === 'tor.list-services') { return Promise.resolve({ services: [], tor_running: false }) } if (request.method === 'vpn.list-peers') { return Promise.resolve({ peers: [] }) } if (request.method === 'fips.status') { return Promise.resolve({ installed: false, service_active: false, key_present: false }) } return Promise.resolve({}) }) vi.mocked(rpcClient.vpnStatus).mockResolvedValue({ connected: false } as never) vi.mocked(rpcClient.dnsStatus).mockResolvedValue({ provider: 'system', resolv_conf_servers: [], doh_enabled: false } as never) vi.mocked(rpcClient.diskStatus).mockResolvedValue({ encrypted: false, warnings: [] } as never) const wrapper = mountServer() await flushPromises() expect(wrapper.text()).toContain('eth0') expect(wrapper.text()).toContain('192.168.1.10') const pendingInterfaces = deferred<{ interfaces: [] }>() vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => { if (request.method === 'network.list-interfaces') return pendingInterfaces.promise return Promise.resolve({}) }) const refresh = (wrapper.vm as unknown as { loadInterfaces: () => Promise }).loadInterfaces() await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('eth0') expect(wrapper.text()).toContain('192.168.1.10') expect(wrapper.text()).toContain('Refreshing interfaces...') pendingInterfaces.reject(new Error('offline')) await refresh await flushPromises() expect(wrapper.text()).toContain('eth0') expect(wrapper.text()).toContain('192.168.1.10') }) it('keeps Tor services visible while refresh is pending or fails', async () => { vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => { if (request.method === 'tor.list-services') { return Promise.resolve({ services: [{ name: 'filebrowser', local_port: 8080, onion_address: 'filebrowser123456789.onion', enabled: true, unauthenticated: false, protocol: false, }], tor_running: true, }) } if (request.method === 'network.diagnostics') { return Promise.resolve({ tor_connected: true }) } if (request.method === 'router.list-forwards') { return Promise.resolve({ forwards: [] }) } if (request.method === 'network.list-interfaces') { return Promise.resolve({ interfaces: [] }) } if (request.method === 'vpn.list-peers') { return Promise.resolve({ peers: [] }) } if (request.method === 'fips.status') { return Promise.resolve({ installed: false, service_active: false, key_present: false }) } return Promise.resolve({}) }) vi.mocked(rpcClient.vpnStatus).mockResolvedValue({ connected: false } as never) vi.mocked(rpcClient.dnsStatus).mockResolvedValue({ provider: 'system', resolv_conf_servers: [], doh_enabled: false } as never) vi.mocked(rpcClient.diskStatus).mockResolvedValue({ encrypted: false, warnings: [] } as never) const wrapper = mountServer({ renderTorServices: true }) await flushPromises() expect(wrapper.text()).toContain('filebrowser') expect(wrapper.text()).toContain('filebrowser123456789.onion') const pendingTor = deferred<{ services: []; tor_running: boolean }>() vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => { if (request.method === 'tor.list-services') return pendingTor.promise return Promise.resolve({}) }) const refresh = (wrapper.vm as unknown as { loadTorServices: () => Promise }).loadTorServices() await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('filebrowser') expect(wrapper.text()).toContain('filebrowser123456789.onion') expect(wrapper.text()).toContain('Refreshing Tor services...') pendingTor.reject(new Error('offline')) await refresh await flushPromises() expect(wrapper.text()).toContain('filebrowser') expect(wrapper.text()).toContain('filebrowser123456789.onion') }) })