返回 CodeWhale
notification_config.rs
根目录 / crates / cli / tests / notification_config.rs
1 //! Real dispatcher regression: nested config edits retain TOML types and
2 //! unrelated bytes. Only config commands run; no provider, player or OS banner.
3 use std::{
4 fs,
5 path::Path,
6 process::{Command, Output},
7 };
8
9 fn run(root: &Path, args: &[&str]) -> Output {
10 Command::new(env!("CARGO_BIN_EXE_codewhale"))
11 .env_clear()
12 .env("HOME", root)
13 .env("USERPROFILE", root)
14 .env("CODEWHALE_HOME", root.join("state"))
15 .env("CODEWHALE_SECRET_BACKEND", "file")
16 .current_dir(root)
17 .arg("--config")
18 .arg(root.join("config.toml"))
19 .arg("config")
20 .args(args)
21 .output()
22 .unwrap()
23 }
24 fn ok(root: &Path, args: &[&str]) -> String {
25 let out = run(root, args);
26 assert!(
27 out.status.success(),
28 "{args:?}: {}",
29 String::from_utf8_lossy(&out.stderr)
30 );
31 String::from_utf8(out.stdout).unwrap()
32 }
33
34 #[test]
35 fn nested_notification_cli_set_get_unset_is_typed_lossless_and_validated() {
36 let temp = tempfile::tempdir().unwrap();
37 let root = temp.path();
38 let path = root.join("config.toml");
39 fs::write(&path, "# keep operator note\n\"notifications.quiet\" = \"false\"\n[notifications]\nfuture = \"keep\"\n[notifications.events]\ninput-needed = false\n").unwrap();
40 for (key, value) in [
41 ("sound", "whale"),
42 ("quiet", "true"),
43 ("threshold_secs", "17"),
44 ("events.approval-needed", "false"),
45 ("event_sound.events", r#"["model-notify", "input-needed"]"#),
46 ("sound_file", "custom call.wav"),
47 ] {
48 ok(root, &["set", &format!("notifications.{key}"), value]);
49 }
50 assert_eq!(ok(root, &["get", "notifications.sound"]).trim(), "whale");
51 assert_eq!(ok(root, &["get", "notifications.quiet"]).trim(), "true");
52 let saved = fs::read_to_string(&path).unwrap();
53 assert!(saved.contains("# keep operator note"));
54 let raw: toml::Value = toml::from_str(&saved).unwrap();
55 assert_eq!(raw["notifications"]["quiet"].as_bool(), Some(true));
56 assert_eq!(
57 raw["notifications"]["threshold_secs"].as_integer(),
58 Some(17)
59 );
60 assert_eq!(
61 raw["notifications"]["event_sound"]["events"]
62 .as_array()
63 .unwrap()
64 .len(),
65 2
66 );
67 assert_eq!(
68 raw["notifications"]["events"]["input-needed"].as_bool(),
69 Some(false)
70 );
71 assert_eq!(raw["notifications"]["future"].as_str(), Some("keep"));
72 assert!(!raw.as_table().unwrap().contains_key("notifications.quiet"));
73 for (key, value) in [
74 ("notifications", "false"),
75 ("notifications.quiet", "maybe"),
76 ("notifications.threshold_secs", "18446744073709551615"),
77 ("notifications.event_sound.events", r#"["unknown"]"#),
78 ] {
79 assert!(!run(root, &["set", key, value]).status.success());
80 assert_eq!(fs::read_to_string(&path).unwrap(), saved);
81 }
82 ok(root, &["unset", "notifications.quiet"]);
83 assert_eq!(ok(root, &["get", "notifications.quiet"]).trim(), "false");
84 assert_eq!(ok(root, &["get", "notifications.sound"]).trim(), "whale");
85 assert!(
86 ok(root, &["get", "notifications"])
87 .contains("notifications.events.approval-needed = false")
88 );
89 }
90
90 lines RUST