use std::fs;
use std::path::PathBuf;
use std::process::Command;
use tempfile::TempDir;
use xybrid_sdk::run_pipeline;
#[test]
fn test_sdk_pipeline_execution() -> Result<(), Box<dyn std::error::Error>> {
println!("🚀 Test 2: SDK pipeline execution");
println!("{}", "=".repeat(60));
let temp_dir = TempDir::new()?;
let config_path = temp_dir.path().join("hiiipe_test.yml");
let config_content = r#"
name: "Test Hiiipe Pipeline"
stages:
- "whisper-tiny@1.2"
- "motivator-llm@5"
- "xtts-mini@0.6"
input:
kind: "Text"
metrics:
network_rtt: 100
battery: 80
temperature: 25.0
availability:
"whisper-tiny@1.2": true
"xtts-mini@0.6": true
"motivator-llm@5": false
"#;
fs::write(&config_path, config_content)?;
println!(" Created test config: {}", config_path.display());
println!(" Executing pipeline...");
let result = run_pipeline(config_path.to_str().unwrap())?;
println!(" Pipeline name: {:?}", result.name);
println!(" Total latency: {}ms", result.total_latency_ms);
println!(" Final output: {}", result.final_output);
println!(" Stage count: {}", result.stages.len());
assert_eq!(
result.stages.len(),
3,
"Pipeline should return exactly 3 stage results"
);
for (i, stage) in result.stages.iter().enumerate() {
println!(
" Stage {}: {} → {} ({}ms)",
i + 1,
stage.name,
stage.target,
stage.latency_ms
);
assert!(!stage.name.is_empty(), "Stage name should not be empty");
assert!(!stage.target.is_empty(), "Stage target should not be empty");
}
assert!(
!result.final_output.is_empty(),
"Final output should not be empty"
);
println!(" ✅ SDK pipeline execution successful");
println!();
Ok(())
}
#[test]
#[ignore = "outdated test needs update"]
fn test_cli_policy_loading() -> Result<(), Box<dyn std::error::Error>> {
println!("📜 Test 3: CLI policy loading");
println!("{}", "=".repeat(60));
let temp_dir = TempDir::new()?;
let policy_path = temp_dir.path().join("test_policy.yaml");
let policy_content = r#"
version: "0.1.0"
rules:
- id: "test_rule"
expression: "input.kind == \"SensitiveData\""
action: "deny"
signature: "test-signature"
"#;
fs::write(&policy_path, policy_content)?;
println!(" Created test policy: {}", policy_path.display());
let config_path = temp_dir.path().join("test_pipeline.yml");
let config_content = r#"
name: "Policy Test Pipeline"
stages:
- "test-stage"
input:
kind: "Text"
metrics:
network_rtt: 100
battery: 50
temperature: 25.0
availability:
"test-stage": true
"#;
fs::write(&config_path, config_content)?;
println!(" Testing CLI with --policy flag...");
let cli_binary = get_cli_binary_path()?;
println!(" CLI binary: {}", cli_binary.display());
let output = Command::new(&cli_binary)
.args([
"run",
"--config",
config_path.to_str().unwrap(),
"--policy",
policy_path.to_str().unwrap(),
])
.current_dir(temp_dir.path())
.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!(" Exit code: {}", output.status.code().unwrap_or(-1));
if !output.status.success() {
eprintln!(" STDOUT:\n{}", stdout);
eprintln!(" STDERR:\n{}", stderr);
}
let policy_loaded = stdout.contains("Loading policy bundle")
|| stdout.contains("Policy bundle loaded")
|| stderr.contains("Loading policy bundle")
|| stderr.contains("Policy bundle loaded");
assert!(
policy_loaded || output.status.success(),
"Policy loading should be mentioned or command should succeed. Exit code: {}, stdout: {}, stderr: {}",
output.status.code().unwrap_or(-1),
stdout,
stderr
);
let policy_bytes = fs::read(&policy_path)?;
assert!(!policy_bytes.is_empty(), "Policy file should not be empty");
println!(" ✅ CLI policy loading verified");
println!();
Ok(())
}
#[test]
#[ignore = "outdated test needs update"]
fn test_trace_latest_command() -> Result<(), Box<dyn std::error::Error>> {
println!("📊 Test 4: Trace command with --latest");
println!("{}", "=".repeat(60));
let cli_binary = get_cli_binary_path()?;
let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?;
let traces_dir = home_dir.join(".xybrid").join("traces");
fs::create_dir_all(&traces_dir)?;
let session_id = "test-trace-session";
let trace_file = traces_dir.join(format!("{}.log", session_id));
let telemetry_entries = [
r#"{"timestamp":1000,"severity":"INFO","event":"stage_start","message":"Stage 'asr' started","attributes":{"stage":"asr"}}"#,
r#"{"timestamp":1001,"severity":"DEBUG","event":"policy_evaluation","message":"Policy evaluation for 'asr': allowed","attributes":{"stage":"asr","allowed":true}}"#,
r#"{"timestamp":1002,"severity":"INFO","event":"routing_decision","message":"Routing decision for 'asr': local","attributes":{"stage":"asr","target":"local","reason":"local_preferred"}}"#,
r#"{"timestamp":1003,"severity":"DEBUG","event":"execution_start","message":"Execution started for 'asr' on local","attributes":{"stage":"asr","target":"local"}}"#,
r#"{"timestamp":1050,"severity":"DEBUG","event":"execution_complete","message":"Execution completed for 'asr' on local in 47ms","attributes":{"stage":"asr","target":"local","execution_time_ms":47}}"#,
r#"{"timestamp":1051,"severity":"INFO","event":"stage_complete","message":"Stage 'asr' completed on local in 51ms","attributes":{"stage":"asr","target":"local","latency_ms":51}}"#,
r#"{"timestamp":1052,"severity":"INFO","event":"stage_start","message":"Stage 'tts' started","attributes":{"stage":"tts"}}"#,
r#"{"timestamp":1100,"severity":"INFO","event":"stage_complete","message":"Stage 'tts' completed on cloud in 48ms","attributes":{"stage":"tts","target":"cloud","latency_ms":48}}"#,
];
let log_content = telemetry_entries.join("\n");
fs::write(&trace_file, log_content)?;
println!(" Created test trace file: {}", trace_file.display());
let now = std::time::SystemTime::now();
let file_time = filetime::FileTime::from_system_time(now);
filetime::set_file_times(&trace_file, file_time, file_time)?;
println!(" Running: xybrid trace --latest");
let output = Command::new(&cli_binary)
.args(["trace", "--latest"])
.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!(" Exit code: {}", output.status.code().unwrap_or(-1));
if !output.status.success() {
eprintln!(" STDOUT:\n{}", stdout);
eprintln!(" STDERR:\n{}", stderr);
}
let has_stage_complete = stdout.contains("stage_complete")
|| stdout.contains("Stage Completions")
|| stdout.contains("Stage:") && stdout.contains("→");
let has_telemetry_table = stdout.contains("Telemetry Events")
|| stdout.contains("Timestamp")
|| stdout.contains("Event");
let has_summary = stdout.contains("Summary")
|| stdout.contains("Total Events")
|| stdout.contains("Policy Evaluations");
assert!(
output.status.success(),
"Trace command should succeed. Exit code: {}, stdout: {}, stderr: {}",
output.status.code().unwrap_or(-1),
stdout,
stderr
);
assert!(
has_stage_complete || has_telemetry_table || has_summary,
"Output should contain telemetry information. stdout: {}, stderr: {}",
stdout,
stderr
);
let stage_complete_found = stdout.contains("stage_complete")
|| stdout.contains("Stage Completions")
|| stdout.contains("Stage:")
|| (stdout.contains("asr") && (stdout.contains("51") || stdout.contains("ms")))
|| (stdout.contains("tts") && (stdout.contains("48") || stdout.contains("ms")));
if !stage_complete_found && output.status.success() {
println!(
" Note: stage_complete not found but command succeeded (may be different session)"
);
}
assert!(
output.status.success(),
"Trace command should succeed. Exit code: {}, stdout: {}, stderr: {}",
output.status.code().unwrap_or(-1),
stdout,
stderr
);
println!(" ✅ Trace command output verified");
println!(" Output length: {} bytes", stdout.len());
println!();
let _ = fs::remove_file(&trace_file);
Ok(())
}
fn get_cli_binary_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.pop();
let debug_path = path.join("target").join("debug").join("xybrid");
if debug_path.exists() {
return Ok(debug_path);
}
let release_path = path.join("target").join("release").join("xybrid");
if release_path.exists() {
return Ok(release_path);
}
Ok(path.join("target").join("debug").join("xybrid"))
}