Skip to main content

zoom_cli/commands/
config.rs

1use crate::config;
2use crate::output::{self, OutputConfig};
3
4pub fn show(profile_arg: Option<&str>, out: &OutputConfig) {
5    let summary = config::load_for_show(profile_arg);
6
7    if out.json {
8        let profiles_json: serde_json::Map<String, serde_json::Value> = summary
9            .profiles
10            .iter()
11            .map(|p| {
12                (
13                    p.name.clone(),
14                    serde_json::json!({
15                        "account_id": masked_or_unset(&p.account_id),
16                        "client_id": masked_or_unset(&p.client_id),
17                        "client_secret": masked_or_unset(&p.client_secret),
18                    }),
19                )
20            })
21            .collect();
22
23        let env_overrides_json: serde_json::Map<String, serde_json::Value> = summary
24            .env_overrides
25            .iter()
26            .map(|(k, v)| {
27                (
28                    k.to_string(),
29                    serde_json::Value::String(output::mask_credential(v)),
30                )
31            })
32            .collect();
33
34        out.print_data(
35            &serde_json::to_string_pretty(&serde_json::json!({
36                "config_file": summary.config_file.to_string_lossy(),
37                "file_exists": summary.file_exists,
38                "active_profile": summary.active_profile,
39                "profiles": profiles_json,
40                "env_overrides": env_overrides_json,
41            }))
42            .expect("serialize"),
43        );
44    } else {
45        out.print_data(&format!(
46            "Config file: {}{}",
47            summary.config_file.display(),
48            if summary.file_exists {
49                ""
50            } else {
51                "  (not found)"
52            },
53        ));
54
55        if summary.profiles.is_empty() && summary.env_overrides.is_empty() {
56            out.print_data("\nNo configuration found. Run `zoom init` to set up credentials.");
57            return;
58        }
59
60        for profile in &summary.profiles {
61            let active_marker = if profile.name == summary.active_profile {
62                "  (active)"
63            } else {
64                ""
65            };
66            out.print_data(&format!("\nProfile: {}{}", profile.name, active_marker));
67            out.print_data(&format!(
68                "  account_id:    {}",
69                masked_or_unset(&profile.account_id)
70            ));
71            out.print_data(&format!(
72                "  client_id:     {}",
73                masked_or_unset(&profile.client_id)
74            ));
75            out.print_data(&format!(
76                "  client_secret: {}",
77                masked_or_unset(&profile.client_secret)
78            ));
79        }
80
81        if !summary.env_overrides.is_empty() {
82            out.print_data("\nEnvironment overrides (take precedence over file):");
83            for (var, val) in &summary.env_overrides {
84                out.print_data(&format!("  {}={}", var, output::mask_credential(val)));
85            }
86        }
87    }
88}
89
90fn masked_or_unset(value: &Option<String>) -> String {
91    match value {
92        Some(v) => output::mask_credential(v),
93        None => "(not set)".to_owned(),
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::test_support::{EnvVarGuard, ProcessEnvLock, set_config_dir_env, write_config};
101    use tempfile::TempDir;
102
103    #[test]
104    fn show_json_includes_active_profile_and_masked_credentials() {
105        let _lock = ProcessEnvLock::acquire().unwrap();
106        let dir = TempDir::new().unwrap();
107        write_config(
108            dir.path(),
109            r#"
110[default]
111account_id = "acct-long-enough-123"
112client_id = "cid-long-enough-456"
113client_secret = "csec-long-enough-789"
114"#,
115        )
116        .unwrap();
117
118        let _cfg_dir = set_config_dir_env(dir.path());
119        let _acct = EnvVarGuard::unset("ZOOM_ACCOUNT_ID");
120        let _cid = EnvVarGuard::unset("ZOOM_CLIENT_ID");
121        let _csec = EnvVarGuard::unset("ZOOM_CLIENT_SECRET");
122        let _prof = EnvVarGuard::unset("ZOOM_PROFILE");
123
124        // Capture stdout by redirecting through a buffer isn't straightforward;
125        // instead call load_for_show directly and validate the summary.
126        let summary = config::load_for_show(None);
127        assert_eq!(summary.active_profile, "default");
128        assert!(summary.file_exists);
129        assert_eq!(summary.profiles.len(), 1);
130        assert_eq!(summary.profiles[0].name, "default");
131        assert_eq!(
132            summary.profiles[0].account_id.as_deref(),
133            Some("acct-long-enough-123")
134        );
135        assert!(summary.env_overrides.is_empty());
136    }
137
138    #[test]
139    fn show_lists_all_profiles_with_default_first() {
140        let _lock = ProcessEnvLock::acquire().unwrap();
141        let dir = TempDir::new().unwrap();
142        write_config(
143            dir.path(),
144            r#"
145[default]
146account_id = "def-acct"
147client_id = "def-cid"
148client_secret = "def-csec"
149
150[work]
151account_id = "work-acct"
152client_id = "work-cid"
153client_secret = "work-csec"
154"#,
155        )
156        .unwrap();
157
158        let _cfg_dir = set_config_dir_env(dir.path());
159        let _env_acct = EnvVarGuard::unset("ZOOM_ACCOUNT_ID");
160        let _env_cid = EnvVarGuard::unset("ZOOM_CLIENT_ID");
161        let _env_csec = EnvVarGuard::unset("ZOOM_CLIENT_SECRET");
162        let _env_prof = EnvVarGuard::unset("ZOOM_PROFILE");
163
164        let summary = config::load_for_show(None);
165        assert_eq!(summary.profiles.len(), 2);
166        assert_eq!(summary.profiles[0].name, "default");
167        assert_eq!(summary.profiles[1].name, "work");
168    }
169
170    #[test]
171    fn show_marks_profile_arg_as_active() {
172        let _lock = ProcessEnvLock::acquire().unwrap();
173        let dir = TempDir::new().unwrap();
174        write_config(
175            dir.path(),
176            r#"
177[default]
178account_id = "def-acct"
179client_id = "def-cid"
180client_secret = "def-csec"
181
182[work]
183account_id = "work-acct"
184client_id = "work-cid"
185client_secret = "work-csec"
186"#,
187        )
188        .unwrap();
189
190        let _cfg_dir = set_config_dir_env(dir.path());
191        let _env_acct = EnvVarGuard::unset("ZOOM_ACCOUNT_ID");
192        let _env_cid = EnvVarGuard::unset("ZOOM_CLIENT_ID");
193        let _env_csec = EnvVarGuard::unset("ZOOM_CLIENT_SECRET");
194        let _env_prof = EnvVarGuard::unset("ZOOM_PROFILE");
195
196        let summary = config::load_for_show(Some("work"));
197        assert_eq!(summary.active_profile, "work");
198    }
199
200    #[test]
201    fn show_reports_env_overrides() {
202        let _lock = ProcessEnvLock::acquire().unwrap();
203        let dir = TempDir::new().unwrap();
204        let _cfg_dir = set_config_dir_env(dir.path());
205        let _acct = EnvVarGuard::set("ZOOM_ACCOUNT_ID", "env-account-id-long");
206        let _cid = EnvVarGuard::unset("ZOOM_CLIENT_ID");
207        let _csec = EnvVarGuard::unset("ZOOM_CLIENT_SECRET");
208        let _prof = EnvVarGuard::unset("ZOOM_PROFILE");
209
210        let summary = config::load_for_show(None);
211        assert_eq!(summary.env_overrides.len(), 1);
212        assert_eq!(summary.env_overrides[0].0, "ZOOM_ACCOUNT_ID");
213        assert_eq!(summary.env_overrides[0].1, "env-account-id-long");
214    }
215
216    #[test]
217    fn show_handles_missing_config_file() {
218        let _lock = ProcessEnvLock::acquire().unwrap();
219        let dir = TempDir::new().unwrap();
220        let _cfg_dir = set_config_dir_env(dir.path());
221        let _acct = EnvVarGuard::unset("ZOOM_ACCOUNT_ID");
222        let _cid = EnvVarGuard::unset("ZOOM_CLIENT_ID");
223        let _csec = EnvVarGuard::unset("ZOOM_CLIENT_SECRET");
224        let _prof = EnvVarGuard::unset("ZOOM_PROFILE");
225
226        let summary = config::load_for_show(None);
227        assert!(!summary.file_exists);
228        assert!(summary.profiles.is_empty());
229    }
230
231    #[test]
232    fn masked_or_unset_masks_present_values() {
233        assert_eq!(
234            masked_or_unset(&Some("abcdefghijklmnop".to_owned())),
235            "abcdef…mnop"
236        );
237        assert_eq!(masked_or_unset(&None), "(not set)");
238    }
239}