返回 CodeWhale
telemetry_surface.rs
根目录 / crates / tui / src / tests / telemetry_surface.rs
1 use super::*;
2 use clap::Parser;
3 use codewhale_telemetry::{SessionSource, Surface};
4
5 fn command_of(args: &[&str]) -> Option<Commands> {
6 Cli::try_parse_from(args)
7 .expect("CLI args should parse")
8 .command
9 }
10
11 #[test]
12 fn every_surface_is_named_by_the_subcommand_not_the_executable() {
13 assert_eq!(telemetry_surface(None), Surface::Tui);
14 assert_eq!(
15 telemetry_surface(command_of(&["codewhale-tui", "resume", "--last"]).as_ref()),
16 Surface::Tui
17 );
18 assert_eq!(
19 telemetry_surface(command_of(&["codewhale-tui", "fork", "--last"]).as_ref()),
20 Surface::Tui
21 );
22 assert_eq!(
23 telemetry_surface(command_of(&["codewhale-tui", "pr", "42"]).as_ref()),
24 Surface::Tui
25 );
26 assert_eq!(
27 telemetry_surface(command_of(&["codewhale-tui", "exec", "hello"]).as_ref()),
28 Surface::Exec
29 );
30 assert_eq!(
31 telemetry_surface(command_of(&["codewhale-tui", "serve", "--http"]).as_ref()),
32 Surface::Serve
33 );
34 assert_eq!(
35 telemetry_surface(command_of(&["codewhale-tui", "serve", "--mcp"]).as_ref()),
36 Surface::McpServer
37 );
38 assert_eq!(
39 telemetry_surface(command_of(&["codewhale-tui", "doctor"]).as_ref()),
40 Surface::Cli
41 );
42 }
43
44 #[test]
45 fn read_only_commands_never_arm_usage_counting() {
46 for args in [
47 vec!["codewhale-tui", "doctor"],
48 vec!["codewhale-tui", "doctor", "--json"],
49 vec!["codewhale-tui", "session-diagnostics", "session.jsonl"],
50 vec!["codewhale-tui", "sessions"],
51 vec!["codewhale-tui", "setup", "--status"],
52 ] {
53 let command = command_of(&args);
54 assert!(
55 telemetry_command_is_read_only(command.as_ref()),
56 "{args:?} must remain state-free"
57 );
58 }
59
60 for args in [
61 vec!["codewhale-tui", "exec", "hello"],
62 vec!["codewhale-tui", "setup", "--skills"],
63 ] {
64 let command = command_of(&args);
65 assert!(
66 !telemetry_command_is_read_only(command.as_ref()),
67 "{args:?} is not a read-only command"
68 );
69 }
70 }
71
72 #[test]
73 fn windows_resume_session_listing_cannot_consume_the_process_telemetry_arm() {
74 // Bare `codewhale resume` on Windows invokes `sessions` in-process before
75 // starting the selected resumed TUI. The listing must remain state-free so
76 // that the process-global telemetry initializer is still available to the
77 // resumed TUI after its native privacy decision.
78 let command = command_of(&["codewhale-tui", "sessions"]);
79 assert_eq!(telemetry_surface(command.as_ref()), Surface::Cli);
80 assert!(telemetry_command_is_read_only(command.as_ref()));
81 }
82
83 #[test]
84 fn unreadable_existing_setup_state_suppresses_telemetry_instead_of_defaulting_on() {
85 let dir = tempfile::tempdir().expect("tempdir");
86 let state_path = dir.path().join("setup_state.json");
87 std::fs::write(&state_path, "not-json").expect("seed corrupt state");
88
89 assert!(codewhale_telemetry::load_setup_state_for_decision_at(&state_path).is_none());
90 assert_eq!(
91 std::fs::read_to_string(state_path).expect("corrupt state remains untouched"),
92 "not-json"
93 );
94 }
95
96 #[test]
97 fn the_session_source_distinguishes_resume_and_fork_from_a_fresh_launch() {
98 assert_eq!(telemetry_session_source(None), SessionSource::Interactive);
99 assert_eq!(
100 telemetry_session_source(command_of(&["codewhale-tui", "resume", "--last"]).as_ref()),
101 SessionSource::Resume
102 );
103 assert_eq!(
104 telemetry_session_source(command_of(&["codewhale-tui", "fork", "--last"]).as_ref()),
105 SessionSource::Fork
106 );
107 assert_eq!(
108 telemetry_session_source(command_of(&["codewhale-tui", "pr", "42"]).as_ref()),
109 SessionSource::Interactive
110 );
111 assert_eq!(
112 telemetry_session_source(command_of(&["codewhale-tui", "serve", "--http"]).as_ref()),
113 SessionSource::Api
114 );
115 assert_eq!(
116 telemetry_session_source(command_of(&["codewhale-tui", "doctor"]).as_ref()),
117 SessionSource::Unknown
118 );
119 }
120
121 #[test]
122 fn a_session_end_built_without_arming_writes_nothing() {
123 let event = telemetry_session_end();
124 assert!(matches!(
125 event,
126 codewhale_telemetry::Event::SessionEnd { .. }
127 ));
128 codewhale_telemetry::record_blocking(event);
129 assert!(!codewhale_telemetry::is_armed());
130 }
131
132 #[test]
133 fn canceled_run_reports_exit_class_error_not_signal() {
134 use crate::core::termination::RunTerminationReason;
135 assert_eq!(RunTerminationReason::Canceled.process_exit_code(), 130);
136 assert_eq!(
137 codewhale_telemetry::ExitClass::Signal.as_str(),
138 "signal",
139 "the SIGINT path's class is a distinct value, not a synonym for error"
140 );
141 assert!(!RunTerminationReason::Canceled.is_success());
142 assert!(RunTerminationReason::Resolved.is_success());
143 assert!(!codewhale_telemetry::is_armed());
144 codewhale_telemetry::set_exit_class(codewhale_telemetry::ExitClass::Error);
145 assert_eq!(
146 codewhale_telemetry::exit_class(),
147 codewhale_telemetry::ExitClass::Clean
148 );
149 }
150
150 lines RUST