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, webCorsOrigins: [], wsIdleTimeout: 30, wsKeepaliveInterval: 20, }; 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({}); 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" }, }); }); });