use anyhow::Result;
use debtmap::risk::context::{
AnalysisTarget, Context, ContextAggregator, ContextDetails, ContextMap, ContextProvider,
ContextualRisk, Impact, Priority,
};
use std::path::PathBuf;
struct MockProvider {
name: String,
weight: f64,
should_fail: bool,
}
impl ContextProvider for MockProvider {
fn name(&self) -> &str {
&self.name
}
fn gather(&self, _target: &AnalysisTarget) -> Result<Context> {
if self.should_fail {
anyhow::bail!("Mock provider failure")
}
Ok(Context {
provider: self.name.clone(),
weight: self.weight,
contribution: 0.5,
details: ContextDetails::Business {
priority: Priority::Medium,
impact: Impact::UserExperience,
annotations: vec!["test".to_string()],
},
})
}
fn weight(&self) -> f64 {
self.weight
}
fn explain(&self, _context: &Context) -> String {
format!("{} explanation", self.name)
}
}
#[test]
fn test_context_aggregator_default() {
let aggregator = ContextAggregator::default();
let target = AnalysisTarget {
root_path: PathBuf::from("/project"),
file_path: PathBuf::from("src/lib.rs"),
function_name: "test_func".to_string(),
line_range: (1, 10),
reference_time: chrono::Utc::now(),
};
let context_map = aggregator.analyze(&target);
assert_eq!(context_map.total_contribution(), 0.0);
}
#[test]
fn test_context_aggregator_new() {
let aggregator = ContextAggregator::new();
let target = AnalysisTarget {
root_path: PathBuf::from("/project"),
file_path: PathBuf::from("src/lib.rs"),
function_name: "test_func".to_string(),
line_range: (1, 10),
reference_time: chrono::Utc::now(),
};
let context_map = aggregator.analyze(&target);
assert_eq!(context_map.total_contribution(), 0.0);
}
#[test]
fn test_context_aggregator_with_provider() {
let provider = Box::new(MockProvider {
name: "test_provider".to_string(),
weight: 1.0,
should_fail: false,
});
let aggregator = ContextAggregator::new().with_provider(provider);
let target = AnalysisTarget {
root_path: PathBuf::from("/project"),
file_path: PathBuf::from("src/lib.rs"),
function_name: "test_func".to_string(),
line_range: (1, 10),
reference_time: chrono::Utc::now(),
};
let context_map = aggregator.analyze(&target);
assert!(context_map.get("test_provider").is_some());
assert_eq!(context_map.total_contribution(), 0.5); }
#[test]
fn test_context_aggregator_with_failing_provider() {
let provider = Box::new(MockProvider {
name: "failing_provider".to_string(),
weight: 1.0,
should_fail: true,
});
let aggregator = ContextAggregator::new().with_provider(provider);
let target = AnalysisTarget {
root_path: PathBuf::from("/project"),
file_path: PathBuf::from("src/lib.rs"),
function_name: "test_func".to_string(),
line_range: (1, 10),
reference_time: chrono::Utc::now(),
};
let context_map = aggregator.analyze(&target);
assert!(context_map.get("failing_provider").is_none());
assert_eq!(context_map.total_contribution(), 0.0);
}
#[test]
fn test_context_aggregator_cache() {
let provider = Box::new(MockProvider {
name: "cached_provider".to_string(),
weight: 1.0,
should_fail: false,
});
let aggregator = ContextAggregator::new().with_provider(provider);
let target = AnalysisTarget {
root_path: PathBuf::from("/project"),
file_path: PathBuf::from("src/lib.rs"),
function_name: "test_func".to_string(),
line_range: (1, 10),
reference_time: chrono::Utc::now(),
};
let context_map1 = aggregator.analyze(&target);
let context_map2 = aggregator.analyze(&target);
assert_eq!(
context_map1.total_contribution(),
context_map2.total_contribution()
);
}
#[test]
fn test_context_aggregator_clear_cache() {
let provider = Box::new(MockProvider {
name: "cache_clear_provider".to_string(),
weight: 1.0,
should_fail: false,
});
let aggregator = ContextAggregator::new().with_provider(provider);
let target = AnalysisTarget {
root_path: PathBuf::from("/project"),
file_path: PathBuf::from("src/lib.rs"),
function_name: "test_func".to_string(),
line_range: (1, 10),
reference_time: chrono::Utc::now(),
};
let _ = aggregator.analyze(&target);
aggregator.clear_cache();
let context_map = aggregator.analyze(&target);
assert!(context_map.get("cache_clear_provider").is_some());
}
#[test]
fn test_context_map_default() {
let context_map = ContextMap::default();
assert_eq!(context_map.total_contribution(), 0.0);
assert!(context_map.get("nonexistent").is_none());
}
#[test]
fn test_context_map_new() {
let context_map = ContextMap::new();
assert_eq!(context_map.total_contribution(), 0.0);
assert!(context_map.get("nonexistent").is_none());
}
#[test]
fn test_context_map_add_and_get() {
let mut context_map = ContextMap::new();
let context = Context {
provider: "test".to_string(),
weight: 2.0,
contribution: 0.5,
details: ContextDetails::Historical {
change_frequency: 0.3,
bug_density: 0.1,
age_days: 100,
author_count: 3,
total_commits: 5,
bug_fix_count: 0,
},
};
context_map.add("test".to_string(), context.clone());
assert!(context_map.get("test").is_some());
assert_eq!(context_map.get("test").unwrap().provider, "test");
assert_eq!(context_map.total_contribution(), 1.0); }
#[test]
fn test_context_map_total_contribution() {
let mut context_map = ContextMap::new();
context_map.add(
"provider1".to_string(),
Context {
provider: "provider1".to_string(),
weight: 1.0,
contribution: 0.5,
details: ContextDetails::CriticalPath {
entry_points: vec!["main".to_string()],
path_weight: 0.8,
is_user_facing: true,
},
},
);
context_map.add(
"provider2".to_string(),
Context {
provider: "provider2".to_string(),
weight: 2.0,
contribution: 0.3,
details: ContextDetails::DependencyChain {
depth: 3,
propagated_risk: 0.7,
dependents: vec!["mod1".to_string()],
blast_radius: 5,
},
},
);
assert_eq!(context_map.total_contribution(), 1.1);
}
#[test]
fn test_context_map_iter() {
let mut context_map = ContextMap::new();
context_map.add(
"provider1".to_string(),
Context {
provider: "provider1".to_string(),
weight: 1.0,
contribution: 0.5,
details: ContextDetails::Business {
priority: Priority::High,
impact: Impact::Revenue,
annotations: vec!["critical".to_string()],
},
},
);
let items: Vec<_> = context_map.iter().collect();
assert_eq!(items.len(), 1);
assert_eq!(items[0].0, "provider1");
}
#[test]
fn test_contextual_risk_new() {
let mut context_map = ContextMap::new();
context_map.add(
"test".to_string(),
Context {
provider: "test".to_string(),
weight: 1.0,
contribution: 0.2,
details: ContextDetails::Business {
priority: Priority::Low,
impact: Impact::UserExperience,
annotations: vec![],
},
},
);
let risk = ContextualRisk::new(5.0, &context_map);
assert_eq!(risk.base_risk, 5.0);
assert_eq!(risk.contextual_risk, 6.0);
assert_eq!(risk.contexts.len(), 1);
assert!(risk.explanation.contains("Base risk: 5.0"));
assert!(risk.explanation.contains("test: +0.2"));
}
#[test]
fn test_contextual_risk_with_cap() {
let mut context_map = ContextMap::new();
context_map.add(
"high_impact".to_string(),
Context {
provider: "high_impact".to_string(),
weight: 2.0,
contribution: 2.0,
details: ContextDetails::Business {
priority: Priority::Critical,
impact: Impact::Security,
annotations: vec!["security-critical".to_string()],
},
},
);
let risk = ContextualRisk::new(8.0, &context_map);
assert_eq!(risk.base_risk, 8.0);
assert_eq!(risk.contextual_risk, 24.0);
}
#[test]
fn test_priority_equality() {
assert_eq!(Priority::Critical, Priority::Critical);
assert_ne!(Priority::Critical, Priority::High);
assert_eq!(Priority::Medium, Priority::Medium);
assert_ne!(Priority::Low, Priority::High);
}
#[test]
fn test_impact_equality() {
assert_eq!(Impact::Revenue, Impact::Revenue);
assert_ne!(Impact::Revenue, Impact::Security);
assert_eq!(Impact::Compliance, Impact::Compliance);
assert_ne!(Impact::Security, Impact::UserExperience);
}