Files
NianToB/tests/unit/gateway-ready-fallback.test.ts
Haze 6bacbd964d perf(gateway): shorten gateway.ready fallback timeout from 30s to 5s
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.
2026-04-24 17:04:10 +08:00

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);
});
});