runeforge 25.8.0

Blueprint to optimal stack JSON converter - Part of Rune* Ecosystem
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;

#[test]
fn test_plan_command_with_baseline_example() {
    let mut cmd = Command::cargo_bin("runeforge").unwrap();
    cmd.args(&["plan", "-f", "examples/baseline.yaml", "--seed", "42", "--strict"])
        .assert()
        .success()
        .stdout(predicate::str::contains("decisions"))
        .stdout(predicate::str::contains("stack"))
        .stdout(predicate::str::contains("estimated"));
}

#[test]
fn test_plan_command_with_latency_example() {
    let mut cmd = Command::cargo_bin("runeforge").unwrap();
    cmd.args(&["plan", "-f", "examples/latency.yaml", "--seed", "42"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"language\": \"Rust\""));
}

#[test]
fn test_plan_command_with_compliance_example() {
    let mut cmd = Command::cargo_bin("runeforge").unwrap();
    cmd.args(&["plan", "-f", "examples/compliance.yaml", "--seed", "42"])
        .assert()
        .success();
}

#[test]
fn test_plan_command_with_output_file() {
    let temp_dir = TempDir::new().unwrap();
    let output_path = temp_dir.path().join("output.json");

    let mut cmd = Command::cargo_bin("runeforge").unwrap();
    cmd.args(&[
        "plan",
        "-f", "examples/baseline.yaml",
        "--seed", "42",
        "--out", output_path.to_str().unwrap()
    ])
    .assert()
    .success();

    // Verify output file was created
    assert!(output_path.exists());
    
    // Verify JSON content
    let content = fs::read_to_string(&output_path).unwrap();
    assert!(content.contains("decisions"));
    assert!(content.contains("stack"));
}

#[test]
fn test_plan_deterministic_with_same_seed() {
    let mut cmd1 = Command::cargo_bin("runeforge").unwrap();
    let output1 = cmd1
        .args(&["plan", "-f", "examples/baseline.yaml", "--seed", "42"])
        .output()
        .unwrap();

    let mut cmd2 = Command::cargo_bin("runeforge").unwrap();
    let output2 = cmd2
        .args(&["plan", "-f", "examples/baseline.yaml", "--seed", "42"])
        .output()
        .unwrap();

    assert_eq!(output1.stdout, output2.stdout);
}

#[test]
fn test_plan_different_with_different_seed() {
    let mut cmd1 = Command::cargo_bin("runeforge").unwrap();
    let output1 = cmd1
        .args(&["plan", "-f", "examples/baseline.yaml", "--seed", "42"])
        .output()
        .unwrap();

    let mut cmd2 = Command::cargo_bin("runeforge").unwrap();
    let output2 = cmd2
        .args(&["plan", "-f", "examples/baseline.yaml", "--seed", "43"])
        .output()
        .unwrap();

    // Note: They might still be the same due to deterministic selection, but that's ok
    // This test just ensures the seed parameter is being used
    assert!(output1.status.success());
    assert!(output2.status.success());
}

#[test]
fn test_plan_command_with_nonexistent_file() {
    let mut cmd = Command::cargo_bin("runeforge").unwrap();
    cmd.args(&["plan", "-f", "nonexistent.yaml", "--seed", "42"])
        .assert()
        .failure();
}

#[test]
fn test_plan_command_with_json_input() {
    // Create a temporary JSON blueprint
    let temp_dir = TempDir::new().unwrap();
    let json_path = temp_dir.path().join("test.json");
    
    let json_content = r#"
    {
        "project_name": "json-test",
        "goals": ["test"],
        "traffic_profile": {
            "rps_peak": 100.0,
            "global": false,
            "latency_sensitive": false
        }
    }
    "#;
    
    fs::write(&json_path, json_content).unwrap();

    let mut cmd = Command::cargo_bin("runeforge").unwrap();
    cmd.args(&["plan", "-f", json_path.to_str().unwrap(), "--seed", "42"])
        .assert()
        .success();
}

#[test]
fn test_schema_validation_requirements() {
    let mut cmd = Command::cargo_bin("runeforge").unwrap();
    let output = cmd
        .args(&["plan", "-f", "examples/baseline.yaml", "--seed", "42", "--strict"])
        .output()
        .unwrap();

    assert!(output.status.success());

    // Parse JSON output to verify it matches schema requirements
    let json_str = String::from_utf8(output.stdout).unwrap();
    let plan: serde_json::Value = serde_json::from_str(&json_str).unwrap();

    // Verify required fields exist
    assert!(plan.get("decisions").is_some());
    assert!(plan.get("stack").is_some());
    assert!(plan.get("estimated").is_some());
    assert!(plan.get("slo").is_some());
    assert!(plan.get("risk").is_some());

    // Verify decisions structure
    let decisions = plan["decisions"].as_array().unwrap();
    for decision in decisions {
        assert!(decision.get("topic").is_some());
        assert!(decision.get("choice").is_some());
        assert!(decision.get("reasons").is_some());
        assert!(decision.get("alternatives").is_some());
        
        // Verify reasons and alternatives have at least 1 item
        let reasons = decision["reasons"].as_array().unwrap();
        let alternatives = decision["alternatives"].as_array().unwrap();
        assert!(!reasons.is_empty());
        assert!(!alternatives.is_empty());
    }

    // Verify stack has required fields
    let stack = &plan["stack"];
    assert!(stack.get("language").is_some());
    assert!(stack.get("backend").is_some());
    assert!(stack.get("infra").is_some());
}