The fallback exists as a safety net for the server-side gateway.ready event. In practice OpenClaw's plugin bootstrap can push the real event well past 30s (observed: handshake completes, then 30s tick by, then the fallback fires with no event having arrived). That long tail kept the stale gating code blocking UI state for the full 30s. Step 1 moved sessions.list off the gatewayReady gate, so this value now only matters as a belt-and-braces signal for any future consumer. 5s is long enough to preserve "event wins when it actually fires on a healthy boot" while avoiding a multi-second stall whenever the server is slow. Updated gateway-ready-fallback.test.ts to advance timers around the new boundary.
128 lines
4.6 KiB
TypeScript
128 lines
4.6 KiB
TypeScript
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<string, unknown>) => 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<string, unknown>) => 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(4_000);
|
|
expect(statusUpdates.find((u) => u.gatewayReady === true)).toBeUndefined();
|
|
|
|
// After fallback timeout (5s)
|
|
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<string, unknown>) => 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 before fallback (at 1s, well under 5s)
|
|
vi.advanceTimersByTime(1_000);
|
|
manager.emit('gateway:ready', {});
|
|
expect(statusUpdates.filter((u) => u.gatewayReady === true)).toHaveLength(1);
|
|
|
|
// Well past the fallback window, no duplicate gatewayReady=true
|
|
vi.advanceTimersByTime(10_000);
|
|
expect(statusUpdates.filter((u) => u.gatewayReady === true)).toHaveLength(1);
|
|
});
|
|
});
|