Skip to content

Commit cc173e6

Browse files
committed
tests: split doctor suite into scenario modules
1 parent dcf23d2 commit cc173e6

6 files changed

Lines changed: 339 additions & 371 deletions

File tree

Lines changed: 5 additions & 371 deletions
Original file line numberDiff line numberDiff line change
@@ -1,371 +1,5 @@
1-
use super::*;
2-
use std::time::Duration;
3-
4-
use axum::routing::get;
5-
use axum::{Json, Router};
6-
use rexos::config::{ProviderKind, RexosConfig};
7-
use rexos::paths::RexosPaths;
8-
use serde_json::json;
9-
10-
#[tokio::test]
11-
async fn doctor_suggests_running_init_when_core_files_are_missing() {
12-
let tmp = tempfile::tempdir().unwrap();
13-
let paths = RexosPaths {
14-
base_dir: tmp.path().join(".loopforge"),
15-
};
16-
std::fs::create_dir_all(&paths.base_dir).unwrap();
17-
18-
let report = run_doctor(DoctorOptions {
19-
paths,
20-
timeout: Duration::from_millis(200),
21-
})
22-
.await
23-
.unwrap();
24-
25-
let value = serde_json::to_value(&report).unwrap();
26-
let next_actions = value
27-
.get("next_actions")
28-
.and_then(|item| item.as_array())
29-
.cloned()
30-
.unwrap_or_default();
31-
assert!(
32-
next_actions
33-
.iter()
34-
.any(|item| item.as_str().unwrap_or("").contains("loopforge init")),
35-
"expected init guidance in next_actions, got: {next_actions:?}"
36-
);
37-
assert!(
38-
report.to_text().contains("Suggested next steps"),
39-
"expected text output to include suggested next steps, got: {}",
40-
report.to_text()
41-
);
42-
}
43-
44-
#[tokio::test]
45-
async fn doctor_suggests_missing_provider_env_vars() {
46-
let tmp = tempfile::tempdir().unwrap();
47-
let paths = RexosPaths {
48-
base_dir: tmp.path().join(".loopforge"),
49-
};
50-
std::fs::create_dir_all(&paths.base_dir).unwrap();
51-
52-
let mut cfg = RexosConfig::default();
53-
cfg.providers.insert(
54-
"anthropic".to_string(),
55-
rexos::config::ProviderConfig {
56-
kind: ProviderKind::Anthropic,
57-
base_url: "https://api.anthropic.com".to_string(),
58-
api_key_env: "ANTHROPIC_API_KEY".to_string(),
59-
default_model: "claude-3-5-sonnet-latest".to_string(),
60-
aws_bedrock: None,
61-
},
62-
);
63-
std::fs::write(paths.config_path(), toml::to_string(&cfg).unwrap()).unwrap();
64-
std::env::remove_var("ANTHROPIC_API_KEY");
65-
66-
let report = run_doctor(DoctorOptions {
67-
paths,
68-
timeout: Duration::from_millis(200),
69-
})
70-
.await
71-
.unwrap();
72-
73-
let value = serde_json::to_value(&report).unwrap();
74-
let next_actions = value
75-
.get("next_actions")
76-
.and_then(|item| item.as_array())
77-
.cloned()
78-
.unwrap_or_default();
79-
assert!(
80-
next_actions
81-
.iter()
82-
.any(|item| item.as_str().unwrap_or("").contains("ANTHROPIC_API_KEY")),
83-
"expected provider env guidance in next_actions, got: {next_actions:?}"
84-
);
85-
}
86-
87-
#[tokio::test]
88-
async fn doctor_probes_local_ollama_models_and_cdp_version() {
89-
async fn models() -> Json<serde_json::Value> {
90-
Json(json!({ "data": [] }))
91-
}
92-
async fn cdp_version() -> Json<serde_json::Value> {
93-
Json(json!({ "Browser": "Chrome/1.0" }))
94-
}
95-
96-
let app = Router::new()
97-
.route("/v1/models", get(models))
98-
.route("/json/version", get(cdp_version));
99-
100-
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
101-
let addr = listener.local_addr().unwrap();
102-
let server = tokio::spawn(async move {
103-
axum::serve(listener, app).await.unwrap();
104-
});
105-
106-
let tmp = tempfile::tempdir().unwrap();
107-
let paths = RexosPaths {
108-
base_dir: tmp.path().join(".loopforge"),
109-
};
110-
std::fs::create_dir_all(&paths.base_dir).unwrap();
111-
112-
let cfg = RexosConfig {
113-
llm: rexos::config::LlmConfig::default(),
114-
providers: [(
115-
"ollama".to_string(),
116-
rexos::config::ProviderConfig {
117-
kind: ProviderKind::OpenAiCompatible,
118-
base_url: format!("http://{addr}/v1"),
119-
api_key_env: "".to_string(),
120-
default_model: "x".to_string(),
121-
aws_bedrock: None,
122-
},
123-
)]
124-
.into_iter()
125-
.collect(),
126-
router: rexos::config::RouterConfig::default(),
127-
security: Default::default(),
128-
};
129-
std::fs::write(paths.config_path(), toml::to_string(&cfg).unwrap()).unwrap();
130-
std::env::set_var("LOOPFORGE_BROWSER_CDP_HTTP", format!("http://{addr}"));
131-
132-
let report = run_doctor(DoctorOptions {
133-
paths,
134-
timeout: Duration::from_millis(500),
135-
})
136-
.await
137-
.unwrap();
138-
139-
let statuses: std::collections::BTreeMap<String, CheckStatus> = report
140-
.checks
141-
.iter()
142-
.map(|check| (check.id.clone(), check.status))
143-
.collect();
144-
assert_eq!(statuses.get("ollama.http"), Some(&CheckStatus::Ok));
145-
assert_eq!(statuses.get("browser.cdp_http"), Some(&CheckStatus::Ok));
146-
147-
std::env::remove_var("LOOPFORGE_BROWSER_CDP_HTTP");
148-
server.abort();
149-
}
150-
151-
#[tokio::test]
152-
async fn doctor_reports_security_posture_checks() {
153-
let tmp = tempfile::tempdir().unwrap();
154-
let paths = RexosPaths {
155-
base_dir: tmp.path().join(".loopforge"),
156-
};
157-
std::fs::create_dir_all(&paths.base_dir).unwrap();
158-
159-
let mut cfg = RexosConfig {
160-
llm: rexos::config::LlmConfig::default(),
161-
providers: [(
162-
"ollama".to_string(),
163-
rexos::config::ProviderConfig {
164-
kind: ProviderKind::OpenAiCompatible,
165-
base_url: "http://127.0.0.1:11434/v1".to_string(),
166-
api_key_env: "".to_string(),
167-
default_model: "x".to_string(),
168-
aws_bedrock: None,
169-
},
170-
)]
171-
.into_iter()
172-
.collect(),
173-
router: rexos::config::RouterConfig::default(),
174-
security: Default::default(),
175-
};
176-
cfg.security.leaks.mode = rexos::security::LeakMode::Redact;
177-
cfg.security.egress.rules.push(rexos::security::EgressRule {
178-
tool: "web_fetch".to_string(),
179-
host: "docs.rs".to_string(),
180-
path_prefix: "/".to_string(),
181-
methods: vec!["GET".to_string()],
182-
});
183-
std::fs::write(paths.config_path(), toml::to_string(&cfg).unwrap()).unwrap();
184-
185-
let report = run_doctor(DoctorOptions {
186-
paths,
187-
timeout: Duration::from_millis(200),
188-
})
189-
.await
190-
.unwrap();
191-
192-
let statuses: std::collections::BTreeMap<String, CheckStatus> = report
193-
.checks
194-
.iter()
195-
.map(|check| (check.id.clone(), check.status))
196-
.collect();
197-
assert_eq!(
198-
statuses.get("security.secrets.mode"),
199-
Some(&CheckStatus::Ok)
200-
);
201-
assert_eq!(statuses.get("security.leaks.mode"), Some(&CheckStatus::Ok));
202-
assert_eq!(
203-
statuses.get("security.egress.rules"),
204-
Some(&CheckStatus::Ok)
205-
);
206-
}
207-
208-
#[tokio::test]
209-
async fn doctor_suggests_leak_guard_and_egress_hardening_when_defaults_are_open() {
210-
let tmp = tempfile::tempdir().unwrap();
211-
let paths = RexosPaths {
212-
base_dir: tmp.path().join(".loopforge"),
213-
};
214-
std::fs::create_dir_all(&paths.base_dir).unwrap();
215-
216-
let cfg = RexosConfig {
217-
llm: rexos::config::LlmConfig::default(),
218-
providers: [(
219-
"ollama".to_string(),
220-
rexos::config::ProviderConfig {
221-
kind: ProviderKind::OpenAiCompatible,
222-
base_url: "http://127.0.0.1:11434/v1".to_string(),
223-
api_key_env: "".to_string(),
224-
default_model: "x".to_string(),
225-
aws_bedrock: None,
226-
},
227-
)]
228-
.into_iter()
229-
.collect(),
230-
router: rexos::config::RouterConfig::default(),
231-
security: Default::default(),
232-
};
233-
std::fs::write(paths.config_path(), toml::to_string(&cfg).unwrap()).unwrap();
234-
235-
let report = run_doctor(DoctorOptions {
236-
paths,
237-
timeout: Duration::from_millis(200),
238-
})
239-
.await
240-
.unwrap();
241-
242-
let statuses: std::collections::BTreeMap<String, CheckStatus> = report
243-
.checks
244-
.iter()
245-
.map(|check| (check.id.clone(), check.status))
246-
.collect();
247-
assert_eq!(
248-
statuses.get("security.leaks.mode"),
249-
Some(&CheckStatus::Warn)
250-
);
251-
assert_eq!(
252-
statuses.get("security.egress.rules"),
253-
Some(&CheckStatus::Warn)
254-
);
255-
assert!(
256-
report
257-
.next_actions
258-
.iter()
259-
.any(|item| item.contains("security.leaks")),
260-
"expected leak-guard guidance, got: {:?}",
261-
report.next_actions
262-
);
263-
assert!(
264-
report
265-
.next_actions
266-
.iter()
267-
.any(|item| item.contains("security.egress")),
268-
"expected egress guidance, got: {:?}",
269-
report.next_actions
270-
);
271-
}
272-
273-
#[tokio::test]
274-
async fn doctor_reports_bedrock_feature_status_when_routed() {
275-
let tmp = tempfile::tempdir().unwrap();
276-
let paths = RexosPaths {
277-
base_dir: tmp.path().join(".loopforge"),
278-
};
279-
std::fs::create_dir_all(&paths.base_dir).unwrap();
280-
281-
let mut cfg = RexosConfig::default();
282-
cfg.router.coding.provider = "bedrock".to_string();
283-
cfg.router.coding.model = "default".to_string();
284-
if let Some(provider) = cfg.providers.get_mut("bedrock") {
285-
provider.default_model = "anthropic.claude-3-5-sonnet-20241022-v2:0".to_string();
286-
}
287-
std::fs::write(paths.config_path(), toml::to_string(&cfg).unwrap()).unwrap();
288-
289-
let report = run_doctor(DoctorOptions {
290-
paths,
291-
timeout: Duration::from_millis(200),
292-
})
293-
.await
294-
.unwrap();
295-
296-
let statuses: std::collections::BTreeMap<String, CheckStatus> = report
297-
.checks
298-
.iter()
299-
.map(|check| (check.id.clone(), check.status))
300-
.collect();
301-
302-
assert_eq!(
303-
statuses.get("bedrock.router.coding.model"),
304-
Some(&CheckStatus::Ok)
305-
);
306-
assert_eq!(
307-
statuses.get("bedrock.providers.bedrock.region"),
308-
Some(&CheckStatus::Ok)
309-
);
310-
assert_eq!(
311-
statuses.get("bedrock.feature"),
312-
Some(if cfg!(feature = "bedrock") {
313-
&CheckStatus::Ok
314-
} else {
315-
&CheckStatus::Error
316-
})
317-
);
318-
319-
if !cfg!(feature = "bedrock") {
320-
assert!(
321-
report
322-
.next_actions
323-
.iter()
324-
.any(|item| item.contains("features bedrock")),
325-
"expected bedrock rebuild guidance, got: {:?}",
326-
report.next_actions
327-
);
328-
}
329-
}
330-
331-
#[tokio::test]
332-
async fn doctor_flags_missing_bedrock_model_and_region() {
333-
let tmp = tempfile::tempdir().unwrap();
334-
let paths = RexosPaths {
335-
base_dir: tmp.path().join(".loopforge"),
336-
};
337-
std::fs::create_dir_all(&paths.base_dir).unwrap();
338-
339-
let mut cfg = RexosConfig::default();
340-
cfg.router.coding.provider = "bedrock".to_string();
341-
cfg.router.coding.model = "default".to_string();
342-
if let Some(provider) = cfg.providers.get_mut("bedrock") {
343-
provider.default_model = "".to_string();
344-
if let Some(aws) = provider.aws_bedrock.as_mut() {
345-
aws.region = "".to_string();
346-
}
347-
}
348-
std::fs::write(paths.config_path(), toml::to_string(&cfg).unwrap()).unwrap();
349-
350-
let report = run_doctor(DoctorOptions {
351-
paths,
352-
timeout: Duration::from_millis(200),
353-
})
354-
.await
355-
.unwrap();
356-
357-
let statuses: std::collections::BTreeMap<String, CheckStatus> = report
358-
.checks
359-
.iter()
360-
.map(|check| (check.id.clone(), check.status))
361-
.collect();
362-
363-
assert_eq!(
364-
statuses.get("bedrock.router.coding.model"),
365-
Some(&CheckStatus::Error)
366-
);
367-
assert_eq!(
368-
statuses.get("bedrock.providers.bedrock.region"),
369-
Some(&CheckStatus::Error)
370-
);
371-
}
1+
mod bedrock;
2+
mod common;
3+
mod guidance;
4+
mod probes;
5+
mod security;

0 commit comments

Comments
 (0)