mirror of
https://github.com/instructkr/claude-code.git
synced 2026-05-27 16:06:44 +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.
|
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.
|
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"),
|
classify_error_kind("slash command /compact is interactive-only"),
|
||||||
"interactive_only"
|
"interactive_only"
|
||||||
);
|
);
|
||||||
|
// #774: agents now uses \n-delimited format — update test string to match real emission
|
||||||
assert_eq!(
|
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"
|
"unknown_agents_subcommand"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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