Ctrl K
ComunidadGuías…

electron/included-x.test.mjs

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

Archivo original ↗Preguntar a KiBot
import { describe, expect, it, vi } from "vitest";
import { createDesktopIncludedX, createIncludedXSync } from "./included-x.mjs";

const access = { url: "https://cloud.openmausbot.com/api/cloud/services/x", token: `omb_xd_${"a".repeat(43)}` };
const paid = (deviceId = "device-1") => ({ status: "connected", deviceId, entitlement: { plan: "pro", status: "active", tier: "pro" } });

function sync(fetchAccess = vi.fn(async () => access)) {
  const sent = [], notes = [];
  return { sent, notes, fetchAccess, x: createIncludedXSync({ fetchAccess, send: (value, note) => { sent.push(value); notes.push(note ?? null); } }) };
}

describe("included X research access on the desktop", () => {
  it("fetches this sign-in's token once the plan is paid, and hands it to the server", async () => {
    const { x, sent, fetchAccess } = sync();
    await x.onState(paid());
    expect(fetchAccess).toHaveBeenCalledTimes(1);
    expect(sent).toEqual([access]);
  });

  it("does not fetch again on every refresh of the same paid sign-in (a new token would replace the one in use)", async () => {
    const { x, sent, fetchAccess } = sync();
    await x.onState(paid());
    await x.onState(paid());
    await x.onState({ ...paid(), checking: true });
    expect(fetchAccess).toHaveBeenCalledTimes(1);
    expect(sent).toEqual([access]);
  });

  it("clears the server's token when the plan stops being active, the app signs out or must sign in again", async () => {
    for (const next of [{ ...paid(), entitlement: { plan: "pro", status: "inactive", tier: "pro" } }, { status: "signed-out" }, { status: "reauth-required", message: "expired" },
      { status: "connected", deviceId: "device-1", entitlement: { plan: "free", status: "inactive", tier: null } }]) {
      const { x, sent } = sync();
      await x.onState(paid());
      await x.onState(next);
      expect(sent, JSON.stringify(next)).toEqual([access, null]);
    }
  });

  it("fetches a new token for a new sign-in on this computer", async () => {
    const { x, sent, fetchAccess } = sync();
    await x.onState(paid("device-1"));
    await x.onState(paid("device-2"));
    expect(fetchAccess).toHaveBeenCalledTimes(2);
    expect(sent).toEqual([access, access]);
  });

  it("a change of account with the Admin down: the account before's token doesn't stay with the server", async () => {
    const fetchAccess = vi.fn(async () => access);
    const { x, sent } = sync(fetchAccess);
    await x.onState(paid("device-a"));
    fetchAccess.mockRejectedValueOnce(new Error("offline"));
    await x.onState(paid("device-b"));
    expect(sent).toEqual([access, null]);
    await x.onState(paid("device-b"));
    expect(sent).toEqual([access, null, access]);
  });

  it("sends nothing for a free account, and null when the Admin says the plan is not active", async () => {
    const free = sync();
    await free.x.onState({ status: "connected", deviceId: "d", entitlement: { plan: "free", status: "inactive", tier: null } });
    expect(free.sent).toEqual([]);
    expect(free.fetchAccess).not.toHaveBeenCalled();
    const refused = sync(vi.fn(async () => null));
    await refused.x.onState(paid());
    expect(refused.sent).toEqual([null]);
    expect(refused.notes).toEqual([{ offered: false }]);
  });

  it("keeps trying on later state changes after a failed fetch, without sending anything for it", async () => {
    const fetchAccess = vi.fn().mockRejectedValueOnce(new Error("offline")).mockResolvedValueOnce(access);
    const { x, sent } = sync(fetchAccess);
    await x.onState(paid());
    expect(sent).toEqual([]);
    await x.onState(paid());
    expect(sent).toEqual([access]);
  });

  it("re-sends the token it holds when the server starts again, and nothing when it holds none", async () => {
    const { x, sent } = sync();
    x.serverStarted();
    expect(sent).toEqual([]);
    await x.onState(paid());
    x.serverStarted();
    expect(sent).toEqual([access, access]);
  });

  it("asks once while a fetch is already under way", async () => {
    let finish;
    const fetchAccess = vi.fn(() => new Promise((resolve) => { finish = resolve; }));
    const { x, sent } = sync(fetchAccess);
    const first = x.onState(paid()), second = x.onState(paid());
    finish(access);
    await Promise.all([first, second]);
    expect(fetchAccess).toHaveBeenCalledTimes(1);
    expect(sent).toEqual([access]);
  });
});

describe("when the Admin says no to a paid sign-in", () => {
  const versioned = (version) => ({ ...paid(), entitlement: { plan: "pro", status: "active", tier: "pro", version } });

  it("asks once for that sign-in and plan version, not on every refresh, and asks again when the plan changes", async () => {
    const { x, sent, fetchAccess } = sync(vi.fn(async () => null));
    await x.onState(versioned(1));
    await x.onState(versioned(1));
    await x.onState({ ...versioned(1), checking: true });
    expect(fetchAccess).toHaveBeenCalledTimes(1);
    expect(sent).toEqual([null]);
    fetchAccess.mockResolvedValueOnce(access);
    await x.onState(versioned(2));
    expect(fetchAccess).toHaveBeenCalledTimes(2);
    expect(sent).toEqual([null, access]);
  });

  it("clears the server's not-offered note once the plan stops or the app signs out, so the card offers the way back", async () => {
    const { x, sent, notes } = sync(vi.fn(async () => null));
    await x.onState(versioned(1));
    await x.onState({ status: "signed-out" });
    expect(sent).toEqual([null, null]);
    expect(notes).toEqual([{ offered: false }, null]);
  });

  it("sends nothing for a fetch that ends after the plan stopped, or after another sign-in took over", async () => {
    let finish;
    const fetchAccess = vi.fn(() => new Promise((resolve) => { finish = resolve; }));
    const lapsed = sync(fetchAccess);
    const asking = lapsed.x.onState(paid());
    await lapsed.x.onState({ status: "signed-out" });
    finish(access);
    await asking;
    expect(lapsed.sent).toEqual([]);
    const other = sync(vi.fn().mockImplementationOnce(() => new Promise((resolve) => { finish = resolve; })).mockResolvedValueOnce(access));
    const first = other.x.onState(paid("device-1"));
    void other.x.onState(paid("device-2"));
    finish(access);
    await first;
    await vi.waitFor(() => expect(other.fetchAccess).toHaveBeenCalledTimes(2));
    await vi.waitFor(() => expect(other.sent).toEqual([access]));
  });
});

describe("main.mjs's wiring of included X research access", () => {
  it("asks the current sign-in and posts its answer to whichever server is running, re-sending to a restarted one", async () => {
    const posted = [], logged = [];
    let account = null, proc = null;
    const x = createDesktopIncludedX({ account: () => account, server: () => proc, log: (line) => logged.push(line) });
    await x.onState(paid());
    expect(posted).toEqual([]);
    account = { xResearchAccess: vi.fn(async () => null) };
    proc = { postMessage: (message) => posted.push(message) };
    await x.onState(paid("device-2"));
    expect(posted).toEqual([{ type: "openmausbot:included-x", access: null, offered: false }]);
    account.xResearchAccess.mockResolvedValue(access);
    await x.onState(paid("device-3"));
    proc = { postMessage: (message) => posted.push({ restarted: message }) };
    x.serverStarted();
    expect(posted.slice(1)).toEqual([{ type: "openmausbot:included-x", access }, { restarted: { type: "openmausbot:included-x", access } }]);
    proc = { postMessage: () => { throw new Error("port closed"); } };
    x.serverStarted();
    expect(logged).toEqual(["X research access sync failed: port closed"]);
  });
});