返回 CodeWhale
command_catalog.rs
根目录 / crates / tui / src / runtime_api / tests / command_catalog.rs
1 use super::*;
2
3 fn entry<'a>(commands: &'a [CommandCatalogEntry], name: &str) -> &'a CommandCatalogEntry {
4 commands
5 .iter()
6 .find(|command| command.name == name)
7 .unwrap_or_else(|| panic!("catalog must contain {name}"))
8 }
9
10 #[test]
11 fn command_catalog_serves_builtins_with_host_binding() {
12 let users = crate::commands::user_registry::UserCommandRegistry::new();
13 let commands = command_catalog(&users);
14
15 let model = entry(&commands, "model");
16 assert_eq!(model.kind, "builtin");
17 assert_eq!(model.binding, "host");
18 assert_eq!(model.discovery, Some("primary"));
19 assert!(!model.hidden);
20 assert_eq!(model.shadowed_by, None);
21 assert!(model.summary.is_some());
22 assert!(model.usage.is_some());
23 assert!(model.takes_arguments);
24
25 // Unlisted builtins run but are not advertised — hidden, not absent.
26 assert!(entry(&commands, "lane").hidden);
27
28 // A usage line's literal verbs surface as subcommands.
29 let goal = entry(&commands, "goal");
30 assert!(
31 goal.subcommands.iter().any(|verb| verb == "blocked"),
32 "goal usage should declare its verbs: {:?}",
33 goal.subcommands
34 );
35 }
36
37 #[test]
38 fn command_catalog_marks_user_shadowing_of_builtin_names_and_aliases() {
39 let users = crate::commands::user_registry::UserCommandRegistry::from_loaded(vec![
40 ("model".to_string(), "Pick the fast route.".to_string()),
41 ("agents".to_string(), "Alias-shaped command.".to_string()),
42 ]);
43 let commands = command_catalog(&users);
44
45 let model = entry(&commands, "model");
46 assert_eq!(model.shadowed_by.as_deref(), Some("model"));
47
48 // The shadowing user command is served as a prompt-bound row.
49 let user_model = commands
50 .iter()
51 .find(|command| command.name == "model" && command.kind == "user")
52 .expect("user command row");
53 assert_eq!(user_model.binding, "prompt");
54
55 // A user command colliding with a builtin's alias shadows that spelling:
56 // `agents` is a `subagents` alias, so the builtin reports it while keeping
57 // its canonical name.
58 let subagents = entry(&commands, "subagents");
59 assert_eq!(subagents.kind, "builtin");
60 assert_eq!(subagents.shadowed_by, None);
61 assert!(
62 subagents.shadowed_aliases.iter().any(|a| a == "agents"),
63 "shadowed_aliases must report the taken spelling: {:?}",
64 subagents.shadowed_aliases
65 );
66 }
67
68 #[tokio::test]
69 async fn get_v1_commands_serves_the_catalog_over_http() -> Result<()> {
70 let _env = lock_test_env();
71 let temp = tempfile::tempdir()?;
72 let root = temp.path().join("commands-route");
73 let sessions_dir = root.join("sessions");
74 let workspace = root.join("workspace");
75 let commands_dir = workspace.join(".codewhale").join("commands");
76 fs::create_dir_all(&commands_dir)?;
77 fs::write(commands_dir.join("model.md"), "Pick the fast route.\n")?;
78
79 let Some((addr, _runtime_threads, handle)) =
80 spawn_test_server_with_root_token_mobile_workspace(
81 root,
82 sessions_dir,
83 None,
84 false,
85 workspace,
86 )
87 .await?
88 else {
89 return Ok(());
90 };
91 let client = crate::tls::reqwest_client();
92
93 let body: serde_json::Value = client
94 .get(format!("http://{addr}/v1/commands"))
95 .send()
96 .await?
97 .error_for_status()?
98 .json()
99 .await?;
100 let commands = body["commands"].as_array().expect("commands array");
101 assert!(
102 commands
103 .iter()
104 .any(|command| command["kind"] == "builtin" && command["binding"] == "host"),
105 "the route must serve builtins: {commands:?}"
106 );
107
108 // The workspace user command named `model` shadows the builtin: the
109 // builtin reports the shadow and the winning definition is served.
110 let builtin_model = commands
111 .iter()
112 .find(|command| command["name"] == "model" && command["kind"] == "builtin")
113 .expect("builtin model row");
114 assert_eq!(builtin_model["shadowed_by"], "model");
115 let user_model = commands
116 .iter()
117 .find(|command| command["name"] == "model" && command["kind"] == "user")
118 .expect("user model row");
119 assert_eq!(user_model["binding"], "prompt");
120
121 handle.abort();
122 Ok(())
123 }
124
124 lines RUST