返回 CodeWhale
debug_diagnostics_regression_tests.rs
根目录 / crates / tui / src / commands / debug_diagnostics_regression_tests.rs
1 //! Untouched-source parity samples for the diagnostics slice (FEAT-029).
2 //!
3 //! The checked-in fixture was captured on baseline 922679d6c. This test only
4 //! reads it; it never updates expected bytes. Timestamped reports and seeded
5 //! pricing/telemetry are captured separately in debug_diagnostics_baseline_tests;
6 //! both suites must pass again after the handlers move.
7
8 use super::{CommandResult, execute};
9 use crate::config::Config;
10 use crate::tui::app::{App, AppAction};
11 use codewhale_models::{SystemBlock, SystemPrompt};
12 use serde_json::{Value, json};
13
14 fn outcome(result: CommandResult) -> Value {
15 let action = match result.action {
16 None => Value::Null,
17 Some(AppAction::FetchBalance) => json!({"type": "FetchBalance"}),
18 Some(AppAction::CacheWarmup) => json!({"type": "CacheWarmup"}),
19 Some(AppAction::OpenContextInspector) => json!({"type": "OpenContextInspector"}),
20 Some(AppAction::OpenTextPager { title, content }) => {
21 json!({"type": "OpenTextPager", "title": title, "content": content})
22 }
23 Some(AppAction::PreviewOutboundRequest {
24 json,
25 base_prompt_only,
26 hypothetical_prompt,
27 }) => json!({"type": "PreviewOutboundRequest", "json": json,
28 "base_prompt_only": base_prompt_only, "hypothetical_prompt": hypothetical_prompt}),
29 Some(other) => panic!("unexpected diagnostics action: {other:?}"),
30 };
31 json!({"message": result.message, "action": action, "is_error": result.is_error})
32 }
33
34 fn collect() -> Value {
35 let cases = [
36 "/balance",
37 "/tokens",
38 "/cost",
39 "/cache",
40 "/cache 0",
41 "/cache stats",
42 "/cache zones",
43 "/cache inspect --verbose --json",
44 "/cache warmup",
45 "/cache nonsense",
46 "/preview-request",
47 "/dryrun json",
48 "/preview_request --base-prompt",
49 "/preview-request --json --prompt keep trailing ",
50 "/preview-request --base-prompt --json",
51 "/preview-request --prompt",
52 "/tools",
53 "/tool-studio invalid",
54 "/system",
55 "/xitong",
56 "/context",
57 "/ctx nonsense",
58 ];
59 let mut samples = serde_json::Map::new();
60 for command in cases {
61 // App::new already protects its own settings transaction. Never wrap it
62 // in with_test_state_io_lock: that mutex is non-reentrant.
63 let workspace = tempfile::tempdir().expect("owned fixture workspace");
64 let mut app = App::new(
65 crate::test_support::test_tui_options(workspace.path()),
66 &Config::default(),
67 );
68 app.ui_locale = codewhale_localization::Locale::En;
69 app.set_provider_identity_record(
70 crate::config::Config::default()
71 .resolve_provider_identity(crate::config::ProviderKind::Deepseek.as_str())
72 .expect("captured fixture provider"),
73 );
74 samples.insert(command.to_string(), outcome(execute(command, &mut app)));
75 }
76 for (label, prompt) in [
77 ("text", SystemPrompt::Text("Example policy".into())),
78 (
79 "utf8_boundary",
80 SystemPrompt::Text(format!("{}éEND", "a".repeat(499))),
81 ),
82 ] {
83 let workspace = tempfile::tempdir().expect("owned fixture workspace");
84 let mut app = App::new(
85 crate::test_support::test_tui_options(workspace.path()),
86 &Config::default(),
87 );
88 app.ui_locale = codewhale_localization::Locale::En;
89 app.system_prompt = Some(prompt);
90 samples.insert(
91 format!("/system:{label}"),
92 outcome(execute("/system", &mut app)),
93 );
94 }
95 for (label, command) in [
96 ("balance_unsupported", "/balance"),
97 ("tokens_reported_telemetry", "/tokens"),
98 ("cost_priced_zero", "/cost"),
99 ("cost_legacy_unknown", "/cost"),
100 ("system_blocks", "/system"),
101 ] {
102 let workspace = tempfile::tempdir().expect("owned fixture workspace");
103 let mut app = App::new(
104 crate::test_support::test_tui_options(workspace.path()),
105 &Config::default(),
106 );
107 app.ui_locale = codewhale_localization::Locale::En;
108 app.set_provider_identity_record(
109 crate::config::Config::default()
110 .resolve_provider_identity(crate::config::ProviderKind::Deepseek.as_str())
111 .expect("captured fixture provider"),
112 );
113 match label {
114 "balance_unsupported" => app.set_provider_identity_record(
115 crate::config::Config::default()
116 .resolve_provider_identity(crate::config::ProviderKind::OpenaiCodex.as_str())
117 .expect("captured fixture provider"),
118 ),
119 "tokens_reported_telemetry" => {
120 app.session.total_tokens = 1234;
121 app.session.last_prompt_tokens = Some(100);
122 app.session.last_completion_tokens = Some(25);
123 app.session.last_prompt_cache_hit_tokens = Some(70);
124 app.session.last_prompt_cache_miss_tokens = Some(30);
125 app.session.total_cache_write_tokens = 250;
126 }
127 "cost_priced_zero" => app.session.cost_priced_turns = 1,
128 "cost_legacy_unknown" => app.session.cost_coverage_unknown_legacy = true,
129 "system_blocks" => {
130 app.system_prompt = Some(SystemPrompt::Blocks(vec![
131 SystemBlock {
132 block_type: "text".into(),
133 text: "First block".into(),
134 cache_control: None,
135 },
136 SystemBlock {
137 block_type: "text".into(),
138 text: "Second block".into(),
139 cache_control: None,
140 },
141 ]));
142 }
143 _ => unreachable!("fixture case list is exhaustive"),
144 }
145 samples.insert(label.into(), outcome(execute(command, &mut app)));
146 }
147 Value::Object(samples)
148 }
149
150 #[test]
151 fn stable_branches_match_untouched_baseline() {
152 let expected: Value =
153 serde_json::from_str(include_str!("fixtures/diagnostics/stable-branches.json"))
154 .expect("reviewed baseline fixture must parse");
155 let actual = collect();
156 assert_eq!(actual, expected, "diagnostics parity diverged");
157 }
158
158 lines RUST