070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
225 lines
7.5 KiB
TypeScript
225 lines
7.5 KiB
TypeScript
import { join } from "node:path";
|
|
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
|
|
import type { PackagedConfig } from "../src/config.js";
|
|
import { PackagedPathAccessError } from "../src/errors.js";
|
|
import { resolvePackagedNamespacePaths } from "../src/paths.js";
|
|
|
|
function stubPlatform(value: NodeJS.Platform): () => void {
|
|
const original = process.platform;
|
|
Object.defineProperty(process, "platform", { value, configurable: true });
|
|
return () => {
|
|
Object.defineProperty(process, "platform", { value: original, configurable: true });
|
|
};
|
|
}
|
|
|
|
function fakeConfig(): PackagedConfig {
|
|
return {
|
|
amrProfile: null,
|
|
appVersion: null,
|
|
daemonCliEntry: null,
|
|
daemonSidecarEntry: null,
|
|
namespace: "release-stable-win",
|
|
namespaceBaseRoot: join("C:", "Users", "Fred", "AppData", "Roaming", "Open Design", "namespaces"),
|
|
nodeCommand: null,
|
|
posthogHost: null,
|
|
posthogKey: null,
|
|
resourceRoot: join("C:", "Program Files", "Open Design", "resources", "open-design"),
|
|
telemetryRelayUrl: null,
|
|
updateMetadataUrl: null,
|
|
webOutputMode: "server",
|
|
webSidecarEntry: null,
|
|
webStandaloneRoot: null,
|
|
};
|
|
}
|
|
|
|
describe("resolvePackagedNamespacePaths", () => {
|
|
let restorePlatform: () => void = () => {};
|
|
|
|
beforeEach(() => {
|
|
restorePlatform = stubPlatform("win32");
|
|
});
|
|
|
|
afterEach(() => {
|
|
restorePlatform();
|
|
});
|
|
|
|
it("models update downloads as a namespace-scoped root beside data", () => {
|
|
const config = fakeConfig();
|
|
const paths = resolvePackagedNamespacePaths(config, config.namespace);
|
|
|
|
expect(paths.namespaceRoot).toBe(join(config.namespaceBaseRoot, config.namespace));
|
|
expect(paths.dataRoot).toBe(join(paths.namespaceRoot, "data"));
|
|
expect(paths.updateRoot).toBe(join(paths.namespaceRoot, "updates"));
|
|
expect(paths.installerObservationRoot).toBe(join(paths.dataRoot, "observations", "installer"));
|
|
});
|
|
|
|
it("rejects namespace overrides that would escape the namespace base root", () => {
|
|
const config: PackagedConfig = {
|
|
amrProfile: null,
|
|
appVersion: "1.2.3",
|
|
daemonCliEntry: null,
|
|
daemonSidecarEntry: null,
|
|
namespace: "release",
|
|
namespaceBaseRoot: "/tmp/open-design-packaged/namespaces",
|
|
nodeCommand: null,
|
|
resourceRoot: "/tmp/open-design-packaged/resources",
|
|
telemetryRelayUrl: null,
|
|
updateMetadataUrl: null,
|
|
posthogKey: null,
|
|
posthogHost: null,
|
|
webSidecarEntry: null,
|
|
webStandaloneRoot: null,
|
|
webOutputMode: "server",
|
|
};
|
|
|
|
expect(() => resolvePackagedNamespacePaths(config, "../release")).toThrow(/namespace/);
|
|
});
|
|
|
|
it("defaults daemon dataRoot to the namespace-scoped packaged data directory", () => {
|
|
const config = fakeConfig();
|
|
|
|
expect(resolvePackagedNamespacePaths(config, config.namespace).dataRoot).toBe(
|
|
join(config.namespaceBaseRoot, config.namespace, "data"),
|
|
);
|
|
});
|
|
|
|
it("uses OD_DATA_DIR as a base for the namespace-scoped packaged daemon dataRoot", () => {
|
|
const config = fakeConfig();
|
|
const override = join("C:", "Users", "Fred", "MyProject", "design", ".od");
|
|
|
|
expect(
|
|
resolvePackagedNamespacePaths(config, config.namespace, { OD_DATA_DIR: override }).dataRoot,
|
|
).toBe(join(override, "namespaces", config.namespace, "data"));
|
|
});
|
|
|
|
it("keeps shared OD_DATA_DIR overrides isolated across packaged namespaces", () => {
|
|
const config = fakeConfig();
|
|
const override = join("C:", "Users", "Fred", "MyProject", "design", ".od");
|
|
const stable = resolvePackagedNamespacePaths(config, "release-stable-win", {
|
|
OD_DATA_DIR: override,
|
|
});
|
|
const beta = resolvePackagedNamespacePaths(config, "release-beta-win", {
|
|
OD_DATA_DIR: override,
|
|
});
|
|
|
|
expect(stable.dataRoot).toBe(join(override, "namespaces", "release-stable-win", "data"));
|
|
expect(beta.dataRoot).toBe(join(override, "namespaces", "release-beta-win", "data"));
|
|
expect(stable.dataRoot).not.toBe(beta.dataRoot);
|
|
});
|
|
|
|
it("preserves already-scoped packaged OD_DATA_DIR values as the final daemon dataRoot", () => {
|
|
const config = fakeConfig();
|
|
const override = join(
|
|
"C:",
|
|
"Users",
|
|
"Fred",
|
|
"AppData",
|
|
"Roaming",
|
|
"Open Design",
|
|
"namespaces",
|
|
config.namespace,
|
|
"data",
|
|
);
|
|
|
|
expect(
|
|
resolvePackagedNamespacePaths(config, config.namespace, { OD_DATA_DIR: override }).dataRoot,
|
|
).toBe(override);
|
|
});
|
|
|
|
it("rejects already-scoped OD_DATA_DIR values that point at a different packaged namespace", () => {
|
|
const config = fakeConfig();
|
|
const override = join(
|
|
"C:",
|
|
"Users",
|
|
"Fred",
|
|
"AppData",
|
|
"Roaming",
|
|
"Open Design",
|
|
"namespaces",
|
|
"release-beta-win",
|
|
"data",
|
|
);
|
|
|
|
expect(
|
|
() =>
|
|
resolvePackagedNamespacePaths(config, config.namespace, {
|
|
OD_DATA_DIR: override,
|
|
}),
|
|
).toThrow(PackagedPathAccessError);
|
|
});
|
|
|
|
it("forwards the OD_DATA_DIR-resolved dataRoot into sidecar launch paths", () => {
|
|
const config = fakeConfig();
|
|
const override = join("C:", "Users", "Fred", "MyProject", "design", ".od");
|
|
const paths = resolvePackagedNamespacePaths(config, config.namespace, {
|
|
OD_DATA_DIR: override,
|
|
});
|
|
|
|
expect(paths.dataRoot).toBe(join(override, "namespaces", config.namespace, "data"));
|
|
expect(paths.namespaceRoot).toBe(join(config.namespaceBaseRoot, config.namespace));
|
|
expect(paths.runtimeRoot).toBe(join(config.namespaceBaseRoot, config.namespace, "runtime"));
|
|
});
|
|
|
|
it("does not read process.env implicitly so headless can keep namespace-root OD_DATA_DIR semantics", () => {
|
|
const config = fakeConfig();
|
|
const original = process.env.OD_DATA_DIR;
|
|
try {
|
|
process.env.OD_DATA_DIR = join("C:", "Users", "Fred", "MyProject", "design", ".od");
|
|
expect(resolvePackagedNamespacePaths(config).dataRoot).toBe(
|
|
join(config.namespaceBaseRoot, config.namespace, "data"),
|
|
);
|
|
} finally {
|
|
if (original == null) delete process.env.OD_DATA_DIR;
|
|
else process.env.OD_DATA_DIR = original;
|
|
}
|
|
});
|
|
|
|
it("rejects relative OD_DATA_DIR values instead of resolving them against cwd", () => {
|
|
const config = fakeConfig();
|
|
|
|
expect(
|
|
() => resolvePackagedNamespacePaths(config, config.namespace, { OD_DATA_DIR: "project/.od" }),
|
|
).toThrow(/OD_DATA_DIR.*absolute path/);
|
|
});
|
|
|
|
it("surfaces the relative-OD_DATA_DIR rejection as PackagedPathAccessError so packaged main() can show a dialog", () => {
|
|
const config = fakeConfig();
|
|
|
|
let captured: unknown;
|
|
try {
|
|
resolvePackagedNamespacePaths(config, config.namespace, { OD_DATA_DIR: "project/.od" });
|
|
} catch (error) {
|
|
captured = error;
|
|
}
|
|
|
|
expect(captured).toBeInstanceOf(PackagedPathAccessError);
|
|
const err = captured as PackagedPathAccessError;
|
|
expect(err.title).toMatch(/OD_DATA_DIR/);
|
|
expect(err.message).toContain("project/.od");
|
|
expect(err.message).toMatch(/absolute path/);
|
|
});
|
|
|
|
it("rejects Windows-style OD_DATA_DIR values on non-Windows hosts so the absolute-path guard is platform-correct", () => {
|
|
const config = fakeConfig();
|
|
const restore = stubPlatform("linux");
|
|
try {
|
|
expect(
|
|
() =>
|
|
resolvePackagedNamespacePaths(config, config.namespace, {
|
|
OD_DATA_DIR: "C:\\Users\\Fred\\OD",
|
|
}),
|
|
).toThrow(PackagedPathAccessError);
|
|
expect(
|
|
() =>
|
|
resolvePackagedNamespacePaths(config, config.namespace, {
|
|
OD_DATA_DIR: "\\\\server\\share",
|
|
}),
|
|
).toThrow(PackagedPathAccessError);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
});
|