Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cli/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"tasks": {
"dev": "deno run --allow-all main.ts",
"test": "deno test --allow-all .",
"test:behavior": "deno test --allow-all tests/behavior/",
"test:contract": "deno test --allow-all tests/contract/",
"check": "deno check main.ts",
"fmt": "deno fmt .",
"lint": "deno lint .",
Expand Down
115 changes: 115 additions & 0 deletions cli/tests/behavior/error_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Behavior tests: error message correctness.
*
* Tests that the CLI emits helpful error messages for bad input.
*/

import { assertEquals } from "@std/assert";
import { makeTempRepo, runCli, runCliIn } from "../helpers.ts";
import { join } from "@std/path";

// ─── Unknown commands ──────────────────────────────────────────────────────────

Deno.test("error: unknown top-level command prints error to stderr", async () => {
const r = await runCli(["badcommand"]);
assertEquals(r.code, 1);
assertEquals(r.stderr.includes("Unknown command group"), true);
});

Deno.test("error: unknown top-level command suggests --help", async () => {
const r = await runCli(["badcommand"]);
assertEquals(r.code, 1);
assertEquals(r.stderr.includes("--help"), true);
});

Deno.test("error: unknown kg subcommand prints error to stderr", async () => {
const r = await runCli(["kg", "badcommand"]);
assertEquals(r.code, 1);
assertEquals(r.stderr.includes("Unknown kg subcommand"), true);
});

Deno.test("error: unknown kg subcommand suggests 'khive kg --help'", async () => {
const r = await runCli(["kg", "badcommand"]);
assertEquals(r.code, 1);
assertEquals(r.stderr.includes("khive kg --help"), true);
});

Deno.test("error: unknown pack subcommand prints error to stderr", async () => {
const r = await runCli(["pack", "badcommand"]);
assertEquals(r.code, 1);
assertEquals(r.stderr.includes("Unknown pack subcommand"), true);
});

Deno.test("error: unknown pack subcommand suggests 'khive pack --help'", async () => {
const r = await runCli(["pack", "badcommand"]);
assertEquals(r.code, 1);
assertEquals(r.stderr.includes("khive pack --help"), true);
});

Deno.test("error: unknown auth subcommand prints error to stderr", async () => {
const r = await runCli(["auth", "badcommand"]);
assertEquals(r.code, 1);
assertEquals(r.stderr.includes("Unknown auth subcommand"), true);
});

Deno.test("error: auth login shows not-implemented message", async () => {
const r = await runCli(["auth", "login"]);
assertEquals(r.code, 1);
assertEquals(r.stderr.includes("not yet implemented"), true);
});

Deno.test("error: kg update shows not-implemented message", async () => {
const r = await runCli(["kg", "update"]);
assertEquals(r.code, 1);
assertEquals(r.stderr.includes("not yet implemented"), true);
});

// ─── pack check with bad path ──────────────────────────────────────────────────

Deno.test("error: pack check nonexistent.yaml exits non-zero", async () => {
const r = await runCli(["pack", "check", "nonexistent-file-that-does-not-exist.yaml"]);
assertEquals(r.code !== 0, true);
});

// ─── Out-of-repo kg commands ───────────────────────────────────────────────────

Deno.test("error: kg validate outside git repo prints error", async () => {
// /tmp is not a git repo
const r = await runCliIn("/tmp", ["kg", "validate"]);
assertEquals(r.code !== 0, true);
});

Deno.test("error: kg stats outside git repo prints error", async () => {
const r = await runCliIn("/tmp", ["kg", "stats"]);
assertEquals(r.code !== 0, true);
});

// ─── Invalid NDJSON ────────────────────────────────────────────────────────────

Deno.test("error: kg validate on invalid NDJSON exits non-zero", async () => {
const repo = await makeTempRepo();
try {
// Write invalid NDJSON to entities file
const entitiesPath = join(repo.root, ".khive", "kg", "entities.ndjson");
await Deno.writeTextFile(entitiesPath, "not valid json\n");
const r = await runCliIn(repo.root, ["kg", "validate"]);
assertEquals(r.code !== 0, true);
} finally {
await repo.cleanup();
}
});

Deno.test("error: kg validate on invalid entity (missing kind) exits non-zero", async () => {
const repo = await makeTempRepo();
try {
const entitiesPath = join(repo.root, ".khive", "kg", "entities.ndjson");
await Deno.writeTextFile(
entitiesPath,
'{"id":"ent_00000000-0000-0000-0000-000000000001","name":"bad"}\n',
);
const r = await runCliIn(repo.root, ["kg", "validate"]);
assertEquals(r.code !== 0, true);
} finally {
await repo.cleanup();
}
});
147 changes: 147 additions & 0 deletions cli/tests/behavior/exit_code_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* Behavior tests: exit code correctness.
*
* Tests that the CLI exits with 0 on success and non-zero on failure.
*/

import { assertEquals } from "@std/assert";
import { makeTempRepo, runCli, runCliIn } from "../helpers.ts";

// ─── Top-level flags ───────────────────────────────────────────────────────────

Deno.test("exit: khive --version exits 0", async () => {
const r = await runCli(["--version"]);
assertEquals(r.code, 0);
});

Deno.test("exit: khive -V exits 0", async () => {
const r = await runCli(["-V"]);
assertEquals(r.code, 0);
});

Deno.test("exit: khive --help exits 0", async () => {
const r = await runCli(["--help"]);
assertEquals(r.code, 0);
});

Deno.test("exit: khive -h exits 0", async () => {
const r = await runCli(["-h"]);
assertEquals(r.code, 0);
});

Deno.test("exit: khive (no args) exits 0", async () => {
const r = await runCli([]);
assertEquals(r.code, 0);
});

// ─── Unknown command groups ────────────────────────────────────────────────────

Deno.test("exit: unknown top-level command exits 1", async () => {
const r = await runCli(["unknown-command"]);
assertEquals(r.code, 1);
});

Deno.test("exit: unknown kg subcommand exits 1", async () => {
const r = await runCli(["kg", "unknown-subcommand"]);
assertEquals(r.code, 1);
});

Deno.test("exit: unknown pack subcommand exits 1", async () => {
const r = await runCli(["pack", "unknown-subcommand"]);
assertEquals(r.code, 1);
});

Deno.test("exit: unknown auth subcommand exits 1", async () => {
const r = await runCli(["auth", "unknown-subcommand"]);
assertEquals(r.code, 1);
});

// ─── kg group help ─────────────────────────────────────────────────────────────

Deno.test("exit: khive kg --help exits 0", async () => {
const r = await runCli(["kg", "--help"]);
assertEquals(r.code, 0);
});

Deno.test("exit: khive kg (no subcommand) exits 0", async () => {
const r = await runCli(["kg"]);
assertEquals(r.code, 0);
});

// ─── pack group ────────────────────────────────────────────────────────────────

Deno.test("exit: khive pack --help exits 0", async () => {
const r = await runCli(["pack", "--help"]);
assertEquals(r.code, 0);
});

Deno.test("exit: khive pack (no subcommand) exits 0", async () => {
const r = await runCli(["pack"]);
assertEquals(r.code, 0);
});

// ─── auth stubs exit non-zero ──────────────────────────────────────────────────

Deno.test("exit: khive auth login exits 1 (not implemented)", async () => {
const r = await runCli(["auth", "login"]);
assertEquals(r.code, 1);
});

Deno.test("exit: khive auth status exits 1 (not implemented)", async () => {
const r = await runCli(["auth", "status"]);
assertEquals(r.code, 1);
});

Deno.test("exit: khive auth logout exits 1 (not implemented)", async () => {
const r = await runCli(["auth", "logout"]);
assertEquals(r.code, 1);
});

// ─── kg update stub exits non-zero ────────────────────────────────────────────

Deno.test("exit: khive kg update exits 1 (not implemented)", async () => {
const r = await runCli(["kg", "update"]);
assertEquals(r.code, 1);
});

// ─── In-repo commands ─────────────────────────────────────────────────────────

Deno.test("exit: kg validate on valid repo exits 0", async () => {
const repo = await makeTempRepo();
try {
const r = await runCliIn(repo.root, ["kg", "validate"]);
assertEquals(r.code, 0, `stderr: ${r.stderr}`);
} finally {
await repo.cleanup();
}
});

Deno.test("exit: kg stats on valid repo exits 0", async () => {
const repo = await makeTempRepo();
try {
const r = await runCliIn(repo.root, ["kg", "stats"]);
assertEquals(r.code, 0, `stderr: ${r.stderr}`);
} finally {
await repo.cleanup();
}
});

Deno.test("exit: kg doctor on valid repo exits 0", async () => {
const repo = await makeTempRepo();
try {
const r = await runCliIn(repo.root, ["kg", "doctor"]);
assertEquals(r.code, 0, `stderr: ${r.stderr}`);
} finally {
await repo.cleanup();
}
});

Deno.test("exit: kg status on valid repo exits 0", async () => {
const repo = await makeTempRepo();
try {
const r = await runCliIn(repo.root, ["kg", "status"]);
assertEquals(r.code, 0, `stderr: ${r.stderr}`);
} finally {
await repo.cleanup();
}
});
Loading
Loading