import { describe, expect, test, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'; import NetworkSettingsCard from './NetworkSettingsCard.svelte'; const getNetworkSettings = vi.fn(); const updateNetworkSettings = vi.fn(); vi.mock('$lib/api/admin', () => ({ getNetworkSettings: () => getNetworkSettings(), updateNetworkSettings: (hops: number) => updateNetworkSettings(hops) })); vi.mock('$lib/stores/toast.svelte', () => ({ pushToast: vi.fn() })); function settings(over: Record = {}) { return { trusted_proxy_hops: 1, max_hops: 10, detected_client_ip: '198.51.100.7', forwarded_chain: '198.51.100.7', remote_addr: '172.18.0.1:40000', ...over }; } beforeEach(() => { vi.clearAllMocks(); }); describe('NetworkSettingsCard', () => { // The detected address is the card's verification affordance — the number // is abstract, this is checkable against the machine you're sitting at. test('shows the address the current setting resolves to', async () => { getNetworkSettings.mockResolvedValue(settings()); render(NetworkSettingsCard); // The address legitimately appears twice — as the detected client and // inside the forwarded chain — so wait on the unique label, not the value. await screen.findByText('Your address right now'); expect(screen.getAllByText('198.51.100.7').length).toBeGreaterThan(0); expect(screen.getByText('172.18.0.1:40000')).toBeTruthy(); }); test('save is inert until the value actually changes', async () => { getNetworkSettings.mockResolvedValue(settings({ trusted_proxy_hops: 1 })); render(NetworkSettingsCard); const save = await screen.findByRole('button', { name: /Save/ }); expect(save).toBeDisabled(); const input = screen.getByRole('spinbutton'); await fireEvent.input(input, { target: { value: '2' } }); await waitFor(() => expect(save).not.toBeDisabled()); }); test('saving sends the new depth and adopts the echoed value', async () => { getNetworkSettings.mockResolvedValue(settings({ trusted_proxy_hops: 1 })); updateNetworkSettings.mockResolvedValue( settings({ trusted_proxy_hops: 2, detected_client_ip: '203.0.113.9' }) ); render(NetworkSettingsCard); const input = await screen.findByRole('spinbutton'); await fireEvent.input(input, { target: { value: '2' } }); await fireEvent.click(screen.getByRole('button', { name: /Save/ })); await waitFor(() => expect(updateNetworkSettings).toHaveBeenCalledWith(2)); // The recomputed address proves the change took effect on this request. expect(await screen.findByText('203.0.113.9')).toBeTruthy(); }); // Counting proxies is the operator's job and the hint is how they do it // without guessing. test('hints the likely depth when it disagrees with the arriving chain', async () => { getNetworkSettings.mockResolvedValue( settings({ trusted_proxy_hops: 1, forwarded_chain: '198.51.100.7, 203.0.113.50' }) ); render(NetworkSettingsCard); expect(await screen.findByText(/arrived with 2 forwarded addresses/)).toBeTruthy(); }); test('no hint when the setting already matches the chain length', async () => { getNetworkSettings.mockResolvedValue( settings({ trusted_proxy_hops: 1, forwarded_chain: '198.51.100.7' }) ); render(NetworkSettingsCard); await screen.findByText('Your address right now'); expect(screen.queryByText(/arrived with/)).toBeNull(); }); test('states the mis-set risk rather than only exposing a number', async () => { getNetworkSettings.mockResolvedValue(settings()); render(NetworkSettingsCard); expect(await screen.findByText(/Count your proxies/)).toBeTruthy(); }); test('offers a retry when loading fails', async () => { getNetworkSettings.mockRejectedValue(new Error('boom')); render(NetworkSettingsCard); const retry = await screen.findByRole('button', { name: 'Try again' }); getNetworkSettings.mockResolvedValue(settings()); await fireEvent.click(retry); await screen.findByText('Your address right now'); }); });