Menu
popagent
publicLatest change 6edc7e928c57dab23c0973ed3f7ee70b3ebb72d5 - Initial popagent baseline by AkurAI Build
import { describe, expect, test } from "bun:test";
import { BrowserSessionRegistry, type ManagedBrowser } from "./browser-sessions";
function fakeBrowser() {
const closed: string[] = [];
const browser: ManagedBrowser = {
getState: async () => ({
currentUrl: "https://example.com",
tabs: [{ id: "tab-1", url: "https://example.com", title: "Example" }],
activeTabIndex: 0,
}),
closeThreadSession: async (threadId) => { closed.push(threadId); },
startScreencast: async () => { throw new Error("not used"); },
injectMouseEvent: async () => undefined,
injectKeyboardEvent: async () => undefined,
};
return { browser, closed };
}
describe("BrowserSessionRegistry", () => {
test("tracks thread browser state and activity", async () => {
let now = 1_000;
const registry = new BrowserSessionRegistry(() => now);
const { browser } = fakeBrowser();
registry.configure({ maxSessions: 4, idleTimeoutMs: 60_000 });
await registry.touch("thread-1", "interactive", browser);
now += 500;
await registry.touch("thread-1", "interactive", browser);
expect(await registry.list()).toEqual([
expect.objectContaining({
id: "interactive:thread-1",
threadId: "thread-1",
access: "interactive",
status: "active",
currentUrl: "https://example.com",
lastActivityAt: new Date(now).toISOString(),
}),
]);
});
test("closes the least-recent session at the configured limit", async () => {
let now = 1_000;
const registry = new BrowserSessionRegistry(() => now);
registry.configure({ maxSessions: 1, idleTimeoutMs: 60_000 });
const first = fakeBrowser();
const second = fakeBrowser();
await registry.touch("old", "read-only", first.browser);
now += 1_000;
await registry.touch("new", "interactive", second.browser);
expect(first.closed).toEqual(["old"]);
expect((await registry.list()).map((session) => session.id)).toEqual(["interactive:new"]);
});
test("cleans idle sessions and supports explicit close", async () => {
let now = 1_000;
const registry = new BrowserSessionRegistry(() => now);
registry.configure({ maxSessions: 4, idleTimeoutMs: 60_000 });
const first = fakeBrowser();
const second = fakeBrowser();
await registry.touch("idle", "read-only", first.browser);
now += 61_000;
await registry.cleanupIdle();
expect(first.closed).toEqual(["idle"]);
await registry.touch("manual", "interactive", second.browser);
expect(await registry.close("interactive:manual")).toBe(true);
expect(await registry.close("missing")).toBe(false);
expect(second.closed).toEqual(["manual"]);
});
test("denies input injection unless takeover is explicitly enabled", async () => {
const registry = new BrowserSessionRegistry();
registry.configure({ maxSessions: 4, idleTimeoutMs: 60_000 });
const { browser } = fakeBrowser();
await registry.touch("thread", "interactive", browser);
await expect(registry.injectMouse("interactive:thread", { type: "mouseMoved", x: 1, y: 2 })).rejects.toThrow("takeover");
expect(registry.setTakeover("interactive:thread", true)).toBe(true);
await expect(registry.injectMouse("interactive:thread", { type: "mouseMoved", x: 1, y: 2 })).resolves.toBeUndefined();
});
});