#![forbid(unsafe_code)]
pub mod calculator;
pub mod config;
pub mod error;
pub mod validation;
pub mod performance;
pub use calculator::{Calculator, BatchOptions, BatchResult, BatchCalculation};
pub use calculator::builder::{CalculatorBuilder, BonusConfig, CalculatorPreset};
pub use calculator::utils::{ScoreExplanation, ScorePrediction, AgentComparison};
pub use config::CalculatorConfig;
pub use error::{ReputationError, ValidationError, BuilderError, CalculationError, Result};
pub use reputation_types::{ConfidenceLevel, ScoreComponents, PriorBreakdown};
pub const ALGORITHM_VERSION: &str = "1.0.0";
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Duration, Utc};
use reputation_types::AgentData;
fn create_valid_agent() -> AgentData {
AgentData {
did: "did:test:123".to_string(),
created_at: Utc::now() - Duration::days(1),
mcp_level: None,
identity_verified: false,
security_audit_passed: false,
open_source: false,
total_interactions: 0,
total_reviews: 0,
average_rating: None,
positive_reviews: 0,
negative_reviews: 0,
}
}
#[test]
fn test_default_calculation() {
let agent = create_valid_agent();
let calc = Calculator::default();
let score = calc.calculate(&agent).unwrap();
assert_eq!(score.score, 50.0);
assert_eq!(score.confidence, 0.0);
}
#[test]
fn test_invalid_did_format() {
let mut agent = create_valid_agent();
agent.did = "invalid-did".to_string();
let calc = Calculator::default();
let result = calc.calculate(&agent);
assert!(result.is_err());
match result.unwrap_err() {
ReputationError::ValidationError(ValidationError::InvalidDid(msg)) => {
assert!(msg.contains("DID must start with 'did:' prefix"));
}
_ => panic!("Expected InvalidDid error"),
}
}
#[test]
fn test_future_date_error() {
let mut agent = create_valid_agent();
agent.created_at = Utc::now() + Duration::days(1);
let calc = Calculator::default();
let result = calc.calculate(&agent);
assert!(result.is_err());
match result.unwrap_err() {
ReputationError::ValidationError(ValidationError::FutureDate(_)) => {}
_ => panic!("Expected FutureDate error"),
}
}
#[test]
fn test_invalid_rating_error() {
let mut agent = create_valid_agent();
agent.average_rating = Some(6.0);
agent.total_reviews = 10;
agent.positive_reviews = 6;
agent.negative_reviews = 4;
agent.total_interactions = 10;
let calc = Calculator::default();
let result = calc.calculate(&agent);
assert!(result.is_err());
match result.unwrap_err() {
ReputationError::ValidationError(ValidationError::InvalidRating(rating)) => {
assert_eq!(rating, 6.0);
}
_ => panic!("Expected InvalidRating error"),
}
}
#[test]
fn test_invalid_mcp_level_error() {
let mut agent = create_valid_agent();
agent.mcp_level = Some(5);
let calc = Calculator::default();
let result = calc.calculate(&agent);
assert!(result.is_err());
match result.unwrap_err() {
ReputationError::ValidationError(ValidationError::InvalidMcpLevel(level)) => {
assert_eq!(level, 5);
}
_ => panic!("Expected InvalidMcpLevel error"),
}
}
#[test]
fn test_inconsistent_reviews_error() {
let mut agent = create_valid_agent();
agent.positive_reviews = 10;
agent.negative_reviews = 5;
agent.total_reviews = 20; agent.total_interactions = 20;
agent.average_rating = Some(4.0);
let calc = Calculator::default();
let result = calc.calculate(&agent);
assert!(result.is_err());
match result.unwrap_err() {
ReputationError::ValidationError(ValidationError::InconsistentReviews) => {
}
_ => panic!("Expected InconsistentReviews error"),
}
}
#[test]
fn test_reviews_exceed_interactions_error() {
let mut agent = create_valid_agent();
agent.total_reviews = 10;
agent.positive_reviews = 6;
agent.negative_reviews = 4;
agent.total_interactions = 5;
agent.average_rating = Some(4.0);
let calc = Calculator::default();
let result = calc.calculate(&agent);
assert!(result.is_err());
match result.unwrap_err() {
ReputationError::ValidationError(ValidationError::InvalidField { field, .. }) => {
assert_eq!(field, "total_reviews");
}
_ => panic!("Expected InvalidField error"),
}
}
#[test]
fn test_calculator_new_invalid_confidence_k() {
let result = Calculator::new(-1.0, 50.0, 80.0);
assert!(result.is_err());
match result.unwrap_err() {
ReputationError::CalculationError(msg) => {
assert!(msg.contains("confidence_k must be positive"));
}
_ => panic!("Expected BuilderError for negative confidence_k"),
}
}
#[test]
fn test_calculator_new_invalid_prior_base() {
let result = Calculator::new(15.0, -10.0, 80.0);
assert!(result.is_err());
let result = Calculator::new(15.0, 110.0, 80.0);
assert!(result.is_err());
}
#[test]
fn test_calculator_new_invalid_prior_max() {
let result = Calculator::new(15.0, 50.0, 40.0); assert!(result.is_err());
let result = Calculator::new(15.0, 50.0, 110.0); assert!(result.is_err());
}
#[test]
fn test_valid_calculation_with_reviews() {
let mut agent = create_valid_agent();
agent.total_interactions = 100;
agent.total_reviews = 50;
agent.positive_reviews = 40;
agent.negative_reviews = 10;
agent.average_rating = Some(4.2);
agent.mcp_level = Some(2);
let calc = Calculator::default();
let score = calc.calculate(&agent).unwrap();
assert!(score.score > 50.0); assert!(score.confidence > 0.0 && score.confidence < 1.0);
}
#[test]
fn test_edge_case_minimum_rating() {
let mut agent = create_valid_agent();
agent.total_interactions = 10;
agent.total_reviews = 10;
agent.positive_reviews = 0;
agent.negative_reviews = 10;
agent.average_rating = Some(1.0);
let calc = Calculator::default();
let score = calc.calculate(&agent).unwrap();
assert!(score.score >= 0.0);
assert!(score.score <= 100.0);
}
#[test]
fn test_edge_case_maximum_rating() {
let mut agent = create_valid_agent();
agent.total_interactions = 10;
agent.total_reviews = 10;
agent.positive_reviews = 10;
agent.negative_reviews = 0;
agent.average_rating = Some(5.0);
let calc = Calculator::default();
let score = calc.calculate(&agent).unwrap();
assert!(score.score >= 0.0);
assert!(score.score <= 100.0);
}
#[test]
fn test_mcp_level_bonuses() {
let base_agent = create_valid_agent();
let calc = Calculator::default();
for level in 0..=3 {
let mut agent = base_agent.clone();
agent.mcp_level = Some(level);
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.score, 50.0 + expected_bonus);
}
}
#[test]
fn test_confidence_calculation() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
let test_cases = vec![(0, 0.0), (15, 0.5), (30, 0.667), (150, 0.909)];
for (interactions, expected_confidence) in test_cases {
agent.total_interactions = interactions;
let score = calc.calculate(&agent).unwrap();
assert!((score.confidence - expected_confidence).abs() < 0.01);
}
}
#[test]
fn test_error_propagation_chain() {
let mut agent = create_valid_agent();
agent.did = "bad-did".to_string();
agent.average_rating = Some(10.0);
let calc = Calculator::default();
let result = calc.calculate(&agent);
assert!(result.is_err());
match result.unwrap_err() {
ReputationError::ValidationError(ValidationError::InvalidDid(_)) => {}
_ => panic!("Expected InvalidDid error to be caught first"),
}
}
#[test]
fn test_valid_edge_case_ratings() {
let calc = Calculator::default();
let test_ratings = vec![1.0, 5.0, 3.0];
for rating in test_ratings {
let mut agent = create_valid_agent();
agent.total_interactions = 50;
agent.total_reviews = 50;
agent.positive_reviews = 25;
agent.negative_reviews = 25;
agent.average_rating = Some(rating);
let result = calc.calculate(&agent);
assert!(result.is_ok(), "Rating {} should be valid", rating);
}
}
#[test]
fn test_zero_confidence_k_prevention() {
let result = Calculator::new(0.0, 50.0, 80.0);
assert!(result.is_err());
}
#[test]
fn test_nan_prevention_in_calculation() {
let calc = Calculator::default();
let agent = create_valid_agent();
let result = calc.calculate(&agent);
assert!(result.is_ok());
let score = result.unwrap();
assert!(!score.score.is_nan());
assert!(!score.confidence.is_nan());
}
#[test]
fn test_all_identity_flags() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
agent.identity_verified = true;
agent.security_audit_passed = true;
agent.open_source = true;
agent.mcp_level = Some(3);
let result = calc.calculate(&agent);
assert!(result.is_ok());
let score = result.unwrap();
assert_eq!(score.score, 80.0); }
#[test]
fn test_identity_verified_bonus() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
let score_without = calc.calculate(&agent).unwrap();
agent.identity_verified = true;
let score_with = calc.calculate(&agent).unwrap();
assert_eq!(score_with.score - score_without.score, 5.0);
assert_eq!(score_with.score, 55.0); }
#[test]
fn test_security_audit_bonus() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
let score_without = calc.calculate(&agent).unwrap();
agent.security_audit_passed = true;
let score_with = calc.calculate(&agent).unwrap();
assert_eq!(score_with.score - score_without.score, 7.0);
assert_eq!(score_with.score, 57.0); }
#[test]
fn test_open_source_bonus() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
let score_without = calc.calculate(&agent).unwrap();
agent.open_source = true;
let score_with = calc.calculate(&agent).unwrap();
assert_eq!(score_with.score - score_without.score, 3.0);
assert_eq!(score_with.score, 53.0); }
#[test]
fn test_age_bonus() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
agent.created_at = Utc::now() - Duration::days(1);
let score_young = calc.calculate(&agent).unwrap();
agent.created_at = Utc::now() - Duration::days(400);
let score_old = calc.calculate(&agent).unwrap();
assert_eq!(score_old.score - score_young.score, 5.0);
assert_eq!(score_young.score, 50.0); assert_eq!(score_old.score, 55.0); }
#[test]
fn test_age_bonus_edge_cases() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
agent.created_at = Utc::now() - Duration::days(365);
let score_365 = calc.calculate(&agent).unwrap();
assert_eq!(score_365.score, 50.0);
agent.created_at = Utc::now() - Duration::days(366);
let score_366 = calc.calculate(&agent).unwrap();
assert_eq!(score_366.score, 55.0); }
#[test]
fn test_combined_bonuses() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
agent.mcp_level = Some(2); agent.identity_verified = true; let score = calc.calculate(&agent).unwrap();
assert_eq!(score.score, 65.0);
agent.open_source = true; let score = calc.calculate(&agent).unwrap();
assert_eq!(score.score, 68.0);
agent.security_audit_passed = true; let score = calc.calculate(&agent).unwrap();
assert_eq!(score.score, 75.0); }
#[test]
fn test_prior_score_cap() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
agent.mcp_level = Some(3); agent.identity_verified = true; agent.security_audit_passed = true; agent.open_source = true; agent.created_at = Utc::now() - Duration::days(400);
let score = calc.calculate(&agent).unwrap();
assert_eq!(score.score, 80.0);
}
#[test]
fn test_prior_score_cap_with_interactions() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
agent.mcp_level = Some(3);
agent.identity_verified = true;
agent.security_audit_passed = true;
agent.open_source = true;
agent.created_at = Utc::now() - Duration::days(400);
agent.total_interactions = 50;
agent.total_reviews = 50;
agent.positive_reviews = 45;
agent.negative_reviews = 5;
agent.average_rating = Some(4.5);
let score = calc.calculate(&agent).unwrap();
assert!(score.score > 80.0); assert!(score.score < 90.0);
}
#[test]
fn test_enhanced_score_structure() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
agent.total_interactions = 50;
agent.total_reviews = 30;
agent.positive_reviews = 25;
agent.negative_reviews = 5;
agent.average_rating = Some(4.2);
agent.mcp_level = Some(2);
agent.identity_verified = true;
let score = calc.calculate(&agent).unwrap();
assert_eq!(score.level, ConfidenceLevel::High); assert!(!score.is_provisional); assert_eq!(score.data_points, 80);
assert_eq!(score.components.prior_score, 65.0); assert_eq!(score.components.empirical_score, 80.0); assert_eq!(score.components.confidence_level, ConfidenceLevel::High);
assert!(score.components.confidence_value > 0.7);
assert_eq!(score.components.prior_breakdown.base_score, 50.0);
assert_eq!(score.components.prior_breakdown.mcp_bonus, 10.0);
assert_eq!(score.components.prior_breakdown.identity_bonus, 5.0);
assert_eq!(score.components.prior_breakdown.total, 65.0);
}
#[test]
fn test_provisional_score() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
agent.total_interactions = 2;
agent.total_reviews = 2;
agent.positive_reviews = 1;
agent.negative_reviews = 1;
agent.average_rating = Some(3.0);
let score = calc.calculate(&agent).unwrap();
assert!(score.is_provisional);
assert_eq!(score.level, ConfidenceLevel::Low);
assert!(score.confidence < 0.2);
}
#[test]
fn test_confidence_level_boundaries() {
let calc = Calculator::default();
let mut agent = create_valid_agent();
agent.total_interactions = 3; agent.total_reviews = 3;
agent.positive_reviews = 2;
agent.negative_reviews = 1;
agent.average_rating = Some(4.0);
let score = calc.calculate(&agent).unwrap();
assert_eq!(score.level, ConfidenceLevel::Low);
agent.total_interactions = 15; agent.total_reviews = 15;
agent.positive_reviews = 12;
agent.negative_reviews = 3;
let score = calc.calculate(&agent).unwrap();
assert_eq!(score.level, ConfidenceLevel::Medium);
agent.total_interactions = 50; agent.total_reviews = 40;
agent.positive_reviews = 32;
agent.negative_reviews = 8;
let score = calc.calculate(&agent).unwrap();
assert_eq!(score.level, ConfidenceLevel::High);
}
#[test]
fn test_individual_bonus_independence() {
let calc = Calculator::default();
let base_agent = create_valid_agent();
let base_score = calc.calculate(&base_agent).unwrap().score;
let mut agent = base_agent.clone();
agent.identity_verified = true;
let score = calc.calculate(&agent).unwrap().score;
assert_eq!(score - base_score, 5.0, "Identity bonus should be 5");
let mut agent = base_agent.clone();
agent.security_audit_passed = true;
let score = calc.calculate(&agent).unwrap().score;
assert_eq!(score - base_score, 7.0, "Security audit bonus should be 7");
let mut agent = base_agent.clone();
agent.open_source = true;
let score = calc.calculate(&agent).unwrap().score;
assert_eq!(score - base_score, 3.0, "Open source bonus should be 3");
let mut agent = base_agent.clone();
agent.mcp_level = Some(1);
let score = calc.calculate(&agent).unwrap().score;
assert_eq!(score - base_score, 5.0, "MCP level 1 bonus should be 5");
let mut agent = base_agent.clone();
agent.created_at = Utc::now() - Duration::days(400);
let score = calc.calculate(&agent).unwrap().score;
assert_eq!(score - base_score, 5.0, "Age bonus should be 5");
}
}