//! Signed self-update tests — the security-critical part is signature
//! verification: a valid signature is accepted, anything tampered is rejected.
//! Fixtures (tests/fixtures/sample.bin + .minisig) were signed with the real
//! release private key, so these run with no key present (as in CI).

use corrosion_host_agent::update;

const SAMPLE: &[u8] = include_bytes!("fixtures/sample.bin");
const SAMPLE_SIG: &str = include_str!("fixtures/sample.bin.minisig");

#[test]
fn accepts_a_validly_signed_binary() {
    update::verify_signature(SAMPLE, SAMPLE_SIG).expect("valid signature must verify");
}

#[test]
fn rejects_a_tampered_binary() {
    let mut tampered = SAMPLE.to_vec();
    tampered[0] ^= 0xFF; // flip a byte
    let err = update::verify_signature(&tampered, SAMPLE_SIG)
        .expect_err("tampered binary must be rejected");
    assert!(err.to_string().contains("verification failed"), "got: {err}");
}

#[test]
fn rejects_a_garbage_signature() {
    assert!(update::verify_signature(SAMPLE, "not a real minisig blob").is_err());
}

#[test]
fn rejects_empty_binary_against_real_sig() {
    assert!(update::verify_signature(b"", SAMPLE_SIG).is_err());
}

#[test]
fn url_allowlist_enforced() {
    // Allowed.
    update::assert_url_allowed("https://cdn.corrosionmgmt.com/host-agent/alpha/corrosion-host-agent-linux-amd64")
        .expect("the real CDN host must be allowed");
    // http rejected.
    assert!(update::assert_url_allowed("http://cdn.corrosionmgmt.com/x").is_err());
    // wrong host rejected.
    assert!(update::assert_url_allowed("https://evil.example.com/x").is_err());
    // credential-in-URL (userinfo bypass) rejected.
    assert!(update::assert_url_allowed("https://cdn.corrosionmgmt.com:[email protected]/x").is_err());
    // host as userinfo trick rejected (real host is evil.com).
    assert!(update::assert_url_allowed("https://[email protected]/x").is_err());
}

#[test]
fn swap_binary_replaces_and_backs_up() {
    let dir = tempfile::tempdir().expect("tempdir");
    let current = dir.path().join("corrosion-host-agent");
    std::fs::write(&current, b"OLD BINARY").unwrap();

    update::swap_binary(&current, b"NEW BINARY").expect("swap should succeed");

    assert_eq!(std::fs::read(&current).unwrap(), b"NEW BINARY", "current is the new binary");
    let backup = dir.path().join("corrosion-host-agent.old");
    assert_eq!(std::fs::read(&backup).unwrap(), b"OLD BINARY", ".old holds the previous binary");
    // the .new scratch file is consumed by the rename
    assert!(!dir.path().join("corrosion-host-agent.new").exists());
}
