152 lines
4.8 KiB
TypeScript
152 lines
4.8 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
vi.mock('electron', () => ({
|
|
app: {
|
|
getPath: () => '/tmp',
|
|
isPackaged: false,
|
|
},
|
|
utilityProcess: {
|
|
fork: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
describe('GatewayManager heartbeat recovery', () => {
|
|
const originalPlatform = process.platform;
|
|
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
vi.clearAllMocks();
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date('2026-03-19T00:00:00.000Z'));
|
|
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
|
});
|
|
|
|
it('restarts after consecutive heartbeat misses reach threshold', async () => {
|
|
const { GatewayManager } = await import('@electron/gateway/manager');
|
|
const manager = new GatewayManager();
|
|
|
|
const ws = {
|
|
readyState: 1, // WebSocket.OPEN
|
|
ping: vi.fn(),
|
|
terminate: vi.fn(),
|
|
on: vi.fn(),
|
|
};
|
|
|
|
(manager as unknown as { ws: typeof ws }).ws = ws;
|
|
(manager as unknown as { shouldReconnect: boolean }).shouldReconnect = true;
|
|
(manager as unknown as { status: { state: string; port: number } }).status = {
|
|
state: 'running',
|
|
port: 18789,
|
|
};
|
|
const restartSpy = vi.spyOn(manager, 'restart').mockResolvedValue();
|
|
|
|
(manager as unknown as { startPing: () => void }).startPing();
|
|
|
|
vi.advanceTimersByTime(120_000);
|
|
|
|
expect(ws.ping).toHaveBeenCalledTimes(3);
|
|
expect(ws.terminate).not.toHaveBeenCalled();
|
|
expect(restartSpy).toHaveBeenCalledTimes(1);
|
|
|
|
(manager as unknown as { connectionMonitor: { clear: () => void } }).connectionMonitor.clear();
|
|
});
|
|
|
|
it('does not restart when heartbeat is recovered by incoming messages', async () => {
|
|
const { GatewayManager } = await import('@electron/gateway/manager');
|
|
const manager = new GatewayManager();
|
|
|
|
const ws = {
|
|
readyState: 1, // WebSocket.OPEN
|
|
ping: vi.fn(),
|
|
terminate: vi.fn(),
|
|
on: vi.fn(),
|
|
};
|
|
|
|
(manager as unknown as { ws: typeof ws }).ws = ws;
|
|
(manager as unknown as { shouldReconnect: boolean }).shouldReconnect = true;
|
|
(manager as unknown as { status: { state: string; port: number } }).status = {
|
|
state: 'running',
|
|
port: 18789,
|
|
};
|
|
const restartSpy = vi.spyOn(manager, 'restart').mockResolvedValue();
|
|
|
|
(manager as unknown as { startPing: () => void }).startPing();
|
|
|
|
vi.advanceTimersByTime(30_000); // ping #1
|
|
vi.advanceTimersByTime(30_000); // miss #1 + ping #2
|
|
(manager as unknown as { handleMessage: (message: unknown) => void }).handleMessage('alive');
|
|
|
|
vi.advanceTimersByTime(30_000); // recovered, ping #3
|
|
vi.advanceTimersByTime(30_000); // miss #1 + ping #4
|
|
vi.advanceTimersByTime(30_000); // miss #2 + ping #5
|
|
|
|
expect(ws.terminate).not.toHaveBeenCalled();
|
|
expect(restartSpy).not.toHaveBeenCalled();
|
|
|
|
(manager as unknown as { connectionMonitor: { clear: () => void } }).connectionMonitor.clear();
|
|
});
|
|
|
|
it('skips heartbeat recovery when auto-reconnect is disabled', async () => {
|
|
const { GatewayManager } = await import('@electron/gateway/manager');
|
|
const manager = new GatewayManager();
|
|
|
|
const ws = {
|
|
readyState: 1,
|
|
ping: vi.fn(),
|
|
terminate: vi.fn(),
|
|
on: vi.fn(),
|
|
};
|
|
|
|
(manager as unknown as { ws: typeof ws }).ws = ws;
|
|
(manager as unknown as { shouldReconnect: boolean }).shouldReconnect = false;
|
|
(manager as unknown as { status: { state: string; port: number } }).status = {
|
|
state: 'running',
|
|
port: 18789,
|
|
};
|
|
const restartSpy = vi.spyOn(manager, 'restart').mockResolvedValue();
|
|
|
|
(manager as unknown as { startPing: () => void }).startPing();
|
|
|
|
vi.advanceTimersByTime(120_000);
|
|
|
|
expect(restartSpy).not.toHaveBeenCalled();
|
|
|
|
(manager as unknown as { connectionMonitor: { clear: () => void } }).connectionMonitor.clear();
|
|
});
|
|
|
|
it('keeps heartbeat recovery disabled on windows', async () => {
|
|
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
|
|
const { GatewayManager } = await import('@electron/gateway/manager');
|
|
const manager = new GatewayManager();
|
|
|
|
const ws = {
|
|
readyState: 1,
|
|
ping: vi.fn(),
|
|
terminate: vi.fn(),
|
|
on: vi.fn(),
|
|
};
|
|
|
|
(manager as unknown as { ws: typeof ws }).ws = ws;
|
|
(manager as unknown as { shouldReconnect: boolean }).shouldReconnect = true;
|
|
(manager as unknown as { status: { state: string; port: number } }).status = {
|
|
state: 'running',
|
|
port: 18789,
|
|
};
|
|
const restartSpy = vi.spyOn(manager, 'restart').mockResolvedValue();
|
|
|
|
(manager as unknown as { startPing: () => void }).startPing();
|
|
|
|
vi.advanceTimersByTime(400_000);
|
|
|
|
expect(restartSpy).not.toHaveBeenCalled();
|
|
|
|
(manager as unknown as { connectionMonitor: { clear: () => void } }).connectionMonitor.clear();
|
|
});
|
|
});
|