Ctrl K
ComunidadGuías…

electron/cua-windows-isolation.test.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { mkdtempSync, readFileSync, rmSync, mkdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const fixture = vi.hoisted(() => ({ home: "", packaged: false, hosts: [], start: null, handlers: new Map() }));
vi.mock("electron", () => ({
  app: { get isPackaged() { return fixture.packaged; }, getPath: () => fixture.home, getAppPath: () => fixture.home },
  ipcMain: { handle: (name, handler) => fixture.handlers.set(name, handler) },
}));
vi.mock("@trycua/cua-driver/embedded", () => ({
  EmbeddedCuaDriverHost: class {
    constructor(options) { this.binary = options.binaryPath; this.bundleId = options.hostBundleId; this.options = options; fixture.hosts.push(this); }
    static withOptions(options) { return new this(options); }
    waitForExit() { return new Promise(() => {}); }
    start(options) { return fixture.start(options); }
    stop = vi.fn(async () => {});
    uniffiDestroy = vi.fn();
  },
}));
vi.mock("@trycua/cua-driver/electron", () => { throw new Error("macOS permissions loaded on Windows"); });
vi.mock("node:child_process", () => ({ execFile: vi.fn(() => { throw new Error("unowned launch"); }) }));
vi.mock("node:net", () => ({ default: { createConnection: vi.fn(() => { throw new Error("foreign pipe probed"); }) } }));

let cua;
const connection = { socketPath: "\\\\.\\pipe\\fixture-owned-cua" };
beforeEach(async () => {
  fixture.home = mkdtempSync(join(tmpdir(), "omb-cua-win-"));
  fixture.hosts = [];
  fixture.packaged = false;
  fixture.handlers.clear();
  fixture.start = vi.fn(async () => connection);
  vi.stubGlobal("process", new Proxy(process, {
    get(target, key) { return key === "platform" ? "win32" : key === "resourcesPath" ? fixture.home : Reflect.get(target, key); },
  }));
  vi.stubEnv("CUA_DRIVER_PATH", join(fixture.home, "cua-driver.exe"));
  vi.stubEnv("OPENMAUSBOT_CUA_EMBEDDED", "");
  vi.resetModules();
  cua = await import("./cua.mjs");
});
afterEach(async () => {
  await cua?.stopCua();
  vi.unstubAllEnvs();
  vi.unstubAllGlobals();
  rmSync(fixture.home, { recursive: true, force: true });
});

describe("Windows owned CUA host", () => {
  it("uses embedded control even in dev, without a shared pipe or macOS permissions", async () => {
    await expect(cua.startCua()).resolves.toMatchObject({
      mode: "embedded", ...connection,
      mcpArgs: ["mcp", "--embedded", "--socket", connection.socketPath],
      mcpEnv: { CUA_DRIVER_EMBEDDED: "1", CUA_DRIVER_RS_TELEMETRY_ENABLED: "0" },
    });
    expect(fixture.start.mock.calls[0][0].signal).toBeInstanceOf(AbortSignal);
    expect(fixture.hosts[0].binary).toBe(process.env.CUA_DRIVER_PATH);
    await cua.stopCua();
    expect(fixture.hosts[0].stop).toHaveBeenCalledOnce();
    expect(fixture.hosts[0].uniffiDestroy).toHaveBeenCalledOnce();
  });

  it("reports a failed host instead of launching or attaching to an unowned daemon", async () => {
    fixture.start.mockRejectedValue(new Error("fixture start failed"));
    await expect(cua.startCua()).resolves.toMatchObject({
      mode: "unavailable", reason: "embedded host failed: fixture start failed",
    });
    expect(fixture.hosts[0].stop).toHaveBeenCalledOnce();
    expect(fixture.hosts[0].uniffiDestroy).toHaveBeenCalledOnce();
  });

  it("tries a start that failed for a passing reason again by itself, twice, and never a blocked helper", async () => {
    vi.useFakeTimers();
    try {
      const exited = Object.assign(new Error("EmbeddedDriverError.ExitedBeforeReady"), { tag: "ExitedBeforeReady", inner: { code: 1 } });
      fixture.start.mockRejectedValueOnce(exited).mockRejectedValueOnce(exited);
      await expect(cua.startCua()).resolves.toMatchObject({ mode: "unavailable", reason: "embedded host failed: ExitedBeforeReady: code 1" });
      await vi.advanceTimersByTimeAsync(15_000);
      expect(fixture.start).toHaveBeenCalledTimes(2);
      await vi.advanceTimersByTimeAsync(60_000);
      expect(fixture.start).toHaveBeenCalledTimes(3);
      expect(JSON.parse(readFileSync(join(fixture.home, "cua-connection.json"), "utf8"))).toMatchObject({ mode: "embedded" });

      fixture.start.mockReset();
      fixture.start.mockRejectedValue(Object.assign(new Error("EmbeddedDriverError.Spawn"), { tag: "Spawn", inner: { reason: "Access is denied. (os error 5)" } }));
      await expect(cua.startCua()).resolves.toMatchObject({ mode: "unavailable", reason: expect.stringContaining("Access is denied") });
      await vi.advanceTimersByTimeAsync(120_000);
      expect(fixture.start, "a blocked helper waits for the person").toHaveBeenCalledTimes(1);

      fixture.start.mockReset();
      fixture.start.mockRejectedValue(exited);
      await cua.startCua();
      await cua.stopCua();
      await vi.advanceTimersByTimeAsync(120_000);
      expect(fixture.start, "Stop cancels a retry").toHaveBeenCalledTimes(1);
    } finally {
      vi.useRealTimers();
    }
  });

  it("cancels a stalled start when the desktop stops", async () => {
    fixture.start.mockImplementation(({ signal }) => new Promise((_, reject) => {
      signal.addEventListener("abort", () => reject(signal.reason), { once: true });
    }));
    const started = cua.startCua();
    const rejected = expect(started).rejects.toMatchObject({ name: "AbortError" });
    await expect.poll(() => fixture.hosts.length).toBe(1);
    await cua.stopCua();
    await rejected;
    expect(fixture.hosts[0].stop).toHaveBeenCalledOnce();
    expect(fixture.hosts[0].uniffiDestroy).toHaveBeenCalledOnce();
  });

  it("discards late startup without replacing or stopping the new host", async () => {
    let finish;
    fixture.start.mockImplementationOnce(() => new Promise(resolve => { finish = resolve; }));
    const first = cua.startCua();
    const rejected = expect(first).rejects.toMatchObject({ name: "AbortError" });
    await expect.poll(() => fixture.hosts.length).toBe(1);
    await cua.stopCua();
    await cua.startCua();
    finish({ socketPath: "stale-pipe" });
    await rejected;
    expect(fixture.hosts[0].stop).toHaveBeenCalledOnce();
    expect(fixture.hosts[1].stop).not.toHaveBeenCalled();
    expect(JSON.parse(readFileSync(join(fixture.home, "cua-connection.json"), "utf8")).socketPath).toBe(connection.socketPath);
  });

  it("resolves the staged development executable without installing a foreign driver", () => {
    vi.stubEnv("CUA_DRIVER_PATH", "");
    const stage = join(fixture.home, "dist-native", "cua-win32-x64");
    mkdirSync(stage, { recursive: true });
    writeFileSync(join(stage, "cua-driver.exe"), "inert fixture");
    expect(cua.resolveDriverBinary()).toBe(join(stage, "cua-driver.exe"));
  });

  it("uses the background daemon in packages but leaves the CLI proxy unchanged", () => {
    fixture.packaged = true;
    vi.stubEnv("CUA_DRIVER_PATH", "");
    const cli = join(fixture.home, "cua-driver.exe");
    const background = join(fixture.home, "cua-driver-background.exe");
    writeFileSync(cli, "inert CLI fixture");
    writeFileSync(background, "inert GUI fixture");
    expect(cua.resolveDriverBinary()).toBe(cli);
    expect(cua.resolveEmbeddedDriverBinary(cli)).toBe(background);
  });

  it("fails visibly when a package is incomplete rather than flashing a console", () => {
    fixture.packaged = true;
    vi.stubEnv("CUA_DRIVER_PATH", "");
    expect(() => cua.resolveEmbeddedDriverBinary(join(fixture.home, "cua-driver.exe"))).toThrow("background CUA driver is missing");
  });

  it("preserves custom and development executables", () => {
    const binary = process.env.CUA_DRIVER_PATH;
    expect(cua.resolveEmbeddedDriverBinary(binary)).toBe(binary);
    fixture.packaged = true;
    expect(cua.resolveEmbeddedDriverBinary(binary)).toBe(binary);
    vi.stubEnv("CUA_DRIVER_PATH", "");
    const external = join(fixture.home, "external", "cua-driver.exe");
    expect(cua.resolveEmbeddedDriverBinary(external)).toBe(external);
  });
});