Files
nextcraft/apps/cli/tests/commands.test.ts
T
CIAgent 135ea21a61 merge: hotfix/fresh-box-experience → main (v0.3.5 fresh-box experience)
Fixes the v0.3.4 fresh-box failures: silent CLI under bare-word PATH
invocation (SEA argv detection), bootstrap dying on venv creation without an
actionable hint (poisoned-partial-venv recovery + apt hint + doctor
venv-capability probe + preflight), installer falsely 'verifying' a silent
binary, and localhost-only server binding (network mode: 0.0.0.0 + wildcard
CORS + hostname-derived API URL — remote browsing zero-config).

38 CLI tests, 3 web tests, 409 ai-service tests green; build/typecheck/lint
clean.

---ci---
phase: hotfix
milestone: v0.4
status: complete
type: hotfix
requirements:
  covered: [REQ-4-001, REQ-4-002, REQ-4-003, REQ-4-004, REQ-4-005]
  partial: []
---/ci---
2026-09-13 00:05:37 +00:00

219 lines
8.9 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, writeFileSync, mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { doctor } from "../src/commands/doctor.ts";
import { bootstrap } from "../src/commands/bootstrap.ts";
import { verify } from "../src/commands/verify.ts";
import { testCtx } from "./helpers.ts";
import type { SpawnResult } from "../src/lib/spawn.ts";
const r0 = (stdout = ""): SpawnResult => ({ code: 0, signal: null, timedOut: false, stdout, stderr: "" });
const r1 = (): SpawnResult => ({ code: 1, signal: null, timedOut: false, stdout: "", stderr: "" });
const r = (stdout: string): SpawnResult => r0(stdout);
test("doctor: all prerequisites present exits 0", async () => {
const ctx = testCtx({
spawn: async (cmd, args) => {
if (cmd === "which") return r0();
if (cmd === "pnpm" && args[0] === "--version") return r0("10.0.0\n");
if (cmd === "python3" && args[0] === "--version") return r0("Python 3.11.2\n");
if (cmd === "git" && args[0] === "--version") return r0("git version 2.39.2\n");
return r0();
},
});
const code = await doctor([], ctx);
assert.equal(code, 0);
assert.ok(ctx.out().includes("All checks passed"));
});
test("doctor: missing pnpm fails with hint, exit 1", async () => {
const ctx = testCtx({
spawn: async (cmd, args) => {
if (cmd === "which" && args[0] === "pnpm") return { ...r1(), code: 1 };
if (cmd === "which") return r0();
if (cmd === "python3") return r0("Python 3.11.2\n");
if (cmd === "git") return r0("git version 2.39.2\n");
return r0();
},
});
const code = await doctor([], ctx);
assert.equal(code, 1);
assert.ok(ctx.err().includes("pnpm not found"));
assert.ok(ctx.err().includes("corepack"));
});
test("doctor: outdated python3 fails with version comparison", async () => {
const ctx = testCtx({
spawn: async (cmd, args) => {
if (cmd === "which") return r0();
if (cmd === "python3" && args[0] === "--version") return r0("Python 3.9.0\n");
if (cmd === "pnpm") return r0("10.0.0\n");
if (cmd === "git") return r0("git version 2.39.2\n");
return r0();
},
});
const code = await doctor([], ctx);
assert.equal(code, 1);
assert.ok(ctx.err().includes("3.9"));
});
test("bootstrap: outside a repo fails with clone hint", async () => {
const dir = mkdtempSync(join(tmpdir(), "nc-norepo-"));
const ctx = testCtx({ cwd: dir });
const code = await bootstrap([], ctx);
assert.equal(code, 1);
assert.ok(ctx.err().includes("git clone"));
});
const doctorPassSpawn = async (cmd: string, args: string[]): Promise<SpawnResult> => {
if (cmd === "pnpm" && args[0] === "--version") return r("10.0.0\n");
if (cmd === "python3" && args[0] === "--version") return r("Python 3.11.2\n");
if (cmd === "git") return r("git version 2.39.2\n");
if (cmd === "python3" && args[1] === "venv") return r0();
return r0();
};
test("bootstrap: step order — preflight, pnpm install, venv bootstrap, env copy", async () => {
const dir = mkdtempSync(join(tmpdir(), "nc-repo-"));
writeFileSync(join(dir, "pnpm-workspace.yaml"), "packages:\n - apps/*\n");
mkdirSync(join(dir, "apps", "ai-service"), { recursive: true });
writeFileSync(join(dir, "apps", "ai-service", ".env.example"), "AI_PORT=8420\nAI_KEY=\n");
const calls: string[] = [];
const ctx = testCtx({
cwd: dir,
spawn: async (cmd, args) => {
calls.push(`${cmd} ${args.join(" ")}`);
return doctorPassSpawn(cmd, args);
},
});
const code = await bootstrap([], ctx);
assert.equal(code, 0);
const installIdx = calls.findIndex((c) => c.startsWith("pnpm install"));
const venvIdx = calls.findIndex((c) => c.startsWith("bash scripts/bootstrap.sh"));
assert.ok(installIdx >= 0, "pnpm install runs");
assert.ok(venvIdx > installIdx, "venv bootstrap runs after pnpm install");
assert.ok(ctx.exists(join(dir, "apps", "ai-service", ".env")));
assert.ok(ctx.out().includes("bootstrap complete"));
});
test("bootstrap: existing .env kept, not overwritten", async () => {
const dir = mkdtempSync(join(tmpdir(), "nc-keep-"));
writeFileSync(join(dir, "pnpm-workspace.yaml"), "packages:\n - apps/*\n");
const aiDir = join(dir, "apps", "ai-service");
mkdirSync(aiDir, { recursive: true });
writeFileSync(join(aiDir, ".env.example"), "AI_PORT=8420\n");
writeFileSync(join(aiDir, ".env"), "AI_PORT=9999\n");
const ctx = testCtx({ cwd: dir, spawn: doctorPassSpawn });
const code = await bootstrap([], ctx);
assert.equal(code, 0);
assert.ok(ctx.out().includes("kept as-is"));
assert.equal(ctx.readFile(join(aiDir, ".env")), "AI_PORT=9999\n");
});
test("bootstrap: failing pnpm install aborts before venv step", async () => {
const dir = mkdtempSync(join(tmpdir(), "nc-fail-"));
writeFileSync(join(dir, "pnpm-workspace.yaml"), "packages:\n - apps/*\n");
const calls: string[] = [];
const ctx = testCtx({
cwd: dir,
spawn: async (cmd, args) => {
calls.push(`${cmd} ${args.join(" ")}`);
if (cmd === "pnpm" && args[0] === "install") return r1();
return doctorPassSpawn(cmd, args);
},
});
const code = await bootstrap([], ctx);
assert.equal(code, 1);
assert.ok(ctx.err().includes("pnpm install failed"), ctx.err());
assert.equal(calls.filter((c) => c.startsWith("bash scripts/bootstrap.sh")).length, 0, "venv step must not run");
});
test("verify: busy port fails with stop-the-process hint", async () => {
const dir = mkdtempSync(join(tmpdir(), "nc-ver-"));
writeFileSync(join(dir, "pnpm-workspace.yaml"), "packages:\n - apps/*\n");
const aiDir = join(dir, "apps", "ai-service");
mkdirSync(aiDir, { recursive: true });
mkdirSync(join(aiDir, ".venv", "bin"), { recursive: true });
writeFileSync(join(aiDir, ".venv", "bin", "python3"), "#!/bin/sh\nexit 0\n");
writeFileSync(join(aiDir, ".env"), "AI_PORT=8420\n");
mkdirSync(join(dir, "node_modules", ".bin"), { recursive: true });
writeFileSync(join(dir, "node_modules", ".bin", "turbo"), "#!/bin/sh\n");
const ctx = testCtx({
cwd: dir,
spawn: async () => r0(),
portFree: async () => false,
});
const code = await verify([], ctx);
assert.equal(code, 1);
assert.ok(ctx.err().includes("port 8420 is busy"));
assert.ok(ctx.err().includes("stop the process listening on 8420"));
});
test("verify: free port passes and reports AI_PORT from .env", async () => {
const dir = mkdtempSync(join(tmpdir(), "nc-ver2-"));
writeFileSync(join(dir, "pnpm-workspace.yaml"), "packages:\n - apps/*\n");
const aiDir = join(dir, "apps", "ai-service");
mkdirSync(aiDir, { recursive: true });
mkdirSync(join(aiDir, ".venv", "bin"), { recursive: true });
writeFileSync(join(aiDir, ".venv", "bin", "python3"), "#!/bin/sh\nexit 0\n");
writeFileSync(join(aiDir, ".env"), "AI_PORT=8421\n");
mkdirSync(join(dir, "node_modules", ".bin"), { recursive: true });
writeFileSync(join(dir, "node_modules", ".bin", "turbo"), "#!/bin/sh\n");
const ctx = testCtx({
cwd: dir,
spawn: async () => r0(),
portFree: async () => true,
});
const code = await verify([], ctx);
assert.equal(code, 0);
assert.ok(ctx.out().includes("port 8421 free"));
});
test("bootstrap: preflight failure aborts before pnpm install", async () => {
const dir = mkdtempSync(join(tmpdir(), "nc-pre-"));
writeFileSync(join(dir, "pnpm-workspace.yaml"), "packages:\n - apps/*\n");
const calls: string[] = [];
const ctx = testCtx({
cwd: dir,
spawn: async (cmd, args) => {
calls.push(`${cmd} ${args.join(" ")}`);
if (cmd === "which") return r0();
if (cmd === "pnpm" && args[0] === "--version") return r("10.0.0\n");
if (cmd === "python3" && args[0] === "--version") return r("Python 3.11.2\n");
if (cmd === "git") return r("git version 2.39.2\n");
if (cmd === "python3" && args[1] === "venv") return r1();
if (cmd === "rm") return r0();
return r0();
},
});
const code = await bootstrap([], ctx);
assert.equal(code, 1);
assert.ok(ctx.err().includes("preflight failed"));
assert.equal(calls.filter((c) => c.startsWith("pnpm install")).length, 0, "pnpm install must not run when preflight fails");
});
test("doctor: venv-capability probe failure surfaces apt hint", async () => {
const ctx = testCtx({
spawn: async (cmd, args) => {
if (cmd === "which") return r0();
if (cmd === "pnpm") return r("10.0.0\n");
if (cmd === "python3" && args[0] === "--version") return r("Python 3.12.3\n");
if (cmd === "git") return r("git version 2.43.0\n");
if (cmd === "node") return r("v24.0.0\n");
if (cmd === "python3" && args[1] === "venv") return r1();
if (cmd === "rm") return r0();
return r0();
},
});
const code = await doctor([], ctx);
assert.equal(code, 1);
assert.ok(ctx.err().includes("cannot create virtual environments"));
assert.ok(ctx.err().includes("apt install python"));
assert.ok(ctx.err().includes("-venv"));
});