modde-cli 0.2.1

CLI interface for modde
mod common;

use common::Fixture;

fn add_game(fx: &Fixture, id: &str) -> std::process::Output {
    fx.cmd()
        .args([
            "game",
            "add",
            id,
            "--display-name",
            "Custom Game",
            "--executable-dir",
            "bin/x64",
        ])
        .output()
        .expect("spawn modde game add")
}

#[test]
fn export_user_game_round_trips_via_import() {
    let fx = Fixture::new();
    let out = add_game(&fx, "round-trip");
    assert!(
        out.status.success(),
        "add failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );

    let export_path = fx.root().join("round-trip.toml");
    let exported = fx
        .cmd()
        .args([
            "game",
            "export",
            "round-trip",
            "--output",
            export_path.to_str().unwrap(),
        ])
        .output()
        .expect("spawn modde game export");
    assert!(
        exported.status.success(),
        "export failed: {}",
        String::from_utf8_lossy(&exported.stderr)
    );

    let removed = fx
        .cmd()
        .args(["game", "remove", "round-trip", "--yes"])
        .output()
        .expect("spawn modde game remove");
    assert!(
        removed.status.success(),
        "remove failed: {}",
        String::from_utf8_lossy(&removed.stderr)
    );

    let imported = fx
        .cmd()
        .args(["game", "import", export_path.to_str().unwrap()])
        .output()
        .expect("spawn modde game import");
    assert!(
        imported.status.success(),
        "import failed: {}",
        String::from_utf8_lossy(&imported.stderr)
    );

    let list = fx
        .cmd()
        .args(["game", "list"])
        .output()
        .expect("spawn modde game list");
    assert!(list.status.success());
    let stdout = String::from_utf8_lossy(&list.stdout);
    assert!(stdout.contains("round-trip"), "missing game id:\n{stdout}");
}

#[test]
fn import_rejects_built_in_id_collision() {
    let fx = Fixture::new();
    let export_path = fx.root().join("skyrim.toml");
    let out = fx
        .cmd()
        .args([
            "game",
            "export",
            "skyrim-se",
            "--output",
            export_path.to_str().unwrap(),
        ])
        .output()
        .expect("spawn modde game export");
    assert!(
        out.status.success(),
        "export failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );

    let imported = fx
        .cmd()
        .args(["game", "import", export_path.to_str().unwrap(), "--force"])
        .output()
        .expect("spawn modde game import");
    assert!(!imported.status.success(), "built-in import should fail");
    let stderr = String::from_utf8_lossy(&imported.stderr);
    assert!(
        stderr.contains("collides with a built-in game"),
        "unexpected stderr:\n{stderr}"
    );
}

#[test]
fn export_with_optiscaler_includes_profiles() {
    let fx = Fixture::new();
    let export_path = fx.root().join("stellar-blade.toml");
    let out = fx
        .cmd()
        .args([
            "game",
            "export",
            "stellar-blade",
            "--with-optiscaler",
            "--output",
            export_path.to_str().unwrap(),
        ])
        .output()
        .expect("spawn modde game export");
    assert!(
        out.status.success(),
        "export failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let content = std::fs::read_to_string(export_path).expect("read export");
    assert!(
        content.contains("[optiscaler]"),
        "missing optiscaler section:\n{content}"
    );
    assert!(
        content.contains("[[optiscaler.profile]]"),
        "missing profile array:\n{content}"
    );
}

#[test]
fn import_profile_attaches_to_built_in_game() {
    let fx = Fixture::new();
    let profile_path = fx.root().join("profile.toml");
    std::fs::write(
        &profile_path,
        r#"
            [optiscaler]
            [[optiscaler.profile]]
            id = "test-profile"
            name = "Test Profile"
            source_url = "https://example.invalid/profile"
            tested_optiscaler_version = "1.0"
            proxy_dll = "dxgi.dll"
            notes = ""
        "#,
    )
    .unwrap();

    let imported = fx
        .cmd()
        .args([
            "game",
            "import-profile",
            profile_path.to_str().unwrap(),
            "--for",
            "stellar-blade",
        ])
        .output()
        .expect("spawn modde game import-profile");
    assert!(
        imported.status.success(),
        "import-profile failed: {}",
        String::from_utf8_lossy(&imported.stderr)
    );
    assert!(
        fx.data_dir()
            .join("games/stellar-blade.optiscaler.toml")
            .exists()
    );
}