Skip to main content

helper_handoff_feedback/support/
utils.rs

1#![allow(dead_code)]
2
3use mubit_sdk::{Client, ClientConfig, TransportMode};
4use serde_json::{json, Value};
5use std::env;
6use std::error::Error;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9#[derive(Debug, Clone, Copy)]
10pub struct ScenarioConfig {
11    pub expect_direct_search_enabled: bool,
12}
13
14impl ScenarioConfig {
15    pub fn from_env() -> Self {
16        Self {
17            expect_direct_search_enabled: env_bool("MUBIT_EXPECT_DIRECT_SEARCH_ENABLED", false),
18        }
19    }
20}
21
22pub fn boxed_error(message: impl Into<String>) -> Box<dyn Error> {
23    Box::new(std::io::Error::other(message.into()))
24}
25
26pub fn require(condition: bool, message: impl Into<String>) -> Result<(), Box<dyn Error>> {
27    if condition {
28        Ok(())
29    } else {
30        Err(boxed_error(message))
31    }
32}
33
34pub fn env_bool(name: &str, fallback: bool) -> bool {
35    let fallback_raw = if fallback { "1" } else { "0" };
36    let raw = env::var(name)
37        .unwrap_or_else(|_| fallback_raw.to_string())
38        .trim()
39        .to_ascii_lowercase();
40    matches!(raw.as_str(), "1" | "true" | "yes" | "on")
41}
42
43pub fn is_permission_denied_like(message: &str) -> bool {
44    let lowered = message.to_ascii_lowercase();
45    lowered.contains("permission denied")
46        || lowered.contains("permissiondenied")
47        || lowered.contains("403")
48        || lowered.contains("disabled")
49}
50
51pub fn new_run_id(prefix: &str) -> String {
52    let ms = SystemTime::now()
53        .duration_since(UNIX_EPOCH)
54        .unwrap_or_default()
55        .as_millis();
56    let pid = std::process::id();
57    format!("{prefix}_{ms}_{pid}")
58}
59
60pub async fn create_client() -> Result<Client, Box<dyn Error>> {
61    let endpoint =
62        env::var("MUBIT_ENDPOINT").unwrap_or_else(|_| "http://127.0.0.1:3000".to_string());
63    let grpc_endpoint =
64        env::var("MUBIT_GRPC_ENDPOINT").unwrap_or_else(|_| "127.0.0.1:50051".to_string());
65    let timeout_ms = env::var("MUBIT_TIMEOUT_MS")
66        .ok()
67        .and_then(|raw| raw.parse::<u64>().ok())
68        .unwrap_or(30_000);
69
70    let api_key =
71        env::var("MUBIT_API_KEY").map_err(|_| boxed_error("MUBIT_API_KEY must be set"))?;
72
73    let mut cfg = ClientConfig::new(endpoint).transport("http");
74    cfg.grpc_endpoint = Some(grpc_endpoint);
75    cfg.timeout_ms = timeout_ms;
76    cfg.api_key = Some(api_key.clone());
77
78    let client = Client::new(cfg)?;
79    client.set_transport(TransportMode::Http);
80    client.set_api_key(Some(api_key));
81
82    let _ = client.auth.health().await?;
83
84    Ok(client)
85}
86
87pub async fn cleanup_run(client: &Client, run_id: &str) -> bool {
88    let control_ok = client
89        .control
90        .delete_run(json!({ "run_id": run_id }))
91        .await
92        .is_ok();
93    let core_ok = match client.core.delete_run(json!({ "run_id": run_id })).await {
94        Ok(_) => true,
95        Err(err) => is_permission_denied_like(&err.to_string()),
96    };
97    control_ok && core_ok
98}
99
100pub fn print_summary(
101    name: &str,
102    passed: bool,
103    detail: &str,
104    metrics: &Value,
105    duration_sec: f64,
106    cleanup_ok: bool,
107) {
108    println!(
109        "[{}] {} {:.2}s  {}",
110        if passed { "PASS" } else { "FAIL" },
111        name,
112        duration_sec,
113        detail
114    );
115    if let Some(map) = metrics.as_object() {
116        if !map.is_empty() {
117            println!("metrics: {}", metrics);
118        }
119    }
120    println!("cleanup: {}", if cleanup_ok { "PASS" } else { "FAIL" });
121}