Ctrl K
ComunidadGuías…

server/bot-cwd.test.ts

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

Archivo original ↗Preguntar a KiBot
import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { homedir, tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterAll, describe, expect, it } from "vitest";

import { folderToFollow, inMyFolderCwd, sharedProjectFolder, validateBotCwd } from "./bot-cwd.ts";

const dir = mkdtempSync(join(tmpdir(), "omb-cwd-"));
afterAll(() => rmSync(dir, { recursive: true, force: true }));

describe("validateBotCwd", () => {
  it("accepts an existing absolute directory", () => {
    expect(validateBotCwd(dir)).toEqual({ ok: true, cwd: dir });
  });

  it("treats null and empty as clearing the folder", () => {
    expect(validateBotCwd(null)).toEqual({ ok: true, cwd: null });
    expect(validateBotCwd("")).toEqual({ ok: true, cwd: null });
    expect(validateBotCwd("   ")).toEqual({ ok: true, cwd: null });
  });

  it("expands a leading ~ to the home folder", () => {
    // compare against homedir() itself: a Windows home like C:\Users\RUNNER~1
    // legitimately contains "~", so "no ~ in the output" is not a valid check
    expect(validateBotCwd("~")).toEqual({ ok: true, cwd: resolve(homedir()) });
  });

  it("rejects relative paths, files, and missing folders with a reason", () => {
    expect(validateBotCwd("relative/path")).toEqual({ ok: false, error: expect.stringMatching(/absolute/) });
    const file = join(dir, "a-file.txt");
    writeFileSync(file, "x");
    expect(validateBotCwd(file)).toEqual({ ok: false, error: expect.stringMatching(/not a folder/) });
    expect(validateBotCwd(join(dir, "nope"))).toEqual({ ok: false, error: expect.stringMatching(/doesn't exist/) });
    expect(validateBotCwd(42)).toEqual({ ok: false, error: expect.stringMatching(/path/) });
  });
});

describe("sharedProjectFolder", () => {
  const data = join(dir, "omb-data");
  const project = join(dir, "omb-data-site");
  const privateWorkspace = join(data, "task-workspaces", "bot", "thread");
  mkdirSync(join(data, "task-workspaces", "bot", "thread"), { recursive: true });
  mkdirSync(project);
  const linkKind = process.platform === "win32" ? "junction" : "dir";
  const intoData = join(dir, "site-link-into-data");
  const outside = join(dir, "omb-outside-site");
  const anotherOutside = join(dir, "omb-another-outside");
  mkdirSync(outside);
  mkdirSync(anotherOutside);
  try { symlinkSync(privateWorkspace, intoData, linkKind); } catch {}
  const outLink = join(dir, "site-link-outside");
  try { symlinkSync(outside, outLink, linkKind); } catch {}
  const linked = existsSync(intoData) && existsSync(outLink);

  it("shares an existing folder outside the data folder, even one whose name starts the same", () => {
    expect(sharedProjectFolder(project, data)).toBe(project);
  });

  it("never shares the data folder, a private workspace in it, a missing folder or no folder", () => {
    expect(sharedProjectFolder(data, data)).toBeUndefined();
    expect(sharedProjectFolder(privateWorkspace, data)).toBeUndefined();
    expect(sharedProjectFolder(join(dir, "gone"), data)).toBeUndefined();
    expect(sharedProjectFolder(null, data)).toBeUndefined();
    expect(sharedProjectFolder(undefined, data)).toBeUndefined();
  });

  it.runIf(linked)("never shares a folder whose link resolves into the data folder, while a link out stays shareable", () => {
    expect(sharedProjectFolder(intoData, data)).toBeUndefined();
    expect(sharedProjectFolder(outLink, data)).toBe(realpathSync(outside));
  });

  it.runIf(linked)("returns the folder a link resolved to at check time, so retargeting it cannot move a shared task", () => {
    expect(sharedProjectFolder(outLink, data)).toBe(realpathSync(outside));
    rmSync(outLink);
    symlinkSync(join(dir, "omb-another-outside"), outLink, linkKind);
    expect(realpathSync(outLink)).not.toBe(realpathSync(outside));
    expect(sharedProjectFolder(outLink, data)).toBe(realpathSync(join(dir, "omb-another-outside")));
  });

  it.runIf(linked)("checks the data root as it really resolves, so a linked data folder is still a boundary", () => {
    const linkedData = join(dir, "omb-data-link");
    try { symlinkSync(data, linkedData, linkKind); } catch {}
    if (!existsSync(linkedData)) return;
    expect(sharedProjectFolder(privateWorkspace, linkedData)).toBeUndefined();
    expect(sharedProjectFolder(project, linkedData)).toBe(project);
  });

  it.runIf(linked)("still validates a linked folder as a plain working folder", () => {
    expect(validateBotCwd(intoData)).toEqual({ ok: true, cwd: intoData });
  });
});

describe("inMyFolderCwd", () => {
  const data = join(dir, "omb-data");
  const project = join(dir, "omb-data-site");
  const privateFolder = join(data, "task-workspaces", "bot", "thread");

  it("shares the sender's project folder outside a room unless asked not to", () => {
    expect(inMyFolderCwd(undefined, true, project, data)).toEqual({ cwd: project });
    expect(inMyFolderCwd(true, true, project, data)).toEqual({ cwd: project });
    expect(inMyFolderCwd(false, true, project, data)).toEqual({});
  });

  it("hands off without a folder where there is nothing to share, even when asked for one", () => {
    expect(inMyFolderCwd(undefined, false, project, data)).toEqual({});
    expect(inMyFolderCwd(undefined, true, privateFolder, data)).toEqual({});
    expect(inMyFolderCwd(true, false, project, data)).toEqual({});
    expect(inMyFolderCwd(true, true, privateFolder, data)).toEqual({});
  });
});

describe("folderToFollow", () => {
  const own = join(dir, "omb-data", "task-workspaces", "bot", "thread");
  const bot = { id: "bot", cwd: join(dir, "omb-data-site") };
  const folders = { worksInFolders: true, keepsPrivateFolder: false };

  it("moves a thread from its own folder to the working folder its bot has been given", () => {
    expect(folderToFollow(bot, "thread", own, folders)).toBe(bot.cwd);
  });

  it("leaves a thread where it is when the bot has no working folder or the thread is not in its own folder", () => {
    expect(folderToFollow({ id: "bot" }, "thread", own, folders)).toBeUndefined();
    expect(folderToFollow(bot, "thread", join(dir, "chosen"), folders)).toBeUndefined();
    expect(folderToFollow(bot, "thread", bot.cwd, folders)).toBeUndefined();
    expect(folderToFollow(bot, "thread", join(dir, "omb-data", "task-workspaces", "bot", "other-thread"), folders)).toBeUndefined();
    expect(folderToFollow(bot, "thread", null, folders)).toBeUndefined();
    expect(folderToFollow(bot, "thread", undefined, folders)).toBeUndefined();
  });

  it("never moves a conversation that keeps a private folder, or one on an engine that works in no folder", () => {
    expect(folderToFollow(bot, "thread", own, { ...folders, keepsPrivateFolder: true })).toBeUndefined();
    expect(folderToFollow(bot, "thread", own, { ...folders, worksInFolders: false })).toBeUndefined();
  });
});