Files
paseo/packages/cli/tests/14-worktree.test.ts
T
paseo-bot[bot]andJason 0147edaaac Restore repository selection for CLI worktree commands (#6467)
* fix(cli): pass cwd to worktree ls and archive

worktree ls/archive called getPaseoWorktreeList({}) which fails because
the daemon requires cwd or repoRoot. Default to process.cwd(), accept
optional --cwd, and cover both with unit and CLI help tests.

Closes #2010

* test(cli): reproduce worktree listing without repository scope

* fix(cli): resolve local worktree paths from the calling directory

Local instance paths belong to the CLI working directory. Explicit endpoint paths remain daemon-relative, matching the project command convention. Cover both through the real daemon.

* refactor(cli): make repository directory selection explicit

---------

Co-authored-by: Jason <mildreins@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-10 15:53:10 +02:00

238 lines
9.4 KiB
TypeScript

#!/usr/bin/env npx tsx
/**
* Phase 14: Worktree Command Tests
*
* Tests the worktree commands for managing Paseo-managed git worktrees.
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - All flags are accepted
*
* Tests:
* - worktree --help shows subcommands
* - worktree ls --help shows options
* - worktree ls handles daemon not running
* - worktree archive --help shows options
* - worktree archive requires name argument
* - worktree archive handles daemon not running
*/
import assert from "node:assert";
import { getAvailablePort } from "./helpers/network.ts";
import { $ } from "zx";
import { mkdtemp, rm, mkdir } from "fs/promises";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { runPaseoCli, startTestDaemon } from "./helpers/test-daemon.ts";
import { tmpdir } from "os";
import { join } from "path";
$.verbose = false;
console.log("=== Worktree Command Tests ===\n");
// Allocate an unused endpoint for connection-error and argument-validation checks.
const port = await getAvailablePort();
const paseoHome = await mkdtemp(join(tmpdir(), "paseo-test-home-"));
try {
// Test 1: worktree --help shows subcommands
{
console.log("Test 1: worktree --help shows subcommands");
const result = await $`npx paseo worktree --help`.nothrow();
assert.strictEqual(result.exitCode, 0, "worktree --help should exit 0");
assert(result.stdout.includes("ls"), "help should mention ls subcommand");
assert(result.stdout.includes("archive"), "help should mention archive subcommand");
console.log("✓ worktree --help shows subcommands\n");
}
// Test 2: worktree ls --help shows options
{
console.log("Test 2: worktree ls --help shows options");
const result = await $`npx paseo worktree ls --help`.nothrow();
assert.strictEqual(result.exitCode, 0, "worktree ls --help should exit 0");
assert(result.stdout.includes("--host"), "help should mention --host option");
assert(result.stdout.includes("--cwd"), "help should mention --cwd option");
console.log("✓ worktree ls --help shows options\n");
}
// Test 3: worktree ls handles daemon not running
{
console.log("Test 3: worktree ls handles daemon not running");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} worktree ls`.nothrow();
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, "should fail when daemon not running");
const output = result.stdout + result.stderr;
const hasError =
output.toLowerCase().includes("daemon") ||
output.toLowerCase().includes("connect") ||
output.toLowerCase().includes("cannot");
assert(hasError, "error message should mention connection issue");
console.log("✓ worktree ls handles daemon not running\n");
}
// Test 4: worktree ls with --host flag is accepted
{
console.log("Test 4: worktree ls with --host flag is accepted");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} worktree ls --host localhost:${port}`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept --host flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ worktree ls with --host flag is accepted\n");
}
// Test 5: worktree archive --help shows options
{
console.log("Test 5: worktree archive --help shows options");
const result = await $`npx paseo worktree archive --help`.nothrow();
assert.strictEqual(result.exitCode, 0, "worktree archive --help should exit 0");
assert(result.stdout.includes("--host"), "help should mention --host option");
assert(result.stdout.includes("--cwd"), "help should mention --cwd option");
assert(result.stdout.includes("<name>"), "help should mention required name argument");
console.log("✓ worktree archive --help shows options\n");
}
// Test 6: worktree archive requires name argument
{
console.log("Test 6: worktree archive requires name argument");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} worktree archive`.nothrow();
assert.notStrictEqual(result.exitCode, 0, "should fail without name");
const output = result.stdout + result.stderr;
const hasError =
output.toLowerCase().includes("missing") ||
output.toLowerCase().includes("required") ||
output.toLowerCase().includes("argument");
assert(hasError, "error should mention missing argument");
console.log("✓ worktree archive requires name argument\n");
}
// Test 7: worktree archive handles daemon not running
{
console.log("Test 7: worktree archive handles daemon not running");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} worktree archive test-worktree`.nothrow();
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, "should fail when daemon not running");
const output = result.stdout + result.stderr;
const hasError =
output.toLowerCase().includes("daemon") ||
output.toLowerCase().includes("connect") ||
output.toLowerCase().includes("cannot");
assert(hasError, "error message should mention connection issue");
console.log("✓ worktree archive handles daemon not running\n");
}
// Test 8: worktree archive with name and --host flag is accepted
{
console.log("Test 8: worktree archive with name and --host flag is accepted");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} worktree archive test-worktree --host localhost:${port}`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept --host flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ worktree archive with name and --host flag is accepted\n");
}
// Test 9: -q (quiet) flag is accepted with worktree ls
{
console.log("Test 9: -q (quiet) flag is accepted with worktree ls");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} -q worktree ls`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept -q flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ -q (quiet) flag is accepted with worktree ls\n");
}
// Test 10: --json flag is accepted with worktree ls
{
console.log("Test 10: --json flag is accepted with worktree ls");
const result =
await $`PASEO_HOME=${paseoHome} npx paseo --host localhost:${port} worktree ls --json`.nothrow();
const output = result.stdout + result.stderr;
assert(!output.includes("unknown option"), "should accept --json flag");
assert(!output.includes("error: option"), "should not have option parsing error");
console.log("✓ --json flag is accepted with worktree ls\n");
}
// Test 11: paseo --help keeps the compatibility command hidden
{
console.log("Test 11: paseo --help hides worktree compatibility command");
const result = await $`npx paseo --help`.nothrow();
assert.strictEqual(result.exitCode, 0, "paseo --help should exit 0");
assert(!result.stdout.includes("worktree"), "help should not advertise worktree subcommand");
console.log("✓ paseo --help hides worktree compatibility command\n");
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true });
}
// Exercise repository selection through the real CLI and daemon. An empty
// request used to fail before Git could inspect even a valid repository.
const daemon = await startTestDaemon();
const git = promisify(execFile);
try {
const repo = join(daemon.workDir, "repository");
await mkdir(repo);
await git("git", ["init", repo]);
await git("git", [
"-C",
repo,
"-c",
"user.name=Test",
"-c",
"user.email=test@example.com",
"commit",
"--allow-empty",
"-m",
"Initial commit",
]);
for (const args of [
["worktree", "ls", "--json"],
["worktree", "ls", "--cwd", repo, "--json"],
["worktree", "ls", "--cwd", "./repository", "--json"],
["worktree", "ls", "--host", `127.0.0.1:${daemon.port}`, "--cwd", ".", "--json"],
]) {
const explicit = args.includes("--cwd");
const result = await runPaseoCli(daemon, args, {
cwd: explicit ? daemon.workDir : repo,
env: { FORCE_COLOR: undefined },
});
assert.strictEqual(result.exitCode, 0, result.stderr);
assert.deepStrictEqual(JSON.parse(result.stdout), []);
}
for (const args of [
["worktree", "archive", "missing", "--json"],
["worktree", "archive", "missing", "--cwd", repo, "--json"],
["worktree", "archive", "missing", "--cwd", "./repository", "--json"],
[
"worktree",
"archive",
"missing",
"--host",
`127.0.0.1:${daemon.port}`,
"--cwd",
".",
"--json",
],
]) {
const result = await runPaseoCli(daemon, args, {
cwd: args.includes("--cwd") ? daemon.workDir : repo,
env: { FORCE_COLOR: undefined },
});
assert.notStrictEqual(result.exitCode, 0);
assert.strictEqual(JSON.parse(result.stderr).error.code, "WORKTREE_NOT_FOUND", result.stderr);
}
} finally {
await daemon.stop();
}
console.log("=== All worktree tests passed ===");