Files
hmbown--codewhale/crates/tui/tests/workflow_tool_stream_acceptance.rs
wehub-resource-sync d68f003000
CI / Change detection (push) Has been cancelled
CI / Version drift (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Workflow RLM cache (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / npm wrapper smoke (push) Has been cancelled
CI / Mobile runtime smoke (push) Has been cancelled
CI / Workflow lint (push) Has been cancelled
CI / Documentation (push) Has been cancelled
Web Frontend / Lint & Type Check (push) Failing after 1s
Auto-close harvested PRs / close (push) Failing after 1s
cargo-deny / cargo-deny (bans licenses sources) (push) Failing after 1s
Security audit / cargo-audit (push) Failing after 1s
Sync to CNB / sync (push) Failing after 1s
cargo-deny / cargo-deny (advisories) (push) Failing after 3s
Web Frontend / Deploy to Cloudflare (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:08:23 +08:00

146 lines
4.9 KiB
Rust

use std::path::PathBuf;
use std::process::{Command, Output};
fn codewhale_tui_binary() -> PathBuf {
if let Some(path) = option_env!("CARGO_BIN_EXE_codewhale-tui") {
return PathBuf::from(path);
}
if let Ok(path) = std::env::var("CARGO_BIN_EXE_codewhale-tui") {
return PathBuf::from(path);
}
let mut path = std::env::current_exe().expect("current test executable path");
path.pop();
if path.ends_with("deps") {
path.pop();
}
path.push(format!("codewhale-tui{}", std::env::consts::EXE_SUFFIX));
path
}
fn assert_terminal_stream_error(output: Output, expected_fragment: &str) {
assert!(
!output.status.success(),
"workflow-tool unexpectedly succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).expect("workflow-tool stdout is UTF-8");
let events = stdout
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| {
serde_json::from_str::<serde_json::Value>(line)
.unwrap_or_else(|error| panic!("invalid stream JSON {line:?}: {error}"))
})
.collect::<Vec<_>>();
let terminal = events.last().expect("terminal error event");
assert_eq!(terminal["type"], "error", "events={events:?}");
assert!(
terminal["error"]
.as_str()
.is_some_and(|error| error.contains(expected_fragment)),
"events={events:?}"
);
assert!(
events.iter().all(|event| event["type"] != "tool_use"),
"setup failure must happen before tool_use: {events:?}"
);
}
#[test]
fn invalid_workflow_input_is_terminal_ndjson() {
let output = Command::new(codewhale_tui_binary())
.args([
"workflow-tool",
"--approval-source",
"explicit-workflow-command",
"--input-json",
"{not-json",
])
.output()
.expect("run workflow-tool");
assert_terminal_stream_error(output, "valid Workflow tool input object");
}
#[test]
fn missing_profile_is_terminal_ndjson() {
let dir = tempfile::tempdir().expect("tempdir");
let config = dir.path().join("config.toml");
std::fs::write(&config, "provider = \"vllm\"\n").expect("write config");
let output = Command::new(codewhale_tui_binary())
.arg("--config")
.arg(&config)
.args([
"--profile",
"missing-profile",
"workflow-tool",
"--approval-source",
"explicit-workflow-command",
"--input-json",
r#"{"action":"run"}"#,
])
.env("CODEWHALE_HOME", dir.path().join("codewhale-home"))
.output()
.expect("run workflow-tool with missing profile");
assert_terminal_stream_error(output, "Profile 'missing-profile' not found");
}
#[test]
fn profile_provider_switch_accepts_source_marked_cli_key_offline() {
let dir = tempfile::tempdir().expect("tempdir");
let config = dir.path().join("config.toml");
std::fs::write(
&config,
r#"
provider = "deepseek"
[features]
mcp = false
[profiles.anthropic]
provider = "anthropic"
"#,
)
.expect("write profile config");
let output = Command::new(codewhale_tui_binary())
.arg("--config")
.arg(&config)
.args([
"--profile",
"anthropic",
"workflow-tool",
"--approval-source",
"explicit-workflow-command",
"--input-json",
r#"{"action":"run","script":"phase('offline'); return { ok: true };"}"#,
])
.env("CODEWHALE_HOME", dir.path().join("codewhale-home"))
.env("DEEPSEEK_API_KEY_SOURCE", "cli")
.env("CODEWHALE_CLI_API_KEY", "profile-switch-secret")
.output()
.expect("run profile-switched workflow-tool");
assert!(
output.status.success(),
"workflow-tool failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).expect("workflow-tool stdout is UTF-8");
let event_types = stdout
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| {
serde_json::from_str::<serde_json::Value>(line)
.unwrap_or_else(|error| panic!("invalid stream JSON {line:?}: {error}"))
})
.map(|event| event["type"].as_str().unwrap_or_default().to_string())
.collect::<Vec<_>>();
assert!(event_types.iter().any(|kind| kind == "tool_use"));
assert!(event_types.iter().any(|kind| kind == "tool_result"));
assert_eq!(event_types.last().map(String::as_str), Some("done"));
assert!(!stdout.contains("profile-switch-secret"));
assert!(!String::from_utf8_lossy(&output.stderr).contains("profile-switch-secret"));
}