Skip to content
Closed
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
31 changes: 31 additions & 0 deletions src/cosh-ng/crates/cosh-shell/tests/raw_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,34 @@ mod support;

pub(crate) use i18n::*;
use support::raw_cli::*;

pub(crate) fn assert_approval_prompt_visible(output: &str) {
assert!(
output.contains("Approval required") || output.contains("Approval req-1"),
"{output}"
);
}

pub(crate) fn assert_zh_approval_prompt_visible(output: &str) {
assert!(
output.contains("需要审批") || output.contains("审批 req-1"),
"{output}"
);
}

pub(crate) fn count_approval_prompts(output: &str) -> usize {
count_occurrences(output, "Approval required") + count_occurrences(output, "Approval req-")
}

pub(crate) fn count_zh_approval_prompts(output: &str) -> usize {
count_occurrences(output, "需要审批") + count_occurrences(output, "审批 req-")
}

pub(crate) fn ls_ccc_failure_analysis(output: &str) -> Option<&'static str> {
[
"The command ls ccc failed with exit code 1.",
"The command ls ccc failed with exit code 2.",
]
.into_iter()
.find(|message| output.contains(message))
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,7 @@ fn raw_cli_details_for_approval_uses_structured_panel() {
(b"exit\n".to_vec(), Duration::from_millis(800)),
]);

assert!(
output.contains("Approval required") || output.contains("Approval req-1"),
"{output}"
);
assert_approval_prompt_visible(&output);
assert!(output.contains("tool request"), "{output}");
assert!(output.contains("Cancelled req-1"), "{output}");
assert!(output.contains("medium risk"), "{output}");
Expand Down Expand Up @@ -187,7 +184,7 @@ fn raw_cli_details_for_approval_uses_zh_language_env() {
],
);

assert!(output.contains("需要审批"), "{output}");
assert_zh_approval_prompt_visible(&output);
assert!(output.contains("已取消 req-1"), "{output}");
assert!(output.contains("风险 medium"), "{output}");
assert!(
Expand All @@ -197,6 +194,7 @@ fn raw_cli_details_for_approval_uses_zh_language_env() {
assert!(output.contains("命令:"), "{output}");
assert!(output.contains("git status"), "{output}");
assert!(!output.contains("Approval required"), "{output}");
assert!(!output.contains("Approval req-1"), "{output}");
assert!(!output.contains("Approval details"), "{output}");
assert!(!output.contains("Cancelled req-1"), "{output}");
assert!(
Expand Down Expand Up @@ -224,7 +222,7 @@ fn raw_cli_multiline_bash_tool_is_visible_in_approval_details() {
(b"exit\n".to_vec(), Duration::from_millis(200)),
]);

assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(output.contains("Command:"), "{output}");
assert!(output.contains("printf one"), "{output}");
assert!(output.contains("printf two"), "{output}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,27 @@ fn raw_cli_streaming_tool_approval_renders_before_agent_finishes() {
]);

assert!(output.contains("Preparing a streamed tool request before finishing."));
assert!(output.contains("Approval required"));
assert_approval_prompt_visible(&output);
assert!(output.contains("Subject: Bash"));
assert!(output.contains("$ git status --short"));
assert!(output.contains("medium risk"));
assert!(!output.contains("Subject: tool Bash"));
assert!(!output.contains("Command: git status --short"));
assert!(!output.contains("Keys: Left/Right select"));
assert!(output.contains("Approved req-1"), "{output}");
assert!(output.contains("sent to shell"), "{output}");
assert!(!output.contains("Bash tool - approved"), "{output}");
assert!(output.contains("$ git status --short"), "{output}");
assert!(!output.contains("Tool result for request req-1"));
assert!(!output.contains("Received approved tool result"));
let approval_marker = if output.contains("Approval req-1") {
"Approval req-1"
} else {
"Approval required"
};
assert_inline_before_followup(
&output,
"Preparing a streamed tool request before finishing.",
"Approval required",
approval_marker,
);
assert!(!output.contains("Analysis continued after the approved command"));
assert!(!output.contains("stdout captured; [Details]"), "{output}");
Expand All @@ -46,7 +50,7 @@ fn raw_cli_approved_bash_tool_prints_native_command_and_stdout() {
let expected_cwd = env!("CARGO_MANIFEST_DIR");

assert!(output.contains("Preparing a streamed pwd request before finishing."));
assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(output.contains("Subject: Bash"), "{output}");
assert!(output.contains("$ pwd"), "{output}");
assert!(output.contains(expected_cwd), "{output}");
Expand All @@ -73,7 +77,7 @@ fn raw_cli_approved_bash_tool_streams_delayed_output_before_analysis() {
let normalized = output.replace('\r', "");

assert!(output.contains("Preparing a delayed streamed tool request before finishing."));
assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(
output.contains("$ sleep 1; echo a; sleep 1; echo b"),
"{output}"
Expand Down Expand Up @@ -103,7 +107,7 @@ fn raw_cli_approved_bash_tool_streams_stderr_to_transcript() {
]);

assert!(output.contains("Preparing a stderr streamed tool request before finishing."));
assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(
output.contains("$ printf 'out\\n'; printf 'err\\n' >&2"),
"{output}"
Expand Down Expand Up @@ -147,7 +151,7 @@ readonly_disabled = ["git status", "pwd"]"#,
],
);

assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(output.contains("Approved req-1"), "{output}");
assert!(output.contains("$ sudo printf approved-sudo"), "{output}");
assert!(output.contains("fake-sudo:approved-sudo"), "{output}");
Expand Down Expand Up @@ -279,7 +283,7 @@ fn raw_cli_approved_bash_tool_drops_stale_pre_approval_followup() {
let expected_cwd = env!("CARGO_MANIFEST_DIR");

assert!(output.contains("Preparing a command before approval."));
assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(output.contains("req-1"), "{output}");
assert!(output.contains("Approved req-1"), "{output}");
assert!(output.contains("sent to shell"), "{output}");
Expand All @@ -303,7 +307,7 @@ fn raw_cli_denied_bash_tool_does_not_render_stale_executed_claim() {
let expected_cwd = env!("CARGO_MANIFEST_DIR");

assert!(output.contains("Preparing a streamed pwd request before finishing."));
assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(output.contains("Subject: Bash"), "{output}");
assert!(output.contains("Denied req-1"), "{output}");
assert!(!output.contains("No command ran."), "{output}");
Expand All @@ -330,13 +334,13 @@ fn raw_cli_denied_bash_tool_uses_zh_language_env() {
&[("COSH_SHELL_LANG", "zh-CN")],
vec![
(b"?? stream pwd tool approval\n".to_vec(), Duration::ZERO),
(b"\x1b[C\x1b[C\n".to_vec(), Duration::from_millis(800)),
(b"\x1b[C\x1b[C\n".to_vec(), Duration::from_millis(2_500)),
(b"exit\n".to_vec(), Duration::from_millis(300)),
],
);
let expected_cwd = env!("CARGO_MANIFEST_DIR");

assert!(output.contains("需要审批"), "{output}");
assert_zh_approval_prompt_visible(&output);
assert!(output.contains("对象: Bash"), "{output}");
assert!(output.contains("已拒绝 req-1"), "{output}");
assert!(output.contains("$ pwd"), "{output}");
Expand All @@ -345,6 +349,7 @@ fn raw_cli_denied_bash_tool_uses_zh_language_env() {
"{output}"
);
assert!(!output.contains("Approval required"), "{output}");
assert!(!output.contains("Approval req-1"), "{output}");
assert!(!output.contains("Subject: Bash"), "{output}");
assert!(!output.contains("Denied req-1"), "{output}");
assert!(!output.contains(expected_cwd), "{output}");
Expand All @@ -367,12 +372,11 @@ fn raw_cli_user_approved_bash_tool_supports_pipe() {
);

assert!(output.contains("Preparing a piped streamed tool request before finishing."));
assert!(output.contains("Approval required"));
assert_approval_prompt_visible(&output);
assert!(output.contains("Subject: Bash"));
assert!(output.contains("$ ps aux | head"));
assert!(output.contains("Approved req-1"), "{output}");
assert!(!output.contains("Blocked req-1"), "{output}");
assert!(!output.contains("Keys: Left/Right select"));
assert!(output.contains("$ ps aux | head"), "{output}");
assert!(
!output.contains("cosh-shell: blocked shell metacharacter"),
Expand Down
12 changes: 6 additions & 6 deletions src/cosh-ng/crates/cosh-shell/tests/raw_cli/approval/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ fn raw_cli_approval_text_input_does_not_confirm_or_leak_to_bash() {
(b"exit\n".to_vec(), Duration::from_millis(200)),
]);

assert!(output.contains("Approval required"));
assert!(output.contains("req-1 · tool request · medium risk"));
assert_approval_prompt_visible(&output);
assert!(output.contains("tool request") && output.contains("medium risk"));
assert!(output.contains("Cancelled"));
assert!(output.contains("$ git status"));
assert!(!output.contains("No command ran."));
Expand All @@ -33,8 +33,8 @@ fn raw_cli_approval_split_arrow_sequence_does_not_cancel() {
(b"exit\n".to_vec(), Duration::from_millis(1_000)),
]);

assert!(output.contains("Approval required"));
assert!(output.contains("req-1 · tool request · medium risk"));
assert_approval_prompt_visible(&output);
assert!(output.contains("tool request") && output.contains("medium risk"));
assert!(
output.contains("> [ Deny ]") || output.contains("[Deny]"),
"{output}"
Expand All @@ -58,8 +58,8 @@ fn raw_cli_approval_application_cursor_arrow_updates_focus() {
(b"exit\n".to_vec(), Duration::from_millis(1_000)),
]);

assert!(output.contains("Approval required"));
assert!(output.contains("req-1 · tool request · medium risk"));
assert_approval_prompt_visible(&output);
assert!(output.contains("tool request") && output.contains("medium risk"));
assert!(
output.contains("> [ Deny ]") || output.contains("[Deny]"),
"{output}"
Expand Down
25 changes: 12 additions & 13 deletions src/cosh-ng/crates/cosh-shell/tests/raw_cli/approval/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ fn raw_cli_zsh_approval_card_capture_does_not_leak_to_shell() {
],
);

assert!(
output.contains("Approval required") || output.contains("Approval req-1"),
"{output}"
);
assert_approval_prompt_visible(&output);
assert!(output.contains("Denied"), "{output}");
assert!(output.contains("$ git status --short"), "{output}");
assert!(!output.contains("No command ran."), "{output}");
Expand All @@ -45,10 +42,13 @@ fn raw_cli_approval_cancel_records_receipt_and_advances_queue() {
(b"exit\n".to_vec(), Duration::from_millis(200)),
]);

assert!(output.contains("Approval required"));
assert!(output.contains("req-1 · tool request · medium risk"));
assert_approval_prompt_visible(&output);
assert!(output.contains("tool request") && output.contains("medium risk"));
assert!(output.contains("$ git status"));
assert!(output.contains("Queue: 1 of 1 pending"));
assert!(
output.contains("Queue: 1/1 pending") || output.contains("Queue: 1 of 1 pending"),
"{output}"
);
assert!(!output.contains("req-1 · shell tool · medium risk"));
assert!(output.contains("Cancelled"), "{output}");
assert!(!output.contains("Cancelled req-2"));
Expand All @@ -75,7 +75,7 @@ fn raw_cli_approval_ctrl_c_cancels_card_without_agent_cancel() {
(b"exit\n".to_vec(), Duration::from_millis(200)),
]);

assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(output.contains("Cancelled req-1"), "{output}");
assert!(output.contains("after-approval-ctrl-c"), "{output}");
assert!(!output.contains("Agent cancellation requested"), "{output}");
Expand All @@ -98,18 +98,17 @@ fn raw_cli_approval_card_uses_zh_language_env() {
],
);

assert!(output.contains("需要审批"), "{output}");
assert_zh_approval_prompt_visible(&output);
assert!(output.contains("对象: Bash"), "{output}");
assert!(
output.contains("Tool 输入: $ git status --short"),
"{output}"
);
assert!(output.contains("Tool 输入:"), "{output}");
assert!(output.contains("$ git status --short"), "{output}");
assert!(output.contains("允许一次"), "{output}");
assert!(output.contains("始终信任"), "{output}");
assert!(output.contains("拒绝"), "{output}");
assert!(output.contains("已批准 req-1"), "{output}");
assert!(output.contains("已发送到 shell"), "{output}");
assert!(!output.contains("Approval required"), "{output}");
assert!(!output.contains("Approval req-1"), "{output}");
assert!(!output.contains("Subject: Bash"), "{output}");
assert!(!output.contains("Allow once"), "{output}");
assert!(!output.contains("Always trust"), "{output}");
Expand Down
3 changes: 1 addition & 2 deletions src/cosh-ng/crates/cosh-shell/tests/raw_cli/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,10 @@ language = "zh-CN"
&[("HOME", &home_str), ("COSH_SHELL_LANG", RAW_CLI_UNSET_ENV)],
);

assert!(output.contains("Config:"), "{output}");
assert!(output.contains("Config"), "{output}");
assert!(output.contains("language: en-US effective"), "{output}");
assert!(!output.contains("语言: zh-CN 来源: config"), "{output}");
assert!(output.contains(".copilot-shell"), "{output}");
assert!(output.contains("config.toml"), "{output}");
assert!(!output.contains("bash: /config"), "{output}");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,19 @@ printf '%s\n' '{{"type":"result","subtype":"success","session_id":"sess-cosh-cor
output.contains("Run /mode approval trust confirm"),
"{output}"
);
assert!(output.contains("Approval required"), "{output}");
assert!(output.contains(&command), "{output}");
assert_approval_prompt_visible(&output);
let compact_output: String = output
.chars()
.filter(|ch| {
!ch.is_whitespace()
&& !matches!(
ch,
'│' | '─' | '╭' | '╮' | '╰' | '╯' | '┌' | '┐' | '└' | '┘'
)
})
.collect();
assert!(compact_output.contains("touch"), "{output}");
assert!(compact_output.contains("should-not-exist"), "{output}");
assert!(!output.contains("Mode set to trust."), "{output}");
assert!(!output.contains("Auto-approved req-1"), "{output}");
assert!(!output.contains("Trusted req-1"), "{output}");
Expand Down Expand Up @@ -182,7 +193,7 @@ printf '%s\n' '{"type":"result","subtype":"success","session_id":"sess-cosh-core
);
let _ = fs::remove_dir_all(&home);

assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(output.contains("Subject: Write"), "{output}");
assert!(
output.contains("/tmp/cosh-core-provider-smoke.txt"),
Expand Down Expand Up @@ -261,7 +272,7 @@ printf '%s\n' '{"type":"result","subtype":"success","session_id":"sess-cosh-core
);
let _ = fs::remove_dir_all(&home);

assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(output.contains("Subject: Write"), "{output}");
assert!(
output.contains("/tmp/cosh-core-provider-details.txt"),
Expand Down Expand Up @@ -338,7 +349,7 @@ printf '%s\n' '{{"type":"result","subtype":"success","session_id":"sess-cosh-cor
);
let _ = fs::remove_dir_all(&home);

assert!(output.contains("Approval required"), "{output}");
assert_approval_prompt_visible(&output);
assert!(output.contains("Subject: Write"), "{output}");
assert!(output.contains("Denied req-1"), "{output}");
assert!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,8 @@ printf '%s\n' '{"type":"system","subtype":"init","session_id":"sess-cosh-core-ho
read -r user_message
case "$user_message" in
*cosh-core-provider-host-executed-disconnect*)
printf '%s\n' '{"type":"control_request","request_id":"ctrl-cosh-core-disconnect","request":{"subtype":"can_use_tool","tool_name":"shell","input":{"command":"df -h"},"tool_use_id":"toolu-cosh-core-disconnect"}}'
exit 0
printf '%s\n' '{"type":"control_request","request_id":"ctrl-cosh-core-disconnect","request":{"subtype":"can_use_tool","tool_name":"shell","input":{"command":"sleep 1; df -h"},"tool_use_id":"toolu-cosh-core-disconnect"}}'
kill -9 "$$"
;;
esac
printf '%s\n' '{"type":"result","subtype":"success","session_id":"sess-cosh-core-host-executed-disconnect","is_error":false,"result":"ignored"}'
Expand All @@ -298,7 +298,7 @@ printf '%s\n' '{"type":"result","subtype":"success","session_id":"sess-cosh-core
&[],
&[("HOME", &home_str), ("COSH_CORE_PATH", &cosh_core_path_str)],
vec![
(b"/mode approval auto\n".to_vec(), Duration::ZERO),
(b"/mode approval trust confirm\n".to_vec(), Duration::ZERO),
(
b"?? cosh-core-provider-host-executed-disconnect\n".to_vec(),
Duration::from_millis(500),
Expand All @@ -315,7 +315,7 @@ printf '%s\n' '{"type":"result","subtype":"success","session_id":"sess-cosh-core

assert!(output.contains("Auto-approved req-1"), "{output}");
assert!(output.contains("Bash tool sent to shell"), "{output}");
assert!(output.contains("$ df -h"), "{output}");
assert!(output.contains("$ sleep 1; df -h"), "{output}");
assert!(
output.contains("selected_shell_execution_path: foreground_shell_handoff_recovery"),
"{output}"
Expand Down
Loading
Loading