Files
AgentCompany/tests/unit/browser-manager-timeout.test.ts
T

135 lines
3.6 KiB
TypeScript

import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { BrowserAction, BrowserActionResult } from "../../src/main/browser-manager";
import { BrowserManager } from "../../src/main/browser-manager";
const mocks = vi.hoisted(() => ({
runAction: vi.fn(),
fromPartition: vi.fn(),
logWarn: vi.fn(),
}));
vi.mock("electron", () => ({
BrowserWindow: vi.fn(),
session: {
fromPartition: mocks.fromPartition,
},
}));
vi.mock("electron-log/main.js", () => ({
default: {
info: vi.fn(),
warn: mocks.logWarn,
error: vi.fn(),
},
}));
vi.mock("../../src/main/browser/browser-action-executor", () => ({
assertAllowedUrl: vi.fn(),
attachWindowSecurity: vi.fn(),
getPlatformRules: vi.fn(() => ({
loginUrl: "https://x.com/i/flow/login",
cookieOrigins: [],
authenticatedCookieNames: [],
})),
waitForLoad: vi.fn(),
runAction: mocks.runAction,
}));
const ACTION_TIMEOUT_MS = 5 * 60 * 1000;
const tempDirs: string[] = [];
function createSession() {
return {
setPermissionCheckHandler: vi.fn(),
setPermissionRequestHandler: vi.fn(),
clearStorageData: vi.fn(),
clearCache: vi.fn(),
clearAuthCache: vi.fn(),
cookies: {
get: vi.fn(async () => []),
},
};
}
function createAction(id: string): BrowserAction {
return {
id,
socialAccountId: "social-1",
platform: "twitter",
actionType: "post",
payload: { content: "hello" },
};
}
function createActionWindow() {
const win = {
destroyed: false,
close: vi.fn(() => {
win.destroyed = true;
}),
isDestroyed: vi.fn(() => win.destroyed),
};
return win;
}
async function createProfileDir() {
const dir = await mkdtemp(join(tmpdir(), "agentcompany-browser-timeout-"));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
vi.useRealTimers();
mocks.runAction.mockReset();
mocks.fromPartition.mockReset();
mocks.logWarn.mockReset();
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("browser manager action timeout", () => {
it("settles a hung action and starts the next queued action", async () => {
const profileDir = await createProfileDir();
const firstWindow = createActionWindow();
const secondResult: BrowserActionResult = {
success: true,
cancelled: false,
summary: "second action completed",
screenshotPath: null,
error: null,
};
mocks.fromPartition.mockReturnValue(createSession());
mocks.runAction
.mockImplementationOnce((action, _screenshotDir, _session, _cancelledActionIds, activeActionWindows) => {
activeActionWindows.set(action.id, firstWindow);
return new Promise(() => {});
})
.mockResolvedValueOnce(secondResult);
vi.useFakeTimers();
const manager = new BrowserManager(profileDir);
const first = manager.executeAction(createAction("action-1"));
await Promise.resolve();
const second = manager.executeAction(createAction("action-2"));
expect(mocks.runAction).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(ACTION_TIMEOUT_MS);
await expect(first).resolves.toMatchObject({
success: false,
cancelled: true,
summary: "Action timed out and was cancelled.",
screenshotPath: null,
error: "Action timed out.",
});
await expect(second).resolves.toEqual(secondResult);
expect(firstWindow.close).toHaveBeenCalledTimes(1);
expect(mocks.runAction).toHaveBeenCalledTimes(2);
});
});