reputation-core 0.1.0

Core calculation engine for the KnowThat Reputation System with advanced scoring algorithms
Documentation
//! Comprehensive tests for enhanced score structure components from REP-012

use reputation_core::{Calculator, ConfidenceLevel};
use reputation_types::AgentDataBuilder;

#[test]
fn test_confidence_level_boundaries() {
    // Test exact boundary values
    assert_eq!(ConfidenceLevel::from_confidence(0.0), ConfidenceLevel::Low);
    assert_eq!(ConfidenceLevel::from_confidence(0.199), ConfidenceLevel::Low);
    assert_eq!(ConfidenceLevel::from_confidence(0.2), ConfidenceLevel::Medium);
    assert_eq!(ConfidenceLevel::from_confidence(0.5), ConfidenceLevel::Medium);
    assert_eq!(ConfidenceLevel::from_confidence(0.699), ConfidenceLevel::Medium);
    assert_eq!(ConfidenceLevel::from_confidence(0.7), ConfidenceLevel::High);
    assert_eq!(ConfidenceLevel::from_confidence(1.0), ConfidenceLevel::High);
}

#[test]
fn test_score_components_breakdown() {
    let calc = Calculator::default();
    
    // Test with an agent that has all bonuses
    let agent = AgentDataBuilder::new("did:test:components")
        .with_reviews(50, 4.2)
        .total_interactions(100)
        .mcp_level(2)
        .identity_verified(true)
        .security_audit_passed(true)
        .open_source(true)
        .build()
        .unwrap();
    
    let score = calc.calculate(&agent).unwrap();
    let components = &score.components;
    
    // Verify prior score breakdown
    assert_eq!(components.prior_score, 75.0); // 50 + 10 + 5 + 7 + 3 = 75
    assert_eq!(components.empirical_score, 80.0); // (4.2 - 1) * 25
    
    // Verify confidence values match
    assert_eq!(components.confidence_level, score.level);
    assert_eq!(components.confidence_value, score.confidence);
    
    // Verify confidence is High (100 interactions)
    assert_eq!(components.confidence_level, ConfidenceLevel::High);
}

#[test]
fn test_prior_breakdown_individual_bonuses() {
    let calc = Calculator::default();
    
    // Test each bonus individually
    let base_agent = AgentDataBuilder::new("did:test:prior")
        .build()
        .unwrap();
    
    let base_score = calc.calculate(&base_agent).unwrap();
    assert_eq!(base_score.components.prior_breakdown.base_score, 50.0);
    assert_eq!(base_score.components.prior_breakdown.mcp_bonus, 0.0);
    assert_eq!(base_score.components.prior_breakdown.identity_bonus, 0.0);
    assert_eq!(base_score.components.prior_breakdown.security_audit_bonus, 0.0);
    assert_eq!(base_score.components.prior_breakdown.open_source_bonus, 0.0);
    assert_eq!(base_score.components.prior_breakdown.age_bonus, 0.0);
    assert_eq!(base_score.components.prior_breakdown.total, 50.0);
    
    // Test MCP bonus
    let mcp_agent = AgentDataBuilder::new("did:test:mcp")
        .mcp_level(2)
        .build()
        .unwrap();
    
    let mcp_score = calc.calculate(&mcp_agent).unwrap();
    assert_eq!(mcp_score.components.prior_breakdown.mcp_bonus, 10.0);
    assert_eq!(mcp_score.components.prior_breakdown.total, 60.0);
    
    // Test identity bonus
    let id_agent = AgentDataBuilder::new("did:test:identity")
        .identity_verified(true)
        .build()
        .unwrap();
    
    let id_score = calc.calculate(&id_agent).unwrap();
    assert_eq!(id_score.components.prior_breakdown.identity_bonus, 5.0);
    assert_eq!(id_score.components.prior_breakdown.total, 55.0);
}

#[test]
fn test_prior_breakdown_cap_enforcement() {
    let calc = Calculator::default();
    
    // Create an agent with all bonuses that would exceed cap
    let agent = AgentDataBuilder::new("did:test:maxed")
        .mcp_level(3) // +15
        .identity_verified(true) // +5
        .security_audit_passed(true) // +7
        .open_source(true) // +3
        .build()
        .unwrap();
    
    // Note: age bonus would need 365+ days, skipping for simplicity
    
    let score = calc.calculate(&agent).unwrap();
    let breakdown = &score.components.prior_breakdown;
    
    // Individual bonuses should be correct
    assert_eq!(breakdown.base_score, 50.0);
    assert_eq!(breakdown.mcp_bonus, 15.0);
    assert_eq!(breakdown.identity_bonus, 5.0);
    assert_eq!(breakdown.security_audit_bonus, 7.0);
    assert_eq!(breakdown.open_source_bonus, 3.0);
    
    // Total should be capped at 80
    assert_eq!(breakdown.total, 80.0);
    assert_eq!(score.components.prior_score, 80.0);
}

#[test]
fn test_is_provisional_flag() {
    let calc = Calculator::default();
    
    // Test provisional (low confidence)
    let new_agent = AgentDataBuilder::new("did:test:new")
        .with_reviews(2, 4.0)
        .total_interactions(2)
        .build()
        .unwrap();
    
    let new_score = calc.calculate(&new_agent).unwrap();
    assert!(new_score.is_provisional);
    assert_eq!(new_score.level, ConfidenceLevel::Low);
    
    // Test non-provisional (medium confidence)
    let medium_agent = AgentDataBuilder::new("did:test:medium")
        .with_reviews(5, 4.0)
        .total_interactions(5)
        .build()
        .unwrap();
    
    let medium_score = calc.calculate(&medium_agent).unwrap();
    assert!(!medium_score.is_provisional);
    assert_eq!(medium_score.level, ConfidenceLevel::Medium);
    
    // Test non-provisional (high confidence)
    let high_agent = AgentDataBuilder::new("did:test:high")
        .with_reviews(50, 4.0)
        .total_interactions(50)
        .build()
        .unwrap();
    
    let high_score = calc.calculate(&high_agent).unwrap();
    assert!(!high_score.is_provisional);
    assert_eq!(high_score.level, ConfidenceLevel::High);
}

#[test]
fn test_data_points_calculation() {
    let calc = Calculator::default();
    
    // Test various scenarios
    let test_cases = vec![
        (0, 0, 0),           // No data
        (5, 10, 15),         // Some reviews, more interactions  
        (50, 50, 100),       // Equal reviews and interactions
        (100, 200, 300),     // Many more interactions than reviews
    ];
    
    for (reviews, interactions, expected_points) in test_cases {
        let mut builder = AgentDataBuilder::new(&format!("did:test:points{}", reviews));
        if reviews > 0 {
            builder = builder.with_reviews(reviews, 4.0);
        }
        let agent = builder
            .total_interactions(interactions)
            .build()
            .unwrap();
        
        let score = calc.calculate(&agent).unwrap();
        assert_eq!(
            score.data_points, expected_points,
            "Failed for reviews={}, interactions={}", reviews, interactions
        );
    }
}

#[test]
fn test_score_components_serialization() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:serialize")
        .with_reviews(30, 3.8)
        .total_interactions(50)
        .mcp_level(1)
        .identity_verified(true)
        .build()
        .unwrap();
    
    let score = calc.calculate(&agent).unwrap();
    
    // Serialize the components
    let json = serde_json::to_string_pretty(&score.components).unwrap();
    
    // Verify JSON contains expected fields
    assert!(json.contains("\"prior_score\""));
    assert!(json.contains("\"empirical_score\""));
    assert!(json.contains("\"confidence_level\""));
    assert!(json.contains("\"confidence_value\""));
    assert!(json.contains("\"prior_breakdown\""));
    assert!(json.contains("\"base_score\""));
    assert!(json.contains("\"mcp_bonus\""));
    assert!(json.contains("\"identity_bonus\""));
    
    // Deserialize and verify
    let deserialized: reputation_core::ScoreComponents = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.prior_score, score.components.prior_score);
    assert_eq!(deserialized.empirical_score, score.components.empirical_score);
    assert_eq!(deserialized.confidence_level, score.components.confidence_level);
}

#[test]
fn test_confidence_level_affects_weighting() {
    let calc = Calculator::default();
    
    // Low confidence - prior should dominate
    let low_conf_agent = AgentDataBuilder::new("did:test:lowconf")
        .with_reviews(1, 5.0) // Perfect rating
        .total_interactions(1)
        .build()
        .unwrap();
    
    let low_score = calc.calculate(&low_conf_agent).unwrap();
    assert!(low_score.confidence < 0.1);
    // Score should be close to prior (50) despite perfect rating
    assert!(low_score.score < 60.0);
    
    // High confidence - empirical should dominate
    let high_conf_agent = AgentDataBuilder::new("did:test:highconf")
        .with_reviews(200, 5.0) // Perfect rating
        .total_interactions(200)
        .build()
        .unwrap();
    
    let high_score = calc.calculate(&high_conf_agent).unwrap();
    assert!(high_score.confidence > 0.9);
    // Score should be close to empirical (100) with perfect rating
    assert!(high_score.score > 95.0);
}

#[test]
fn test_edge_case_zero_data_components() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:zero")
        .build()
        .unwrap();
    
    let score = calc.calculate(&agent).unwrap();
    
    // Verify all components
    assert_eq!(score.components.prior_score, 50.0);
    assert_eq!(score.components.empirical_score, 50.0); // Default empirical when no reviews
    assert_eq!(score.components.confidence_value, 0.0);
    assert_eq!(score.components.confidence_level, ConfidenceLevel::Low);
    assert_eq!(score.data_points, 0);
    assert!(score.is_provisional);
    
    // Final score should be pure prior when confidence is 0
    assert_eq!(score.score, 50.0);
}

#[test]
fn test_all_mcp_levels_in_breakdown() {
    let calc = Calculator::default();
    
    for level in 0..=3 {
        let agent = AgentDataBuilder::new(&format!("did:test:mcp{}", level))
            .mcp_level(level)
            .build()
            .unwrap();
        
        let score = calc.calculate(&agent).unwrap();
        let expected_bonus = match level {
            1 => 5.0,
            2 => 10.0,
            3 => 15.0,
            _ => 0.0,
        };
        
        assert_eq!(
            score.components.prior_breakdown.mcp_bonus, expected_bonus,
            "Wrong MCP bonus for level {}", level
        );
    }
}

#[test]
fn test_components_consistency() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:consistency")
        .with_reviews(25, 3.5)
        .total_interactions(40)
        .mcp_level(1)
        .security_audit_passed(true)
        .build()
        .unwrap();
    
    let score = calc.calculate(&agent).unwrap();
    
    // Verify mathematical consistency
    let expected_confidence = 40.0 / (40.0 + 15.0);
    assert!((score.confidence - expected_confidence).abs() < 0.001);
    
    // Verify score calculation from components
    let calculated_score = (1.0 - score.confidence) * score.components.prior_score 
        + score.confidence * score.components.empirical_score;
    assert!((score.score - calculated_score).abs() < 0.001);
    
    // Verify prior breakdown adds up
    let breakdown = &score.components.prior_breakdown;
    let manual_total = breakdown.base_score + breakdown.mcp_bonus + breakdown.identity_bonus
        + breakdown.security_audit_bonus + breakdown.open_source_bonus + breakdown.age_bonus;
    assert_eq!(breakdown.total, manual_total.min(80.0));
}