import { beforeEach, describe, expect, it, vi } from 'vitest'; vi.mock('electron', () => ({ app: { getPath: () => '/tmp', isPackaged: false, }, utilityProcess: {}, })); vi.mock('@electron/utils/logger', () => ({ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), }, })); vi.mock('@electron/utils/config', () => ({ PORTS: { OPENCLAW_GATEWAY: 18789 }, })); describe('GatewayManager gatewayReady fallback', () => { beforeEach(() => { vi.useFakeTimers(); vi.clearAllMocks(); }); afterEach(() => { vi.useRealTimers(); }); it('sets gatewayReady=false when entering starting state', async () => { vi.resetModules(); const { GatewayManager } = await import('@electron/gateway/manager'); const manager = new GatewayManager(); const statusUpdates: Array<{ gatewayReady?: boolean }> = []; manager.on('status', (status: { gatewayReady?: boolean }) => { statusUpdates.push({ gatewayReady: status.gatewayReady }); }); // Simulate start attempt (will fail but we can check the initial status) try { await manager.start(); } catch { // expected to fail — no actual gateway process } const startingUpdate = statusUpdates.find((u) => u.gatewayReady === false); expect(startingUpdate).toBeDefined(); }); it('emits gatewayReady=true when gateway:ready event is received', async () => { vi.resetModules(); const { GatewayManager } = await import('@electron/gateway/manager'); const manager = new GatewayManager(); // Force internal state to 'running' for the test const stateController = (manager as unknown as { stateController: { setStatus: (u: Record) => void } }).stateController; stateController.setStatus({ state: 'running', connectedAt: Date.now() }); const statusUpdates: Array<{ gatewayReady?: boolean; state: string }> = []; manager.on('status', (status: { gatewayReady?: boolean; state: string }) => { statusUpdates.push({ gatewayReady: status.gatewayReady, state: status.state }); }); manager.emit('gateway:ready', {}); const readyUpdate = statusUpdates.find((u) => u.gatewayReady === true); expect(readyUpdate).toBeDefined(); }); it('auto-sets gatewayReady=true after fallback timeout if no event received', async () => { vi.resetModules(); const { GatewayManager } = await import('@electron/gateway/manager'); const manager = new GatewayManager(); // Force internal state to 'running' without gatewayReady const stateController = (manager as unknown as { stateController: { setStatus: (u: Record) => void } }).stateController; stateController.setStatus({ state: 'running', connectedAt: Date.now() }); const statusUpdates: Array<{ gatewayReady?: boolean }> = []; manager.on('status', (status: { gatewayReady?: boolean }) => { statusUpdates.push({ gatewayReady: status.gatewayReady }); }); // Call the private scheduleGatewayReadyFallback method (manager as unknown as { scheduleGatewayReadyFallback: () => void }).scheduleGatewayReadyFallback(); // Before timeout, no gatewayReady update vi.advanceTimersByTime(29_000); expect(statusUpdates.find((u) => u.gatewayReady === true)).toBeUndefined(); // After 30s fallback timeout vi.advanceTimersByTime(2_000); const readyUpdate = statusUpdates.find((u) => u.gatewayReady === true); expect(readyUpdate).toBeDefined(); }); it('cancels fallback timer when gateway:ready event arrives first', async () => { vi.resetModules(); const { GatewayManager } = await import('@electron/gateway/manager'); const manager = new GatewayManager(); const stateController = (manager as unknown as { stateController: { setStatus: (u: Record) => void } }).stateController; stateController.setStatus({ state: 'running', connectedAt: Date.now() }); const statusUpdates: Array<{ gatewayReady?: boolean }> = []; manager.on('status', (status: { gatewayReady?: boolean }) => { statusUpdates.push({ gatewayReady: status.gatewayReady }); }); // Schedule fallback (manager as unknown as { scheduleGatewayReadyFallback: () => void }).scheduleGatewayReadyFallback(); // gateway:ready event arrives at 5s vi.advanceTimersByTime(5_000); manager.emit('gateway:ready', {}); expect(statusUpdates.filter((u) => u.gatewayReady === true)).toHaveLength(1); // After 30s, no duplicate gatewayReady=true vi.advanceTimersByTime(30_000); expect(statusUpdates.filter((u) => u.gatewayReady === true)).toHaveLength(1); }); });