/** * Unified wizard integration test — exercises the `skipGateway: false` code * path so that wiring between `runWizard` and the two gateway stages is * covered. The gateway stages themselves are mocked (they require a real * daemon + network) but the dynamic imports and option plumbing are real. */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { mkdtempSync, rmSync, cpSync, existsSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { HeadlessPrompter } from '../../src/prompter/headless-prompter.js'; import { createConfigService } from '../../src/config/config-service.js'; const gatewayConfigMock = vi.fn(); const gatewayBootstrapMock = vi.fn(); vi.mock('../../src/stages/gateway-config.js', () => ({ gatewayConfigStage: (...args: unknown[]) => gatewayConfigMock(...args), })); vi.mock('../../src/stages/gateway-bootstrap.js', () => ({ gatewayBootstrapStage: (...args: unknown[]) => gatewayBootstrapMock(...args), })); // Import AFTER the mocks so runWizard picks up the mocked stage modules. import { runWizard } from '../../src/wizard.js'; describe('Unified wizard (runWizard with default skipGateway)', () => { let tmpDir: string; const repoRoot = join(import.meta.dirname, '..', '..'); const originalIsTTY = process.stdin.isTTY; const originalAssumeYes = process.env['MOSAIC_ASSUME_YES']; beforeEach(() => { tmpDir = mkdtempSync(join(tmpdir(), 'mosaic-unified-wizard-')); const candidates = [join(repoRoot, 'framework', 'templates'), join(repoRoot, 'templates')]; for (const templatesDir of candidates) { if (existsSync(templatesDir)) { cpSync(templatesDir, join(tmpDir, 'templates'), { recursive: true }); break; } } gatewayConfigMock.mockReset(); gatewayBootstrapMock.mockReset(); // Pretend we're on an interactive TTY so the wizard's headless-abort // branch does not call `process.exit(1)` during these tests. Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true }); delete process.env['MOSAIC_ASSUME_YES']; }); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true, }); if (originalAssumeYes === undefined) { delete process.env['MOSAIC_ASSUME_YES']; } else { process.env['MOSAIC_ASSUME_YES'] = originalAssumeYes; } }); it('invokes the gateway config + bootstrap stages by default', async () => { gatewayConfigMock.mockResolvedValue({ ready: true, host: 'localhost', port: 14242 }); gatewayBootstrapMock.mockResolvedValue({ completed: true }); const prompter = new HeadlessPrompter({ 'Installation mode': 'quick', 'What name should agents use?': 'TestBot', 'Communication style': 'direct', 'Your name': 'Tester', 'Your pronouns': 'They/Them', 'Your timezone': 'UTC', }); await runWizard({ mosaicHome: tmpDir, sourceDir: tmpDir, prompter, configService: createConfigService(tmpDir, tmpDir), gatewayHost: 'localhost', gatewayPort: 14242, skipGatewayNpmInstall: true, }); expect(gatewayConfigMock).toHaveBeenCalledTimes(1); expect(gatewayBootstrapMock).toHaveBeenCalledTimes(1); const configCall = gatewayConfigMock.mock.calls[0]; expect(configCall[2]).toMatchObject({ host: 'localhost', defaultPort: 14242, skipInstall: true, }); const bootstrapCall = gatewayBootstrapMock.mock.calls[0]; expect(bootstrapCall[2]).toMatchObject({ host: 'localhost', port: 14242 }); }); it('does not invoke bootstrap when config stage reports not ready', async () => { gatewayConfigMock.mockResolvedValue({ ready: false }); const prompter = new HeadlessPrompter({ 'Installation mode': 'quick', 'What name should agents use?': 'TestBot', 'Communication style': 'direct', 'Your name': 'Tester', 'Your pronouns': 'They/Them', 'Your timezone': 'UTC', }); await runWizard({ mosaicHome: tmpDir, sourceDir: tmpDir, prompter, configService: createConfigService(tmpDir, tmpDir), skipGatewayNpmInstall: true, }); expect(gatewayConfigMock).toHaveBeenCalledTimes(1); expect(gatewayBootstrapMock).not.toHaveBeenCalled(); }); it('respects skipGateway: true', async () => { const prompter = new HeadlessPrompter({ 'Installation mode': 'quick', 'What name should agents use?': 'TestBot', 'Communication style': 'direct', 'Your name': 'Tester', 'Your pronouns': 'They/Them', 'Your timezone': 'UTC', }); await runWizard({ mosaicHome: tmpDir, sourceDir: tmpDir, prompter, configService: createConfigService(tmpDir, tmpDir), skipGateway: true, }); expect(gatewayConfigMock).not.toHaveBeenCalled(); expect(gatewayBootstrapMock).not.toHaveBeenCalled(); }); });