Files
AetherForge/server/web/src/pages/SettingsPage.test.tsx
AetherForge 1413a9394f Add pool presets, operator bake-ins, and USB pack improvements
- Pool preset checkboxes with failover in Calibrate and Forge

- Tier 1/2 UX: setup banner, forge next worker, LAN defaults, blueprint prompt

- USB: auto-install forge tools, seed config.json, sync LAUNCH.bat
2026-06-01 21:06:19 -07:00

149 lines
6.6 KiB
TypeScript

/**
* @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.*Save Calibration/i)
).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();
});
});