open-design/tools/pack/tests/mac-lifecycle.test.ts
Marc Chan b06f26a5fd
test: strengthen e2e PR coverage (#796)
* test: strengthen e2e PR coverage

* fix: address e2e PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* ci: cache Windows packaged smoke builds

* test: fake additional agent runtimes

* fix: address e2e PR feedback

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Route tools-pack mac starts through a launch-time packaged config override so portable packaged smoke runs keep using the namespace runtime root that inspect and logs expect.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Fall back to the packaged app's embedded config when the build output config is missing so installed mac starts still work.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: align packaged mac PR smoke with tools-pack runtime mode

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Keep blake3-wasm out of the packaged mac daemon prebundle so the standalone runtime loads the Cloudflare asset hasher from node_modules instead of crashing in ESM.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address e2e PR feedback

Skip the portable mac launch override when the bundled packaged config is missing so installed fallback app targets can still boot with packaged defaults.

Add a regression test covering the missing-config start path.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(pack): remove duplicate mac prebundle dependency key
2026-05-08 16:48:10 +08:00

138 lines
5.6 KiB
TypeScript

import { chmod, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ToolPackConfig } from "../src/config.js";
import { resolveMacPaths } from "../src/mac/paths.js";
const requestJsonIpc = vi.fn(async () => ({ state: "running" }));
const resolveAppIpcPath = vi.fn(() => "/tmp/open-design/ipc/test/desktop.sock");
const createSidecarLaunchEnv = vi.fn(({ extraEnv }: { extraEnv: NodeJS.ProcessEnv }) => extraEnv);
const spawnBackgroundProcess = vi.fn(async ({ env }: { env: NodeJS.ProcessEnv }) => ({ env, pid: 1234 }));
vi.mock("@open-design/sidecar", () => ({
createSidecarLaunchEnv,
requestJsonIpc,
resolveAppIpcPath,
}));
vi.mock("@open-design/platform", () => ({
collectProcessTreePids: vi.fn(),
createProcessStampArgs: vi.fn(() => []),
listProcessSnapshots: vi.fn(async () => []),
matchesStampedProcess: vi.fn(() => false),
readLogTail: vi.fn(async () => []),
spawnBackgroundProcess,
stopProcesses: vi.fn(async () => []),
}));
const { startPackedMacApp } = await import("../src/mac/lifecycle.js");
function makeConfig(root: string, overrides: Partial<ToolPackConfig> = {}): ToolPackConfig {
return {
containerized: false,
electronBuilderCliPath: "/x/electron-builder/cli.js",
electronDistPath: "/x/electron/dist",
electronVersion: "41.3.0",
macCompression: "normal",
namespace: "local-test",
platform: "mac",
portable: true,
removeData: false,
removeLogs: false,
removeProductUserData: false,
removeSidecars: false,
roots: {
output: {
appBuilderRoot: join(root, ".tmp", "tools-pack", "out", "mac", "namespaces", "local-test", "builder"),
namespaceRoot: join(root, ".tmp", "tools-pack", "out", "mac", "namespaces", "local-test"),
platformRoot: join(root, ".tmp", "tools-pack", "out", "mac"),
root: join(root, ".tmp", "tools-pack", "out"),
},
runtime: {
namespaceBaseRoot: join(root, ".tmp", "tools-pack", "runtime", "mac", "namespaces"),
namespaceRoot: join(root, ".tmp", "tools-pack", "runtime", "mac", "namespaces", "local-test"),
},
cacheRoot: join(root, ".tmp", "tools-pack", "cache"),
toolPackRoot: join(root, ".tmp", "tools-pack"),
},
silent: true,
signed: false,
to: "app",
webOutputMode: "standalone",
workspaceRoot: root,
...overrides,
};
}
afterEach(() => {
vi.clearAllMocks();
requestJsonIpc.mockResolvedValue({ state: "running" });
});
describe("startPackedMacApp", () => {
it("skips the launch override when the bundled config is missing", async () => {
const root = await mkdtemp(join(tmpdir(), "open-design-tools-pack-mac-lifecycle-"));
try {
const config = makeConfig(root);
const paths = resolveMacPaths(config);
const executablePath = join(paths.installedAppPath, "Contents", "MacOS", "Open Design");
await mkdir(join(paths.installedAppPath, "Contents", "MacOS"), { recursive: true });
await writeFile(executablePath, "#!/bin/sh\nexit 0\n", "utf8");
await chmod(executablePath, 0o755);
const result = await startPackedMacApp(config);
const launchConfigPath = join(config.roots.runtime.namespaceRoot, "open-design-config.json");
const launchEnv = spawnBackgroundProcess.mock.calls[0]?.[0]?.env as NodeJS.ProcessEnv | undefined;
expect(result.source).toBe("installed");
expect(result.status?.state).toBe("running");
expect(launchEnv?.OD_PACKAGED_CONFIG_PATH).toBeUndefined();
await expect(readFile(launchConfigPath, "utf8")).rejects.toMatchObject({ code: "ENOENT" });
} finally {
await rm(root, { force: true, recursive: true });
}
});
it("passes a launch override config path for portable mac starts", async () => {
const root = await mkdtemp(join(tmpdir(), "open-design-tools-pack-mac-lifecycle-"));
try {
const config = makeConfig(root);
const paths = resolveMacPaths(config);
const executablePath = join(paths.installedAppPath, "Contents", "MacOS", "Open Design");
const bundledConfigPath = join(paths.installedAppPath, "Contents", "Resources", "open-design-config.json");
await mkdir(join(paths.installedAppPath, "Contents", "MacOS"), { recursive: true });
await mkdir(join(paths.installedAppPath, "Contents", "Resources"), { recursive: true });
await writeFile(executablePath, "#!/bin/sh\nexit 0\n", "utf8");
await chmod(executablePath, 0o755);
await writeFile(
bundledConfigPath,
`${JSON.stringify({
appVersion: "1.2.3",
daemonCliEntryRelative: "open-design/bin/od",
namespace: config.namespace,
nodeCommandRelative: "open-design/bin/node",
}, null, 2)}\n`,
"utf8",
);
const result = await startPackedMacApp(config);
const launchConfigPath = join(config.roots.runtime.namespaceRoot, "open-design-config.json");
const launchEnv = spawnBackgroundProcess.mock.calls[0]?.[0]?.env as NodeJS.ProcessEnv | undefined;
expect(result.source).toBe("installed");
expect(result.status?.state).toBe("running");
expect(launchEnv?.OD_PACKAGED_CONFIG_PATH).toBe(launchConfigPath);
await expect(readFile(launchConfigPath, "utf8")).resolves.toContain(
`"namespaceBaseRoot": ${JSON.stringify(config.roots.runtime.namespaceBaseRoot)}`,
);
await expect(readFile(launchConfigPath, "utf8")).resolves.toContain('"appVersion": "1.2.3"');
} finally {
await rm(root, { force: true, recursive: true });
}
});
});