261 lines
9.3 KiB
JavaScript
261 lines
9.3 KiB
JavaScript
import { describe, expect, test } from "bun:test";
|
|
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
|
|
import {
|
|
desktopBootstrapPath,
|
|
globalOpencodeConfigDir,
|
|
legacyDesktopBootstrapPath,
|
|
MAX_CONFIG_ROOT_LENGTH,
|
|
normalizeWorkspaceRootPath,
|
|
opencodeDbCandidates,
|
|
openworkEnvStorePath,
|
|
openworkServerConfigPath,
|
|
resolveGlobalOpencodeConfigPath,
|
|
resolveWorkspaceOpencodeConfigPath,
|
|
workspaceOpencodeConfigCandidates,
|
|
} from "../index.mjs";
|
|
|
|
async function withTempDir(callback) {
|
|
const root = await mkdtemp(path.join(tmpdir(), "openwork-paths-"));
|
|
try {
|
|
await callback(root);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
describe("workspace root paths", () => {
|
|
test("normalizes valid Windows verbatim drive and UNC paths cross-platform", () => {
|
|
const opts = { platform: "win32" };
|
|
expect(normalizeWorkspaceRootPath("\\\\?\\C:\\Users\\Ada\\Workspace", opts))
|
|
.toBe("C:\\Users\\Ada\\Workspace");
|
|
expect(normalizeWorkspaceRootPath("\\\\?\\C:\\", opts)).toBe("C:\\");
|
|
expect(normalizeWorkspaceRootPath("//?/UNC/server/share/Workspace", opts))
|
|
.toBe("\\\\server\\share\\Workspace");
|
|
expect(normalizeWorkspaceRootPath("\\\\?\\UNC\\server\\share", opts))
|
|
.toBe("\\\\server\\share");
|
|
});
|
|
|
|
test("preserves valid normal drives and UNC shares without checking availability", () => {
|
|
const opts = { platform: "win32" };
|
|
expect(normalizeWorkspaceRootPath("Z:\\Disconnected\\Workspace", opts))
|
|
.toBe("Z:\\Disconnected\\Workspace");
|
|
expect(normalizeWorkspaceRootPath("\\\\offline-server\\share\\Workspace", opts))
|
|
.toBe("\\\\offline-server\\share\\Workspace");
|
|
expect(normalizeWorkspaceRootPath("\\\\offline-server\\pipe\\Workspace", opts))
|
|
.toBe("\\\\offline-server\\pipe\\Workspace");
|
|
});
|
|
|
|
test("rejects Win32 device namespace roots", () => {
|
|
const opts = { platform: "win32" };
|
|
for (const value of [
|
|
"\\\\.\\pipe\\openwork",
|
|
"//./PIPE/openwork",
|
|
"\\\\.\\PhysicalDrive0",
|
|
"\\\\?\\UNC\\.\\pipe\\openwork",
|
|
"\\\\?\\UNC\\?\\PhysicalDrive0",
|
|
]) {
|
|
expect(() => normalizeWorkspaceRootPath(value, opts)).toThrow("Invalid Windows workspace root");
|
|
}
|
|
});
|
|
|
|
test("rejects incomplete Windows drive and UNC roots", () => {
|
|
const opts = { platform: "win32" };
|
|
for (const value of [
|
|
"C:",
|
|
"\\\\?\\",
|
|
"\\\\?\\C:",
|
|
"\\\\?\\C:Workspace",
|
|
"\\\\?\\UNC",
|
|
"\\\\?\\UNC\\server",
|
|
"\\\\server",
|
|
]) {
|
|
expect(() => normalizeWorkspaceRootPath(value, opts)).toThrow("Invalid Windows workspace root");
|
|
}
|
|
});
|
|
|
|
test("applies Windows validation only when Windows is injected", () => {
|
|
expect(normalizeWorkspaceRootPath("\\\\?\\C:", { platform: "linux" })).toBe("\\\\?\\C:");
|
|
});
|
|
});
|
|
|
|
describe("OpenCode database paths", () => {
|
|
test("uses the production channel name and honors explicit overrides", () => {
|
|
expect(opencodeDbCandidates({
|
|
env: {},
|
|
homeDir: "/Users/ada",
|
|
platform: "darwin",
|
|
defaultChannel: "latest",
|
|
})).toContain("/Users/ada/Library/Application Support/opencode/opencode.db");
|
|
expect(opencodeDbCandidates({
|
|
env: { OPENCODE_CHANNEL: "preview" },
|
|
dataDirs: ["/tmp/opencode"],
|
|
homeDir: "/Users/ada",
|
|
platform: "darwin",
|
|
})[0]).toBe("/tmp/opencode/opencode-preview.db");
|
|
expect(opencodeDbCandidates({
|
|
env: { OPENCODE_DB: "/tmp/production.db" },
|
|
homeDir: "/Users/ada",
|
|
platform: "darwin",
|
|
})).toEqual(["/tmp/production.db"]);
|
|
});
|
|
});
|
|
|
|
describe("openwork server config paths", () => {
|
|
test("uses APPDATA on Windows", () => {
|
|
expect(openworkServerConfigPath({
|
|
env: { APPDATA: "C:\\Users\\Ada\\AppData\\Roaming" },
|
|
homeDir: "C:\\Users\\Ada",
|
|
platform: "win32",
|
|
})).toBe("C:\\Users\\Ada\\AppData\\Roaming\\openwork\\server.json");
|
|
});
|
|
|
|
test("uses XDG_CONFIG_HOME on Unix", () => {
|
|
expect(openworkServerConfigPath({
|
|
env: { XDG_CONFIG_HOME: "/tmp/xdg" },
|
|
homeDir: "/home/ada",
|
|
platform: "linux",
|
|
})).toBe("/tmp/xdg/openwork/server.json");
|
|
});
|
|
|
|
test("falls back to ~/.config", () => {
|
|
expect(openworkServerConfigPath({ env: {}, homeDir: "/home/ada", platform: "linux" }))
|
|
.toBe("/home/ada/.config/openwork/server.json");
|
|
});
|
|
|
|
test("honors OPENWORK_SERVER_CONFIG", () => {
|
|
expect(openworkServerConfigPath({
|
|
env: { OPENWORK_SERVER_CONFIG: "/tmp/openwork/server.json" },
|
|
homeDir: "/home/ada",
|
|
platform: "linux",
|
|
})).toBe("/tmp/openwork/server.json");
|
|
});
|
|
});
|
|
|
|
describe("openwork env store and desktop bootstrap paths", () => {
|
|
test("honors OPENWORK_ENV_STORE", () => {
|
|
expect(openworkEnvStorePath({
|
|
env: { OPENWORK_ENV_STORE: "/tmp/openwork/env.json" },
|
|
homeDir: "/home/ada",
|
|
platform: "linux",
|
|
})).toBe("/tmp/openwork/env.json");
|
|
});
|
|
|
|
test("uses the same openwork config layout for env.json", () => {
|
|
expect(openworkEnvStorePath({
|
|
env: { XDG_CONFIG_HOME: "/tmp/xdg" },
|
|
homeDir: "/home/ada",
|
|
platform: "linux",
|
|
})).toBe("/tmp/xdg/openwork/env.json");
|
|
});
|
|
|
|
test("honors OPENWORK_DESKTOP_BOOTSTRAP_PATH", () => {
|
|
expect(desktopBootstrapPath({
|
|
env: { OPENWORK_DESKTOP_BOOTSTRAP_PATH: "/tmp/bootstrap.json" },
|
|
homeDir: "/home/ada",
|
|
platform: "linux",
|
|
})).toBe("/tmp/bootstrap.json");
|
|
});
|
|
|
|
test("preserves dev-data desktop bootstrap path when userDataDir is injected", () => {
|
|
expect(desktopBootstrapPath({
|
|
env: { OPENWORK_DEV_MODE: "1" },
|
|
homeDir: "/Users/ada",
|
|
platform: "darwin",
|
|
userDataDir: "/tmp/openwork-userdata",
|
|
})).toBe("/tmp/openwork-userdata/openwork-dev-data/home/.config/openwork/desktop-bootstrap.json");
|
|
});
|
|
|
|
test("resolves the legacy desktop bootstrap path from the chosen home", () => {
|
|
expect(legacyDesktopBootstrapPath({ env: {}, homeDir: "/Users/ada", platform: "darwin" }))
|
|
.toBe("/Users/ada/.config/openwork/desktop-bootstrap.json");
|
|
});
|
|
});
|
|
|
|
describe("global OpenCode config paths", () => {
|
|
test("accepts safe OPENCODE_CONFIG_DIR as the config directory", async () => {
|
|
await withTempDir(async (root) => {
|
|
const opencodeConfigDir = path.join(root, "explicit-opencode");
|
|
await mkdir(opencodeConfigDir, { recursive: true });
|
|
const json = path.join(opencodeConfigDir, "opencode.json");
|
|
await writeFile(json, "{}", "utf8");
|
|
|
|
const opts = {
|
|
env: { OPENCODE_CONFIG_DIR: opencodeConfigDir, XDG_CONFIG_HOME: path.join(root, "xdg") },
|
|
homeDir: path.join(root, "home"),
|
|
platform: "linux",
|
|
};
|
|
expect(globalOpencodeConfigDir(opts)).toBe(opencodeConfigDir);
|
|
expect(resolveGlobalOpencodeConfigPath(opts)).toBe(json);
|
|
});
|
|
});
|
|
|
|
test("prefers opencode.jsonc over opencode.json and falls back to jsonc", async () => {
|
|
await withTempDir(async (root) => {
|
|
const dir = path.join(root, "xdg", "opencode");
|
|
await mkdir(dir, { recursive: true });
|
|
const opts = { env: { XDG_CONFIG_HOME: path.join(root, "xdg") }, homeDir: path.join(root, "home"), platform: "linux" };
|
|
const jsonc = path.join(dir, "opencode.jsonc");
|
|
const json = path.join(dir, "opencode.json");
|
|
|
|
expect(resolveGlobalOpencodeConfigPath(opts)).toBe(jsonc);
|
|
await writeFile(json, "{}", "utf8");
|
|
expect(resolveGlobalOpencodeConfigPath(opts)).toBe(json);
|
|
await writeFile(jsonc, "{}", "utf8");
|
|
expect(resolveGlobalOpencodeConfigPath(opts)).toBe(jsonc);
|
|
});
|
|
});
|
|
|
|
test("rejects relative OPENCODE_CONFIG_DIR", () => {
|
|
const opts = {
|
|
env: { OPENCODE_CONFIG_DIR: "relative/opencode", XDG_CONFIG_HOME: "/tmp/xdg" },
|
|
homeDir: "/home/ada",
|
|
platform: "linux",
|
|
};
|
|
expect(globalOpencodeConfigDir(opts)).toBe("/tmp/xdg/opencode");
|
|
});
|
|
|
|
test("rejects over-long OPENCODE_CONFIG_DIR", () => {
|
|
const opts = {
|
|
env: { OPENCODE_CONFIG_DIR: `/${"a".repeat(MAX_CONFIG_ROOT_LENGTH)}`, XDG_CONFIG_HOME: "/tmp/xdg" },
|
|
homeDir: "/home/ada",
|
|
platform: "linux",
|
|
};
|
|
expect(globalOpencodeConfigDir(opts)).toBe("/tmp/xdg/opencode");
|
|
});
|
|
|
|
test("rejects forbidden control characters in OPENCODE_CONFIG_DIR", () => {
|
|
const opts = {
|
|
env: { OPENCODE_CONFIG_DIR: "/tmp/opencode\n", XDG_CONFIG_HOME: "/tmp/xdg" },
|
|
homeDir: "/home/ada",
|
|
platform: "linux",
|
|
};
|
|
expect(globalOpencodeConfigDir(opts)).toBe("/tmp/xdg/opencode");
|
|
});
|
|
});
|
|
|
|
describe("workspace OpenCode config paths", () => {
|
|
test("returns the four server candidates in order", () => {
|
|
expect(workspaceOpencodeConfigCandidates("/repo/workspace")).toEqual([
|
|
"/repo/workspace/opencode.jsonc",
|
|
"/repo/workspace/opencode.json",
|
|
"/repo/workspace/.opencode/opencode.jsonc",
|
|
"/repo/workspace/.opencode/opencode.json",
|
|
]);
|
|
});
|
|
|
|
test("resolves the first existing workspace candidate", async () => {
|
|
await withTempDir(async (root) => {
|
|
await mkdir(path.join(root, ".opencode"), { recursive: true });
|
|
const hiddenJsonc = path.join(root, ".opencode", "opencode.jsonc");
|
|
const hiddenJson = path.join(root, ".opencode", "opencode.json");
|
|
await writeFile(hiddenJson, "{}", "utf8");
|
|
expect(resolveWorkspaceOpencodeConfigPath(root)).toBe(hiddenJson);
|
|
await writeFile(hiddenJsonc, "{}", "utf8");
|
|
expect(resolveWorkspaceOpencodeConfigPath(root)).toBe(hiddenJsonc);
|
|
});
|
|
});
|
|
});
|