返回 CodeWhale
workflow_tool_stream_acceptance.rs
根目录 / crates / tui / tests / integration / workflow_tool_stream_acceptance.rs
1 use std::process::{Command, Output};
2
3 fn assert_terminal_stream_error(output: Output, expected_fragment: &str) {
4 assert!(
5 !output.status.success(),
6 "workflow-tool unexpectedly succeeded\nstdout:\n{}\nstderr:\n{}",
7 String::from_utf8_lossy(&output.stdout),
8 String::from_utf8_lossy(&output.stderr)
9 );
10 let stdout = String::from_utf8(output.stdout).expect("workflow-tool stdout is UTF-8");
11 let events = stdout
12 .lines()
13 .filter(|line| !line.trim().is_empty())
14 .map(|line| {
15 serde_json::from_str::<serde_json::Value>(line)
16 .unwrap_or_else(|error| panic!("invalid stream JSON {line:?}: {error}"))
17 })
18 .collect::<Vec<_>>();
19 let terminal = events.last().expect("terminal error event");
20 assert_eq!(terminal["type"], "error", "events={events:?}");
21 assert!(
22 terminal["error"]
23 .as_str()
24 .is_some_and(|error| error.contains(expected_fragment)),
25 "events={events:?}"
26 );
27 assert!(
28 events.iter().all(|event| event["type"] != "tool_use"),
29 "setup failure must happen before tool_use: {events:?}"
30 );
31 }
32
33 #[test]
34 fn invalid_workflow_input_is_terminal_ndjson() {
35 let output = Command::new(crate::binary::codewhale())
36 .args([
37 "workflow-tool",
38 "--approval-source",
39 "explicit-workflow-command",
40 "--input-json",
41 "{not-json",
42 ])
43 .output()
44 .expect("run workflow-tool");
45 assert_terminal_stream_error(output, "valid Workflow tool input object");
46 }
47
48 #[test]
49 fn missing_profile_is_terminal_ndjson() {
50 let dir = tempfile::tempdir().expect("tempdir");
51 let config = dir.path().join("config.toml");
52 std::fs::write(&config, "provider = \"vllm\"\n").expect("write config");
53 let output = Command::new(crate::binary::codewhale())
54 .arg("--config")
55 .arg(&config)
56 .args([
57 "--profile",
58 "missing-profile",
59 "workflow-tool",
60 "--approval-source",
61 "explicit-workflow-command",
62 "--input-json",
63 r#"{"action":"run"}"#,
64 ])
65 .env("CODEWHALE_HOME", dir.path().join("codewhale-home"))
66 .output()
67 .expect("run workflow-tool with missing profile");
68 assert_terminal_stream_error(output, "Profile 'missing-profile' not found");
69 }
70
71 #[test]
72 fn profile_provider_switch_accepts_source_marked_cli_key_offline() {
73 let dir = tempfile::tempdir().expect("tempdir");
74 let config = dir.path().join("config.toml");
75 std::fs::write(
76 &config,
77 r#"
78 provider = "deepseek"
79
80 [features]
81 mcp = false
82
83 [profiles.anthropic]
84 provider = "anthropic"
85 "#,
86 )
87 .expect("write profile config");
88 let output = Command::new(crate::binary::codewhale())
89 .arg("--config")
90 .arg(&config)
91 .args([
92 "--profile",
93 "anthropic",
94 "workflow-tool",
95 "--approval-source",
96 "explicit-workflow-command",
97 "--input-json",
98 r#"{"action":"run","script":"phase('offline'); return { ok: true };"}"#,
99 ])
100 .env("CODEWHALE_HOME", dir.path().join("codewhale-home"))
101 .env("DEEPSEEK_API_KEY_SOURCE", "cli")
102 .env("CODEWHALE_CLI_API_KEY", "profile-switch-secret")
103 .output()
104 .expect("run profile-switched workflow-tool");
105
106 assert!(
107 output.status.success(),
108 "workflow-tool failed\nstdout:\n{}\nstderr:\n{}",
109 String::from_utf8_lossy(&output.stdout),
110 String::from_utf8_lossy(&output.stderr)
111 );
112 let stdout = String::from_utf8(output.stdout).expect("workflow-tool stdout is UTF-8");
113 let event_types = stdout
114 .lines()
115 .filter(|line| !line.trim().is_empty())
116 .map(|line| {
117 serde_json::from_str::<serde_json::Value>(line)
118 .unwrap_or_else(|error| panic!("invalid stream JSON {line:?}: {error}"))
119 })
120 .map(|event| event["type"].as_str().unwrap_or_default().to_string())
121 .collect::<Vec<_>>();
122 assert!(event_types.iter().any(|kind| kind == "tool_use"));
123 assert!(event_types.iter().any(|kind| kind == "tool_result"));
124 assert_eq!(event_types.last().map(String::as_str), Some("done"));
125 assert!(!stdout.contains("profile-switch-secret"));
126 assert!(!String::from_utf8_lossy(&output.stderr).contains("profile-switch-secret"));
127 }
128
128 lines RUST