135 lines
3.6 KiB
TypeScript
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);
|
|
});
|
|
});
|