claude-code-best/packages/remote-control-server/src/__tests__/disconnect-monitor.test.ts
Cheng Zi Feng d1176325a4 fix(remote-control): harden self-hosted session flows (#278)
Co-authored-by: chengzifeng <chengzifeng@meituan.com>
2026-06-04 23:50:24 +08:00

109 lines
3.2 KiB
TypeScript

import { describe, test, expect, beforeEach, mock } from "bun:test";
// Mock config with very short timeout for testing
const mockConfig = {
port: 3000,
host: "0.0.0.0",
apiKeys: ["test-api-key"],
baseUrl: "http://localhost:3000",
pollTimeout: 8,
heartbeatInterval: 20,
jwtExpiresIn: 3600,
disconnectTimeout: 300,
};
mock.module("../config", () => ({
config: mockConfig,
getBaseUrl: () => "http://localhost:3000",
}));
import {
storeReset,
storeCreateEnvironment,
storeUpdateEnvironment,
storeCreateSession,
storeUpdateSession,
storeGetEnvironment,
storeGetSession,
} from "../store";
import { getEventBus, getAllEventBuses, removeEventBus } from "../transport/event-bus";
import { runDisconnectMonitorSweep } from "../services/disconnect-monitor";
describe("Disconnect Monitor Logic", () => {
beforeEach(() => {
storeReset();
for (const [key] of getAllEventBuses()) {
removeEventBus(key);
}
});
test("environment times out when lastPollAt is too old", () => {
const env = storeCreateEnvironment({ secret: "s" });
const timeoutMs = 300 * 1000; // 5 minutes
// Simulate lastPollAt being 6 minutes ago
const oldDate = new Date(Date.now() - timeoutMs - 60000);
storeUpdateEnvironment(env.id, { lastPollAt: oldDate });
runDisconnectMonitorSweep();
const updated = storeGetEnvironment(env.id);
expect(updated?.status).toBe("disconnected");
});
test("environment stays active when lastPollAt is recent", () => {
const env = storeCreateEnvironment({ secret: "s" });
runDisconnectMonitorSweep();
const updated = storeGetEnvironment(env.id);
expect(updated?.status).toBe("active");
});
test("session becomes inactive when updatedAt is too old", () => {
const session = storeCreateSession({ status: "idle" } as any);
storeUpdateSession(session.id, { status: "running" });
const rec = storeGetSession(session.id);
expect(rec).toBeTruthy();
if (!rec) return;
rec.updatedAt = new Date(Date.now() - 300 * 1000 * 2 - 60000);
runDisconnectMonitorSweep();
const updated = storeGetSession(session.id);
expect(updated?.status).toBe("inactive");
});
test("session stays running when recently updated", () => {
const session = storeCreateSession({});
storeUpdateSession(session.id, { status: "running" });
runDisconnectMonitorSweep();
const updated = storeGetSession(session.id);
expect(updated?.status).toBe("running");
});
test("session timeout publishes an inactive session_status event", () => {
const session = storeCreateSession({});
storeUpdateSession(session.id, { status: "idle" });
const rec = storeGetSession(session.id);
expect(rec).toBeTruthy();
if (!rec) return;
rec.updatedAt = new Date(Date.now() - 300 * 1000 * 2 - 60000);
const bus = getEventBus(session.id);
const events: Array<{ type: string; payload: { status?: string } }> = [];
bus.subscribe((event) => {
events.push({ type: event.type, payload: event.payload as { status?: string } });
});
runDisconnectMonitorSweep();
expect(events).toContainEqual({
type: "session_status",
payload: { status: "inactive" },
});
});
});