feat: T1007 System Service Discovery - fixed allowlist probe in posture heartbeat
This commit is contained in:
148
server/web/src/pages/SettingsPage.test.tsx
Normal file
148
server/web/src/pages/SettingsPage.test.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { cleanup, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import SettingsPage, { deepMerge } from './SettingsPage';
|
||||
import { mockServerConfig, mockServerInfo } from '../test/fixtures';
|
||||
import { api } from '../api/client';
|
||||
import { clearStoredAuth, getStoredAuth, setStoredAuth } from '../api/auth';
|
||||
|
||||
function renderSettings() {
|
||||
return render(<SettingsPage />);
|
||||
}
|
||||
|
||||
describe('deepMerge', () => {
|
||||
it('preserves base keys not present in override', () => {
|
||||
const base = { a: 1, nested: { keep: true, old: 'x' } };
|
||||
const result = deepMerge(base, { nested: { old: 'y' } });
|
||||
expect(result.a).toBe(1);
|
||||
expect(result.nested.keep).toBe(true);
|
||||
expect(result.nested.old).toBe('y');
|
||||
});
|
||||
|
||||
it('replaces scalar values from override', () => {
|
||||
const base = { port: 8989, pool: { host: 'a', port: 3333 } };
|
||||
const result = deepMerge(base, { port: 8080, pool: { host: 'b' } });
|
||||
expect(result.port).toBe(8080);
|
||||
expect(result.pool.host).toBe('b');
|
||||
expect(result.pool.port).toBe(3333);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SettingsPage (Calibrate)', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
sessionStorage.clear();
|
||||
vi.spyOn(api, 'getConfig').mockResolvedValue(mockServerConfig());
|
||||
vi.spyOn(api, 'getServerInfo').mockResolvedValue(mockServerInfo);
|
||||
vi.spyOn(api, 'updateConfig').mockImplementation(async (cfg) => cfg as ReturnType<typeof mockServerConfig>);
|
||||
vi.spyOn(api, 'createUser').mockResolvedValue({ success: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('shows loading state then Calibrate heading', async () => {
|
||||
renderSettings();
|
||||
expect(screen.getByRole('heading', { level: 1, name: 'Calibrate' })).toBeInTheDocument();
|
||||
expect(screen.getByText('Loading server calibration...')).toBeInTheDocument();
|
||||
expect(await screen.findByText('CONTROL SERVER · LOCAL HOST')).toBeInTheDocument();
|
||||
expect(screen.getAllByRole('heading', { level: 1, name: 'Calibrate' })).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders key section headings and labels', async () => {
|
||||
renderSettings();
|
||||
expect(await screen.findByText('Control Server')).toBeInTheDocument();
|
||||
expect(screen.getByText('Upstream Pool')).toBeInTheDocument();
|
||||
expect(screen.getByText('Fleet Payout Wallet')).toBeInTheDocument();
|
||||
expect(screen.getByText('Access Control')).toBeInTheDocument();
|
||||
expect(screen.getByText('Fleet Security')).toBeInTheDocument();
|
||||
expect(screen.getByText('Listen Port')).toBeInTheDocument();
|
||||
expect(screen.getByText(/Public URL \(LAN\)/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/XMR Address/i)).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('8080')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows detected LAN endpoints banner', async () => {
|
||||
renderSettings();
|
||||
expect(await screen.findByText('DETECTED LAN ENDPOINTS')).toBeInTheDocument();
|
||||
expect(screen.getByText(mockServerInfo.suggested_url!)).toBeInTheDocument();
|
||||
expect(screen.getByText(/IPs on this host:/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('saves calibration via updateConfig API', async () => {
|
||||
const updateSpy = vi.spyOn(api, 'updateConfig');
|
||||
renderSettings();
|
||||
await screen.findByRole('button', { name: 'Save Calibration' });
|
||||
await userEvent.setup().click(screen.getByRole('button', { name: 'Save Calibration' }));
|
||||
await waitFor(() => {
|
||||
expect(updateSpy).toHaveBeenCalled();
|
||||
});
|
||||
expect(await screen.findByText('Calibration saved — control server updated.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('applies best defaults to public URL from server info', async () => {
|
||||
vi.spyOn(api, 'getConfig').mockResolvedValue(
|
||||
mockServerConfig({ server: { public_url: '' } })
|
||||
);
|
||||
renderSettings();
|
||||
await screen.findByRole('button', { name: 'Use best defaults' });
|
||||
const publicUrlInput = screen.getByPlaceholderText(mockServerInfo.suggested_url!) as HTMLInputElement;
|
||||
expect(publicUrlInput.value).toBe('');
|
||||
await userEvent.setup().click(screen.getByRole('button', { name: 'Use best defaults' }));
|
||||
expect(
|
||||
await screen.findByText('Best defaults applied to the form — click Save Calibration to keep them.')
|
||||
).toBeInTheDocument();
|
||||
expect(publicUrlInput.value).toBe(mockServerInfo.suggested_url);
|
||||
});
|
||||
|
||||
it('stores browser session credentials', async () => {
|
||||
renderSettings();
|
||||
const accessSection = (await screen.findByText('Access Control')).closest('.settings-section') as HTMLElement;
|
||||
const user = userEvent.setup();
|
||||
const sessionUser = within(accessSection).getAllByPlaceholderText('admin')[0];
|
||||
const sessionPass = accessSection.querySelectorAll('input[type="password"]')[0] as HTMLInputElement;
|
||||
await user.type(sessionUser, 'admin');
|
||||
await user.type(sessionPass, 'secret-pass');
|
||||
await user.click(within(accessSection).getByRole('button', { name: 'Save session login' }));
|
||||
expect(getStoredAuth()).toBeTruthy();
|
||||
expect(await screen.findByText(/Session login saved/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clears browser session on logout', async () => {
|
||||
setStoredAuth('admin', 'secret-pass');
|
||||
renderSettings();
|
||||
await screen.findByText('Session active');
|
||||
await userEvent.setup().click(screen.getByRole('button', { name: 'Clear session' }));
|
||||
expect(getStoredAuth()).toBeNull();
|
||||
expect(await screen.findByText('Session login cleared.')).toBeInTheDocument();
|
||||
clearStoredAuth();
|
||||
});
|
||||
|
||||
it('adds dashboard user via createUser API', async () => {
|
||||
const createSpy = vi.spyOn(api, 'createUser');
|
||||
renderSettings();
|
||||
const accessSection = (await screen.findByText('Access Control')).closest('.settings-section') as HTMLElement;
|
||||
const user = userEvent.setup();
|
||||
const newUserInput = within(accessSection).getAllByPlaceholderText('admin')[1];
|
||||
const newPassInput = within(accessSection).getByPlaceholderText('••••••••');
|
||||
await user.type(newUserInput, 'operator');
|
||||
await user.type(newPassInput, 'op-pass-123');
|
||||
await user.click(within(accessSection).getByRole('button', { name: 'Add User' }));
|
||||
await waitFor(() => {
|
||||
expect(createSpy).toHaveBeenCalledWith('operator', 'op-pass-123');
|
||||
});
|
||||
expect(await screen.findByText('User "operator" added successfully!')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('describes first-run admin credentials in Access Control help', async () => {
|
||||
renderSettings();
|
||||
expect(
|
||||
await screen.findByText(/first server start, credentials are printed once in the server console/i)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/admin/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user