blvm-node 0.1.48

Bitcoin Commons BLVM: Minimal Bitcoin node implementation using blvm-protocol and blvm-consensus
//! Tests for Module Manifest Validator

use blvm_node::module::registry::manifest::ModuleManifest;
use blvm_node::module::validation::manifest_validator::{ManifestValidator, ValidationResult};
use std::collections::HashMap;

fn create_valid_manifest() -> ModuleManifest {
    ModuleManifest {
        name: "test-module".to_string(),
        version: "1.0.0".to_string(),
        description: Some("Test module".to_string()),
        author: Some("Test Author".to_string()),
        capabilities: Vec::new(),
        rpc_overrides: Vec::new(),
        dependencies: HashMap::new(),
        optional_dependencies: HashMap::new(),
        entry_point: "test-module.so".to_string(),
        config_schema: HashMap::new(),
        binary: None,
        downloads: HashMap::new(),
        signatures: None,
        payment: None,
    }
}

#[test]
fn test_manifest_validator_new() {
    let validator = ManifestValidator::new();
    // Validator should be created
}

#[test]
fn test_manifest_validator_default() {
    let validator = ManifestValidator::default();
    // Default should work
}

#[test]
fn test_manifest_validator_valid_manifest() {
    let validator = ManifestValidator::new();
    let manifest = create_valid_manifest();

    let result = validator.validate(&manifest);
    assert_eq!(result, ValidationResult::Valid);
}

#[test]
fn test_manifest_validator_empty_name() {
    let validator = ManifestValidator::new();
    let mut manifest = create_valid_manifest();
    manifest.name = String::new();

    let result = validator.validate(&manifest);
    match result {
        ValidationResult::Invalid(errors) => {
            assert!(errors.iter().any(|e| e.contains("name cannot be empty")));
        }
        ValidationResult::Valid => panic!("Should be invalid"),
    }
}

#[test]
fn test_manifest_validator_empty_version() {
    let validator = ManifestValidator::new();
    let mut manifest = create_valid_manifest();
    manifest.version = String::new();

    let result = validator.validate(&manifest);
    match result {
        ValidationResult::Invalid(errors) => {
            assert!(errors.iter().any(|e| e.contains("version cannot be empty")));
        }
        ValidationResult::Valid => panic!("Should be invalid"),
    }
}

#[test]
fn test_manifest_validator_empty_entry_point() {
    let validator = ManifestValidator::new();
    let mut manifest = create_valid_manifest();
    manifest.entry_point = String::new();

    let result = validator.validate(&manifest);
    match result {
        ValidationResult::Invalid(errors) => {
            assert!(
                errors
                    .iter()
                    .any(|e| e.contains("Entry point cannot be empty"))
            );
        }
        ValidationResult::Valid => panic!("Should be invalid"),
    }
}

#[test]
fn test_manifest_validator_invalid_version_format() {
    let validator = ManifestValidator::new();
    let mut manifest = create_valid_manifest();
    manifest.version = "invalid".to_string();

    let result = validator.validate(&manifest);
    match result {
        ValidationResult::Invalid(errors) => {
            assert!(errors.iter().any(|e| e.contains("Invalid version format")));
        }
        ValidationResult::Valid => panic!("Should be invalid"),
    }
}

#[test]
fn test_manifest_validator_valid_version_formats() {
    let validator = ManifestValidator::new();

    let valid_versions = vec![
        "1.0",
        "1.0.0",
        "2.1.3",
        "1.0.0-alpha",
        "1.0.0+build",
        "1.0.0-alpha+build",
    ];

    for version in valid_versions {
        let mut manifest = create_valid_manifest();
        manifest.version = version.to_string();

        let result = validator.validate(&manifest);
        assert_eq!(
            result,
            ValidationResult::Valid,
            "Version {version} should be valid"
        );
    }
}

#[test]
fn test_manifest_validator_invalid_name_format() {
    let validator = ManifestValidator::new();

    let invalid_names = vec![
        "",             // Empty
        "-invalid",     // Starts with dash
        "_invalid",     // Starts with underscore
        "invalid name", // Contains space
        "invalid@name", // Contains invalid char
    ];

    for name in invalid_names {
        let mut manifest = create_valid_manifest();
        manifest.name = name.to_string();

        let result = validator.validate(&manifest);
        match result {
            ValidationResult::Invalid(errors) => {
                assert!(
                    errors.iter().any(|e| e.contains("Invalid module name")),
                    "Name '{name}' should be invalid"
                );
            }
            ValidationResult::Valid => panic!("Name '{name}' should be invalid"),
        }
    }
}

#[test]
fn test_manifest_validator_valid_name_formats() {
    let validator = ManifestValidator::new();

    let valid_names = vec![
        "test-module",
        "test_module",
        "testModule",
        "test123",
        "a",
        "module-name-with-dashes",
        "module_name_with_underscores",
    ];

    for name in valid_names {
        let mut manifest = create_valid_manifest();
        manifest.name = name.to_string();

        let result = validator.validate(&manifest);
        assert_eq!(
            result,
            ValidationResult::Valid,
            "Name '{name}' should be valid"
        );
    }
}

#[test]
fn test_manifest_validator_invalid_capability() {
    let validator = ManifestValidator::new();
    let mut manifest = create_valid_manifest();
    manifest.capabilities = vec!["invalid-capability".to_string()];

    let result = validator.validate(&manifest);
    match result {
        ValidationResult::Invalid(errors) => {
            assert!(errors.iter().any(|e| e.contains("Unknown capability")));
        }
        ValidationResult::Valid => panic!("Should be invalid"),
    }
}

#[test]
fn test_manifest_validator_valid_capabilities() {
    let validator = ManifestValidator::new();
    let mut manifest = create_valid_manifest();
    manifest.capabilities = vec![
        "read_blockchain".to_string(),
        "subscribe_events".to_string(),
        "queue_inbound_block".to_string(),
    ];

    let result = validator.validate(&manifest);
    assert_eq!(result, ValidationResult::Valid);
}

#[test]
fn test_manifest_validator_invalid_dependency_name() {
    let validator = ManifestValidator::new();
    let mut manifest = create_valid_manifest();
    let mut deps = HashMap::new();
    deps.insert("invalid-name!".to_string(), "1.0.0".to_string());
    manifest.dependencies = deps;

    let result = validator.validate(&manifest);
    match result {
        ValidationResult::Invalid(errors) => {
            assert!(errors.iter().any(|e| e.contains("Invalid dependency name")));
        }
        ValidationResult::Valid => panic!("Should be invalid"),
    }
}

#[test]
fn test_manifest_validator_invalid_dependency_version() {
    let validator = ManifestValidator::new();
    let mut manifest = create_valid_manifest();
    let mut deps = HashMap::new();
    deps.insert("valid-dep".to_string(), "invalid-version".to_string());
    manifest.dependencies = deps;

    let result = validator.validate(&manifest);
    match result {
        ValidationResult::Invalid(errors) => {
            assert!(
                errors
                    .iter()
                    .any(|e| e.contains("Invalid dependency version format"))
            );
        }
        ValidationResult::Valid => panic!("Should be invalid"),
    }
}

#[test]
fn test_manifest_validator_valid_dependency_versions() {
    let validator = ManifestValidator::new();

    let valid_versions = vec!["1.0.0", ">=1.0.0", "<=2.0.0", "==1.0.0", "^1.0.0", "~1.0.0"];

    for version in valid_versions {
        let mut manifest = create_valid_manifest();
        let mut deps = HashMap::new();
        deps.insert("valid-dep".to_string(), version.to_string());
        manifest.dependencies = deps;

        let result = validator.validate(&manifest);
        assert_eq!(
            result,
            ValidationResult::Valid,
            "Version '{version}' should be valid"
        );
    }
}

#[test]
fn test_manifest_validator_multiple_errors() {
    let validator = ManifestValidator::new();
    let mut manifest = create_valid_manifest();
    manifest.name = String::new();
    manifest.version = "invalid".to_string();
    manifest.entry_point = String::new();

    let result = validator.validate(&manifest);
    match result {
        ValidationResult::Invalid(errors) => {
            assert!(errors.len() >= 3); // Should have multiple errors
        }
        ValidationResult::Valid => panic!("Should be invalid"),
    }
}

#[test]
fn test_manifest_content_without_signatures_strips_table() {
    use blvm_node::module::validation::manifest_content_without_signatures;

    let input = r#"
name = "test-module"
version = "1.0.0"
entry_point = "test-module.so"

[signatures]
threshold = "1-of-1"

[[signatures.maintainers]]
name = "bob"
public_key = "02bb"
signature = "cafe"
"#;
    let stripped = manifest_content_without_signatures(input).unwrap();
    let table = toml::from_str::<toml::Value>(&stripped)
        .unwrap()
        .as_table()
        .unwrap()
        .clone();
    assert!(!table.contains_key("signatures"));
    assert_eq!(
        table.get("entry_point").unwrap().as_str(),
        Some("test-module.so")
    );
}

#[test]
fn test_validate_module_signature_unsigned_skips() {
    use blvm_node::module::validation::manifest_validator::validate_module_signature;
    use std::path::PathBuf;

    let manifest = create_valid_manifest();
    let binary_path = PathBuf::from("/nonexistent/binary.so");

    let result = validate_module_signature(&manifest, &binary_path);
    assert!(
        result.is_ok(),
        "unsigned manifests skip signature verification"
    );
}

#[test]
fn test_validate_module_signature_signed_requires_manifest_file() {
    use blvm_node::module::registry::manifest::{MaintainerSignature, SignatureSection};
    use blvm_node::module::validation::manifest_validator::validate_module_signature;
    use std::path::Path;

    let mut manifest = create_valid_manifest();
    manifest.signatures = Some(SignatureSection {
        threshold: Some("1-of-1".to_string()),
        maintainers: vec![MaintainerSignature {
            name: "alice".to_string(),
            public_key: "02".repeat(32),
            signature: "00".repeat(64),
        }],
    });

    let temp = tempfile::TempDir::new().unwrap();
    let binary = temp.path().join("test-module.so");
    std::fs::write(&binary, b"binary").unwrap();

    let result = validate_module_signature(&manifest, &binary);
    assert!(
        result.is_err(),
        "signed manifests must fail when module.toml is missing beside the binary"
    );
}