aether_project/
testing.rs1use std::path::{Path, PathBuf};
7
8use serde_json::{Value, json};
9
10use crate::prompt_file::{PromptFile, PromptTriggers};
11use crate::{AetherSettings, AgentConfig, McpSourceSpec, PromptSource};
12use aether_core::agent_spec::{AgentSpec, AgentSpecExposure};
13use aether_core::core::Prompt;
14pub use utils::testing::TestWorkspace;
15
16pub const DEFAULT_MODEL: &str = "anthropic:claude-sonnet-4-5";
18
19pub fn mcp_json(server: &str) -> String {
20 json!({ "servers": { server: { "command": format!("{server}-server") } } }).to_string()
21}
22
23pub fn project() -> TestWorkspace {
24 TestWorkspace::new()
25}
26
27pub fn home() -> TestWorkspace {
28 TestWorkspace::new()
29}
30
31pub fn settings_agent(name: &str, description: &str) -> AgentConfig {
33 AgentConfig {
34 name: name.to_string(),
35 description: description.to_string(),
36 model: DEFAULT_MODEL.to_string(),
37 user_invocable: true,
38 ..AgentConfig::default()
39 }
40}
41
42pub fn agent_json(name: &str, description: &str) -> Value {
45 json!({
46 "name": name,
47 "description": description,
48 "model": DEFAULT_MODEL,
49 "userInvocable": true,
50 })
51}
52
53pub fn agent_json_with(name: &str, description: &str, extra: Value) -> Value {
56 let Value::Object(extra) = extra else {
57 panic!("extra agent fields must be a JSON object, got {extra}");
58 };
59 let mut agent = agent_json(name, description);
60 agent.as_object_mut().expect("agent_json builds an object").extend(extra);
61 agent
62}
63
64pub fn agent_config(name: &str) -> AgentConfig {
66 AgentConfig { prompts: vec![PromptSource::file("PROMPT.md")], ..settings_agent(name, &format!("{name} agent")) }
67}
68
69pub fn settings_with_mcps(mcps: Vec<McpSourceSpec>) -> AetherSettings {
70 AetherSettings { agents: vec![AgentConfig { mcps, ..agent_config("alpha") }], ..AetherSettings::default() }
71}
72
73pub fn has_prompt_file(spec: &AgentSpec, expected: impl AsRef<Path>) -> bool {
74 spec.prompts.iter().any(|prompt| matches!(prompt, Prompt::File { path, .. } if path.ends_with(&expected)))
75}
76
77pub fn prompt_file(name: &str) -> PromptFileBuilder {
80 PromptFileBuilder {
81 file: PromptFile {
82 name: name.to_string(),
83 description: format!("{name} skill"),
84 body: format!("# {name}\n\nSkill body."),
85 path: PathBuf::new(),
86 user_invocable: true,
87 agent_invocable: true,
88 argument_hint: None,
89 tags: Vec::new(),
90 triggers: PromptTriggers::default(),
91 agent_authored: false,
92 helpful: 0,
93 harmful: 0,
94 },
95 }
96}
97
98pub fn fake_spec(name: &str, exposure: AgentSpecExposure) -> AgentSpec {
100 AgentSpec {
101 name: name.to_string(),
102 description: format!("{name} agent"),
103 model: DEFAULT_MODEL.to_string(),
104 exposure,
105 ..AgentSpec::default()
106 }
107}
108
109pub struct PromptFileBuilder {
111 file: PromptFile,
112}
113
114impl PromptFileBuilder {
115 pub fn description(mut self, description: &str) -> Self {
117 self.file.description = description.to_string();
118 self
119 }
120
121 pub fn body(mut self, body: &str) -> Self {
123 self.file.body = body.to_string();
124 self
125 }
126
127 pub fn user_invocable(mut self, user_invocable: bool) -> Self {
129 self.file.user_invocable = user_invocable;
130 self
131 }
132
133 pub fn agent_invocable(mut self, agent_invocable: bool) -> Self {
135 self.file.agent_invocable = agent_invocable;
136 self
137 }
138
139 pub fn argument_hint(mut self, argument_hint: &str) -> Self {
141 self.file.argument_hint = Some(argument_hint.to_string());
142 self
143 }
144
145 pub fn tags(mut self, tags: &[&str]) -> Self {
147 self.file.tags = tags.iter().map(ToString::to_string).collect();
148 self
149 }
150
151 pub fn read_triggers(mut self, patterns: &[&str]) -> Self {
153 self.file.triggers = PromptTriggers::new(patterns.iter().map(ToString::to_string).collect())
154 .expect("read_triggers patterns must be valid globs");
155 self
156 }
157
158 pub fn agent_authored(mut self, agent_authored: bool) -> Self {
160 self.file.agent_authored = agent_authored;
161 self
162 }
163
164 pub fn ratings(mut self, helpful: u32, harmful: u32) -> Self {
166 self.file.helpful = helpful;
167 self.file.harmful = harmful;
168 self
169 }
170
171 pub fn build(self) -> PromptFile {
173 self.file
174 }
175}