diff --git a/packages/cli/src/__tests__/agent-tarball.test.ts b/packages/cli/src/__tests__/agent-tarball.test.ts index f4f0aacd..b649dfa2 100644 --- a/packages/cli/src/__tests__/agent-tarball.test.ts +++ b/packages/cli/src/__tests__/agent-tarball.test.ts @@ -171,15 +171,21 @@ describe("tryTarballInstall", () => { }); describe("non-root home directory mirroring", () => { - it("mirrors dotfiles from /root/ to $HOME for non-root users", async () => { + let mirrorCmd: string; + + beforeEach(async () => { const fetchFn = mockFetch(new Response(JSON.stringify(RELEASE_PAYLOAD))); const runner = createMockRunner(); - await tryTarballInstall(runner, "openclaw", fetchFn); + mirrorCmd = String(runner.runServer.mock.calls[1][0]); + }); - const mirrorCmd = String(runner.runServer.mock.calls[1][0]); + it("mirrors dotfiles from /root/ to $HOME with non-root guard and ownership fix", () => { expect(mirrorCmd).toContain("cp -a"); expect(mirrorCmd).toContain('"$HOME/$_d"'); + expect(mirrorCmd).toContain('if [ "$(id -u)" != "0" ]; then'); + expect(mirrorCmd).toContain('chown -R "$(id -u):$(id -g)"'); + expect(mirrorCmd).toContain('cp /root/.spawn-tarball "$HOME/.spawn-tarball"'); for (const dir of [ ".claude", ".local", @@ -193,16 +199,6 @@ describe("tryTarballInstall", () => { } }); - it("mirrors the .spawn-tarball marker file", async () => { - const fetchFn = mockFetch(new Response(JSON.stringify(RELEASE_PAYLOAD))); - const runner = createMockRunner(); - - await tryTarballInstall(runner, "openclaw", fetchFn); - - const mirrorCmd = String(runner.runServer.mock.calls[1][0]); - expect(mirrorCmd).toContain('cp /root/.spawn-tarball "$HOME/.spawn-tarball"'); - }); - it("returns true even when mirror step fails (non-fatal)", async () => { const fetchFn = mockFetch(new Response(JSON.stringify(RELEASE_PAYLOAD))); const runner = createMockRunner(); @@ -212,25 +208,5 @@ describe("tryTarballInstall", () => { expect(result).toBe(true); }); - - it("guards mirror behind non-root check (id -u)", async () => { - const fetchFn = mockFetch(new Response(JSON.stringify(RELEASE_PAYLOAD))); - const runner = createMockRunner(); - - await tryTarballInstall(runner, "openclaw", fetchFn); - - const mirrorCmd = String(runner.runServer.mock.calls[1][0]); - expect(mirrorCmd).toContain('if [ "$(id -u)" != "0" ]; then'); - }); - - it("fixes ownership of mirrored files with chown", async () => { - const fetchFn = mockFetch(new Response(JSON.stringify(RELEASE_PAYLOAD))); - const runner = createMockRunner(); - - await tryTarballInstall(runner, "openclaw", fetchFn); - - const mirrorCmd = String(runner.runServer.mock.calls[1][0]); - expect(mirrorCmd).toContain('chown -R "$(id -u):$(id -g)"'); - }); }); }); diff --git a/packages/cli/src/__tests__/cmdrun-happy-path.test.ts b/packages/cli/src/__tests__/cmdrun-happy-path.test.ts index 81154829..2f2bda5a 100644 --- a/packages/cli/src/__tests__/cmdrun-happy-path.test.ts +++ b/packages/cli/src/__tests__/cmdrun-happy-path.test.ts @@ -441,7 +441,7 @@ describe("cmdRun happy-path pipeline", () => { // ── Dry-run mode ────────────────────────────────────────────────────────── describe("dry-run mode skips download", () => { - it("should not download script in dry-run mode", async () => { + it("should skip download, skip history, and show preview with agent/cloud info", async () => { global.fetch = mockFetchForDownload({ primaryOk: true, }); @@ -449,31 +449,15 @@ describe("cmdRun happy-path pipeline", () => { await cmdRun("claude", "sprite", undefined, true); - // In dry-run, only manifest fetch should occur (no script download) + // No script download — only manifest fetch const scriptFetches = fetchCalls.filter((c) => c.url.includes("openrouter.ai") && !c.url.includes("manifest")); expect(scriptFetches).toHaveLength(0); - }); - - it("should not save history in dry-run mode", async () => { - global.fetch = mockFetchForDownload({ - primaryOk: true, - }); - await loadManifest(true); - - await cmdRun("claude", "sprite", undefined, true); + // No history written const historyPath = join(historyDir, "history.json"); expect(existsSync(historyPath)).toBe(false); - }); - - it("should show dry-run preview with agent and cloud info", async () => { - global.fetch = mockFetchForDownload({ - primaryOk: true, - }); - await loadManifest(true); - - await cmdRun("claude", "sprite", undefined, true); + // Preview shows agent and cloud names const allOutput = consoleMocks.log.mock.calls.map((c: unknown[]) => c.join(" ")).join("\n"); expect(allOutput).toContain("Claude Code"); expect(allOutput).toContain("Sprite"); @@ -495,7 +479,7 @@ describe("cmdRun happy-path pipeline", () => { // ── Launch message formatting ───────────────────────────────────────────── describe("launch step message", () => { - it("should show 'Launching on ' for normal run", async () => { + it("should show 'Launching on ' without 'with prompt' when no prompt given", async () => { global.fetch = mockFetchForDownload({ primaryOk: true, }); @@ -508,6 +492,7 @@ describe("cmdRun happy-path pipeline", () => { expect(launchMsg).toBeDefined(); expect(launchMsg).toContain("Claude Code"); expect(launchMsg).toContain("Sprite"); + expect(launchMsg).not.toContain("with prompt"); }); it("should append 'with prompt...' when prompt is provided", async () => { @@ -522,19 +507,6 @@ describe("cmdRun happy-path pipeline", () => { const launchMsg = stepCalls.find((msg: string) => msg.includes("Launching")); expect(launchMsg).toContain("with prompt"); }); - - it("should append '...' without prompt when no prompt provided", async () => { - global.fetch = mockFetchForDownload({ - primaryOk: true, - }); - await loadManifest(true); - - await cmdRun("claude", "sprite"); - - const stepCalls = mockLogStep.mock.calls.map((c: unknown[]) => c.join(" ")); - const launchMsg = stepCalls.find((msg: string) => msg.includes("Launching")); - expect(launchMsg).not.toContain("with prompt"); - }); }); // ── Script content validation ─────────────────────────────────────────────