mirror of
https://github.com/instructkr/claude-code.git
synced 2026-05-27 07:56:46 +00:00
test(#775): integration tests for #769-#771 interactive-only guards and #774 hint fields; fix stale classifier unit test string
This commit is contained in:
@@ -7715,3 +7715,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
|
||||
773. **Config deprecation warnings only emitted as unstructured stderr text in `--output-format json` mode** — dogfooded 2026-05-27 on `212f0b2a`. `emit_config_warning_once()` always wrote to stderr regardless of output format, causing JSON-mode callers to receive an unexpected `warning: ...` text line on stderr before the JSON object. Callers had to implement ad-hoc stripping. Fix: added `ConfigLoader::load_collecting_warnings()` method that returns `(RuntimeConfig, Vec<String>)` so callers can surface warnings structurally; `render_config_json()` now uses this and includes a `warnings: []` array in the config JSON envelope. Existing `load()` path unchanged (still emits to stderr for text-mode callers). 36 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori startup-friction probe on `212f0b2a`, 2026-05-27.
|
||||
|
||||
774. **`claw agents bogus`, `claw plugins bogus`, `claw mcp bogus` returned `hint: null`** — dogfooded 2026-05-27 on `727a1ea4`. Three "unknown subcommand" envelopes had `error_kind` correctly set but `hint: null`: (1) `unknown_agents_subcommand` — both text and JSON handler emitted single-line error with inline remediation after `.`, no `\n`; (2) `unknown_plugins_action` — same, period-delimited remediation; (3) `unknown_mcp_action` — `render_mcp_usage_json` never included a `hint` field at all. Fixes: (1)+(2) added `\n` before remediation suffix in `commands/src/lib.rs`; (3) added `hint` field to `render_mcp_usage_json` pointing at supported actions. All three now return non-null `hint`. 36 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori envelope-consistency probe on `727a1ea4`, 2026-05-27.
|
||||
|
||||
775. **Missing integration tests for #769-#771 interactive-only guards and #774 hint fields** — dogfooded 2026-05-27 on `c760a49c`. Fixes #769-#771 (session/cost/clear/memory/ultraplan/model/usage/stats/fork interactive-only guards) and #774 (agents/plugins/mcp unknown-subcommand hints) had no integration tests — a regression in any of those 10+ match arms would go undetected. Also: classify_error_kind unit test for `unknown_agents_subcommand` used the old single-line format string, not the `\n`-delimited format emitted after #774. Fixed: (1) updated unit test string to match new `\n`-delimited emission; (2) added `agents_plugins_mcp_unknown_subcommand_have_hint_774` asserting `error_kind` + non-null `hint` for all three; (3) added `interactive_only_guard_batch_769_to_771` asserting `interactive_only` + non-null `hint` for 10 cases. 38 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori test-coverage sweep on `c760a49c`, 2026-05-27.
|
||||
|
||||
@@ -12965,8 +12965,9 @@ mod tests {
|
||||
classify_error_kind("slash command /compact is interactive-only"),
|
||||
"interactive_only"
|
||||
);
|
||||
// #774: agents now uses \n-delimited format — update test string to match real emission
|
||||
assert_eq!(
|
||||
classify_error_kind("unknown agents subcommand: bogus. Supported: list, show, help"),
|
||||
classify_error_kind("unknown agents subcommand: bogus.\nSupported: list, show, help"),
|
||||
"unknown_agents_subcommand"
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -2076,3 +2076,142 @@ fn slash_only_verbs_with_args_return_interactive_only_not_credentials_770() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agents_plugins_mcp_unknown_subcommand_have_hint_774() {
|
||||
// #774: `claw agents bogus`, `claw plugins bogus`, `claw mcp bogus` returned
|
||||
// hint:null despite having correct error_kind. Fixed by adding \n delimiter
|
||||
// to error strings in commands/src/lib.rs and explicit hint in mcp JSON envelope.
|
||||
let root = unique_temp_dir("unknown-subcommands-774");
|
||||
fs::create_dir_all(&root).expect("temp dir should exist");
|
||||
|
||||
// agents bogus
|
||||
{
|
||||
let output = run_claw(&root, &["--output-format", "json", "agents", "bogus"], &[]);
|
||||
assert!(!output.status.success(), "agents bogus should fail");
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let json_line = stderr
|
||||
.lines()
|
||||
.find(|l| l.trim_start().starts_with('{'))
|
||||
.expect("agents bogus should emit JSON error");
|
||||
let parsed: serde_json::Value = serde_json::from_str(json_line).unwrap();
|
||||
assert_eq!(parsed["error_kind"], "unknown_agents_subcommand");
|
||||
let hint = parsed["hint"].as_str().unwrap_or("");
|
||||
assert!(
|
||||
!hint.is_empty(),
|
||||
"agents bogus hint must be non-null (#774)"
|
||||
);
|
||||
assert!(
|
||||
hint.contains("list") || hint.contains("show") || hint.contains("help"),
|
||||
"agents bogus hint must mention supported actions, got: {hint:?}"
|
||||
);
|
||||
}
|
||||
|
||||
// plugins bogus
|
||||
{
|
||||
let output = run_claw(&root, &["--output-format", "json", "plugins", "bogus"], &[]);
|
||||
assert!(!output.status.success(), "plugins bogus should fail");
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let json_line = stderr
|
||||
.lines()
|
||||
.find(|l| l.trim_start().starts_with('{'))
|
||||
.expect("plugins bogus should emit JSON error");
|
||||
let parsed: serde_json::Value = serde_json::from_str(json_line).unwrap();
|
||||
assert_eq!(parsed["error_kind"], "unknown_plugins_action");
|
||||
let hint = parsed["hint"].as_str().unwrap_or("");
|
||||
assert!(
|
||||
!hint.is_empty(),
|
||||
"plugins bogus hint must be non-null (#774)"
|
||||
);
|
||||
}
|
||||
|
||||
// mcp bogus
|
||||
{
|
||||
let output = run_claw(&root, &["--output-format", "json", "mcp", "bogus"], &[]);
|
||||
assert!(!output.status.success(), "mcp bogus should fail");
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let json_str = if stdout.trim().starts_with('{') {
|
||||
stdout.to_string()
|
||||
} else {
|
||||
stderr
|
||||
.lines()
|
||||
.find(|l| l.trim_start().starts_with('{'))
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
};
|
||||
let parsed: serde_json::Value =
|
||||
serde_json::from_str(json_str.trim()).expect("mcp bogus should emit JSON");
|
||||
assert_eq!(parsed["error_kind"], "unknown_mcp_action");
|
||||
let hint = parsed["hint"].as_str().unwrap_or("");
|
||||
assert!(!hint.is_empty(), "mcp bogus hint must be non-null (#774)");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interactive_only_guard_batch_769_to_771() {
|
||||
// #769-#771: a sweep of slash-only verbs with args that previously fell to
|
||||
// CliAction::Prompt hitting the credential gate. All must return
|
||||
// error_kind:interactive_only (not missing_credentials) with non-null hint.
|
||||
let root = unique_temp_dir("interactive-only-batch-769-771");
|
||||
fs::create_dir_all(&root).expect("temp dir should exist");
|
||||
// Need a git repo for some subcommands
|
||||
std::process::Command::new("git")
|
||||
.args(["init", "-q"])
|
||||
.current_dir(&root)
|
||||
.output()
|
||||
.ok();
|
||||
|
||||
let cases: &[&[&str]] = &[
|
||||
// #769: session with unknown subcommand
|
||||
&["session", "bogus"],
|
||||
&["session", "nuke"],
|
||||
// #770: slash-only verbs with trailing args
|
||||
&["cost", "breakdown"],
|
||||
&["clear", "--force"],
|
||||
&["memory", "reset"],
|
||||
&["ultraplan", "bogus"],
|
||||
&["model", "opus", "extra"],
|
||||
// #771: usage/stats/fork
|
||||
&["usage", "extra"],
|
||||
&["stats", "extra"],
|
||||
&["fork", "newbranch"],
|
||||
];
|
||||
|
||||
for args in cases {
|
||||
let full_args: Vec<&str> = std::iter::once("--output-format")
|
||||
.chain(std::iter::once("json"))
|
||||
.chain(args.iter().copied())
|
||||
.collect();
|
||||
let output = run_claw(&root, &full_args, &[]);
|
||||
assert!(
|
||||
!output.status.success(),
|
||||
"claw {} should exit non-zero",
|
||||
args.join(" ")
|
||||
);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let json_line = stderr
|
||||
.lines()
|
||||
.find(|l| l.trim_start().starts_with('{'))
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"claw {} should emit JSON, got stderr: {stderr}",
|
||||
args.join(" ")
|
||||
)
|
||||
});
|
||||
let parsed: serde_json::Value = serde_json::from_str(json_line).unwrap();
|
||||
assert_eq!(
|
||||
parsed["error_kind"],
|
||||
"interactive_only",
|
||||
"claw {} must return interactive_only, got {:?}",
|
||||
args.join(" "),
|
||||
parsed["error_kind"]
|
||||
);
|
||||
let hint = parsed["hint"].as_str().unwrap_or("");
|
||||
assert!(
|
||||
!hint.is_empty(),
|
||||
"claw {} must have non-null hint",
|
||||
args.join(" ")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user