fix: harden TaskTree CLI contracts
This commit is contained in:
@@ -68,6 +68,10 @@ test("TaskTree command surface covers hierarchy, lifecycle, batch, stats, and re
|
||||
test("TaskTree help and public operations do not expose the legacy domain term", async () => {
|
||||
const result = await runTaskTreeCli(["--help"]);
|
||||
assert.doesNotMatch(JSON.stringify(result), /mdtodo/iu);
|
||||
assert.ok(result.usage.includes("hwlab-cli tasktree group list|overview"));
|
||||
assert.ok(result.usage.includes("hwlab-cli tasktree group get|delete|stats --group ID"));
|
||||
assert.match(result.usage.join("\n"), /task update .*--start ISO\|null.*--due ISO\|null/u);
|
||||
assert.doesNotMatch(result.usage.join("\n"), /report create/u);
|
||||
});
|
||||
|
||||
test("TaskTree rejects ambiguous title input and empty update patches before transport", async () => {
|
||||
@@ -80,3 +84,69 @@ test("TaskTree rejects ambiguous title input and empty update patches before tra
|
||||
(error: any) => error?.code === "update_patch_required"
|
||||
);
|
||||
});
|
||||
|
||||
test("TaskTree rejects unknown, duplicate, hidden, and extra CLI arguments", async () => {
|
||||
const cases: Array<[string[], string]> = [
|
||||
[["group", "list", "unexpected"], "invalid_arguments"],
|
||||
[["group", "list", "--bogus", "value"], "invalid_option"],
|
||||
[["group", "create", "--name", "One", "--name", "Two"], "duplicate_option"],
|
||||
[["report", "create", "--task", "tt_1", "--title", "Hidden", "--body", "body"], "unsupported_command"],
|
||||
[["group", "list", "--api-url", "http://tasktree.test"], "invalid_option"],
|
||||
[["group", "list", "--stdin"], "invalid_option"]
|
||||
];
|
||||
for (const [argv, code] of cases) {
|
||||
await assert.rejects(runTaskTreeCli(argv), (error: any) => error?.code === code);
|
||||
}
|
||||
});
|
||||
|
||||
test("TaskTree accepts an explicit empty description value", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let received: unknown;
|
||||
globalThis.fetch = (async (_input, init) => {
|
||||
received = JSON.parse(String(init?.body));
|
||||
return Response.json({ ok: true, operation: "group.create", data: {} });
|
||||
}) as typeof fetch;
|
||||
try {
|
||||
await runTaskTreeCli(
|
||||
["group", "create", "--name", "Group", "--description", "", "--overapi"],
|
||||
{ HWLAB_TASKTREE_API_URL: "http://tasktree.test" }
|
||||
);
|
||||
assert.deepEqual(received, { operation: "group.create", name: "Group", description: "" });
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("TaskTree returns stable transport and runtime configuration errors", async () => {
|
||||
await assert.rejects(
|
||||
runTaskTreeCli(["group", "list", "--overapi"]),
|
||||
(error: any) => error?.code === "missing_api_url"
|
||||
);
|
||||
await assert.rejects(
|
||||
runTaskTreeCli(["health"], {}),
|
||||
(error: any) => error?.code === "missing_database_url"
|
||||
);
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
try {
|
||||
globalThis.fetch = (async () => { throw new Error("connection refused"); }) as typeof fetch;
|
||||
await assert.rejects(
|
||||
runTaskTreeCli(["group", "list", "--overapi"], { HWLAB_TASKTREE_API_URL: "http://tasktree.test" }),
|
||||
(error: any) => error?.code === "api_unreachable"
|
||||
);
|
||||
|
||||
for (const response of [
|
||||
new Response("not json", { status: 502 }),
|
||||
Response.json({ ok: true, operation: "task.get", data: {} }),
|
||||
Response.json({ ok: true, operation: "group.list", data: {} }, { status: 500 })
|
||||
]) {
|
||||
globalThis.fetch = (async () => response) as typeof fetch;
|
||||
await assert.rejects(
|
||||
runTaskTreeCli(["group", "list", "--overapi"], { HWLAB_TASKTREE_API_URL: "http://tasktree.test" }),
|
||||
(error: any) => error?.code === "invalid_api_response"
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user