Ctrl K
ComunidadGuías…

electron/cua-watch-restart.test.mjs

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

Archivo original ↗Preguntar a KiBot
// The Mac driver's watch as cua.mjs wires it: a real start, a socket that stops answering, and a real restart. Nothing here can
// start the operator's own driver: attachStandalone stops at the app it can't find, and no child process exists to launch.
import { mkdtempSync, rmSync } 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: "", alive: true, dead: new Set(), hosts: [], failStarts: 0, granted: true }));
vi.mock("electron", () => ({ app: { isPackaged: false, getPath: () => fixture.home }, ipcMain: { handle: () => {} } }));
vi.mock("@trycua/cua-driver/embedded", () => ({
  EmbeddedCuaDriverHost: class {
    static withOptions() { return new this(); }
    // Each start answers on its own socket, so one driver can stop answering while the next one works.
    constructor() { fixture.hosts.push(this); this.socket = `/fixture/driver-${fixture.hosts.length}.sock`; }
    async start() {
      if (fixture.failStarts > 0) { fixture.failStarts -= 1; throw new Error("the driver could not start"); }
      return { socketPath: this.socket };
    }
    // Main's exit watch (cua.mjs watchEmbeddedHost): this driver never exits, it only stops answering.
    waitForExit() { return new Promise(() => {}); }
    stop = vi.fn(async () => {});
    uniffiDestroy = vi.fn();
  },
}));
vi.mock("@trycua/cua-driver/electron", () => ({
  requestMacOSPermissions: () => ({ accessibility: fixture.granted, screenRecording: fixture.granted }),
  hasRequiredMacOSPermissions: () => fixture.granted,
}));
vi.mock("node:fs", async (original) => {
  const fs = await original();
  return { ...fs, default: { ...fs.default, existsSync: (file) => {
    if (String(file).endsWith(".sock")) return fixture.alive && !fixture.dead.has(String(file));
    if (String(file).includes("CuaDriver.app")) return false;
    return fs.existsSync(file);
  } } };
});
vi.mock("node:net", async (original) => {
  const net = await original();
  const { EventEmitter } = await import("node:events");
  return { ...net, default: { ...net.default, createConnection: () => {
    const socket = Object.assign(new EventEmitter(), { destroy: vi.fn() });
    queueMicrotask(() => socket.emit("connect"));
    return socket;
  } } };
});
vi.mock("./cua-watch.mjs", async (original) => {
  const actual = await original();
  return { ...actual, watchDriver: (options) => actual.watchDriver({ ...options, everyMs: 5, log: () => {} }) };
});
// The helper's own bounded restarts (three tries), quickly.
vi.mock("./cua-restart.mjs", async (original) => {
  const actual = await original();
  return { ...actual, createHelperRestarts: (options) => actual.createHelperRestarts({ ...options, waitsMs: [5, 5, 5], log: () => {} }) };
});

let cua;
beforeEach(async () => {
  Object.assign(fixture, { home: mkdtempSync(join(tmpdir(), "omb-cua-watch-")), alive: true, dead: new Set(), hosts: [], failStarts: 0, granted: true });
  vi.stubEnv("OPENMAUSBOT_CUA_EMBEDDED", "1");
  vi.stubEnv("CUA_DRIVER_PATH", "/fixture/cua-driver");
  vi.resetModules();
  cua = await import("./cua.mjs");
});
afterEach(async () => {
  await cua?.stopCua();
  vi.unstubAllEnvs();
  rmSync(fixture.home, { recursive: true, force: true });
});
const pause = (ms = 60) => new Promise((resolve) => setTimeout(resolve, ms));

describe.skipIf(process.platform !== "darwin")("the Mac driver dies after it started", () => {
  it("leaves a driver that answers alone", async () => {
    await cua.startCua();
    await pause();
    expect(fixture.hosts).toHaveLength(1);
  });

  it("starts it again, and watches the new one", async () => {
    await expect(cua.startCua()).resolves.toMatchObject({ mode: "embedded" });
    fixture.dead.add(fixture.hosts[0].socket);
    await expect.poll(() => fixture.hosts.length).toBe(2);
    expect(fixture.hosts[0].stop).toHaveBeenCalledOnce();
    await pause();
    expect(fixture.hosts).toHaveLength(2);
    fixture.dead.add(fixture.hosts[1].socket);
    await expect.poll(() => fixture.hosts.length).toBe(3);
    await pause();
    expect(fixture.hosts).toHaveLength(3);
  });

  it("tries again when the restart fails, and stops trying after a few", async () => {
    await cua.startCua();
    fixture.failStarts = 100;
    fixture.alive = false;
    await expect.poll(() => fixture.hosts.length).toBe(4);
    await pause(100);
    expect(fixture.hosts).toHaveLength(4);
  });

  it("recovers when a later try works", async () => {
    await cua.startCua();
    fixture.failStarts = 1;
    fixture.alive = false;
    await expect.poll(() => fixture.hosts.length).toBeGreaterThanOrEqual(3);
    fixture.alive = true;
    const { readFileSync } = await import("node:fs");
    await expect.poll(() => JSON.parse(readFileSync(join(fixture.home, "cua-connection.json"), "utf8")).mode).toBe("embedded");
  });

  it("doesn't keep asking when macOS permissions are what's missing", async () => {
    await cua.startCua();
    fixture.granted = false;
    fixture.alive = false;
    await expect.poll(() => fixture.hosts.length).toBe(1);
    await pause(100);
    expect(fixture.hosts).toHaveLength(1);
    expect(JSON.parse((await import("node:fs")).readFileSync(join(fixture.home, "cua-connection.json"), "utf8")).mode).toBe("unavailable");
  });

  it("stops watching on an explicit Stop", async () => {
    await cua.startCua();
    await cua.stopCua();
    fixture.alive = false;
    await pause();
    expect(fixture.hosts).toHaveLength(1);
  });
});