use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NodeMetrics {
pub fingerprint: String,
pub in_degree: i64,
pub out_degree: i64,
pub total_degree: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CentralityMetrics {
pub fingerprint: String,
pub betweenness_centrality: Option<f64>,
pub closeness_centrality: Option<f64>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct CentralityAnalysisResult {
pub centrality_metrics: Vec<CentralityMetrics>,
pub total_nodes_analyzed: Option<usize>,
pub betweenness_distribution: Option<CentralityDistribution>,
pub closeness_distribution: Option<CentralityDistribution>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CentralityDistribution {
pub min: f64,
pub max: f64,
pub mean: f64,
pub p50: f64,
pub p75: f64,
pub p90: f64,
pub p95: f64,
pub p99: f64,
pub p999: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PathResult {
pub source_fingerprint: String,
pub target_fingerprint: String,
pub path_exists: bool,
pub path_length: Option<usize>,
pub path_cost: Option<f64>,
pub intermediate_nodes: Option<Vec<String>>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct PathAnalysisResult {
pub path_results: Vec<PathResult>,
pub total_paths_analyzed: Option<usize>,
pub connected_community_pairs: Option<usize>,
pub disconnected_community_pairs: Option<usize>,
pub average_path_length: Option<f64>,
pub max_path_length: Option<usize>,
pub min_path_length: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CommunityConnectivity {
pub source_community_id: String,
pub target_community_id: String,
pub connection_strength: f64,
pub shortest_path_length: Option<usize>,
pub number_of_connecting_paths: usize,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct GraphMetrics {
pub node_count: Option<i64>,
pub relationship_count: Option<i64>,
pub degree_distribution: Option<HashMap<i64, i64>>,
pub average_degree: Option<f64>,
pub max_degree: Option<i64>,
pub min_degree: Option<i64>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_metrics_creation() {
let node = NodeMetrics {
fingerprint: "RELAY001".to_string(),
in_degree: 5,
out_degree: 3,
total_degree: 8,
};
assert_eq!(node.fingerprint, "RELAY001");
assert_eq!(node.in_degree, 5);
assert_eq!(node.out_degree, 3);
assert_eq!(node.total_degree, 8);
}
#[test]
fn test_centrality_metrics_creation() {
let centrality = CentralityMetrics {
fingerprint: "RELAY001".to_string(),
betweenness_centrality: Some(0.75),
closeness_centrality: Some(0.85),
};
assert_eq!(centrality.fingerprint, "RELAY001");
assert_eq!(centrality.betweenness_centrality, Some(0.75));
assert_eq!(centrality.closeness_centrality, Some(0.85));
}
#[test]
fn test_centrality_metrics_partial() {
let centrality = CentralityMetrics {
fingerprint: "RELAY002".to_string(),
betweenness_centrality: Some(0.45),
closeness_centrality: None,
};
assert_eq!(centrality.fingerprint, "RELAY002");
assert_eq!(centrality.betweenness_centrality, Some(0.45));
assert!(centrality.closeness_centrality.is_none());
}
#[test]
fn test_centrality_distribution_creation() {
let distribution = CentralityDistribution {
min: 0.0,
max: 1.0,
mean: 0.5,
p50: 0.45,
p75: 0.65,
p90: 0.80,
p95: 0.90,
p99: 0.95,
p999: 0.99,
};
assert_eq!(distribution.min, 0.0);
assert_eq!(distribution.max, 1.0);
assert_eq!(distribution.mean, 0.5);
assert_eq!(distribution.p99, 0.95);
}
#[test]
fn test_centrality_analysis_result_default() {
let result = CentralityAnalysisResult::default();
assert!(result.centrality_metrics.is_empty());
assert!(result.total_nodes_analyzed.is_none());
assert!(result.betweenness_distribution.is_none());
assert!(result.closeness_distribution.is_none());
}
#[test]
fn test_path_result_creation() {
let path = PathResult {
source_fingerprint: "SOURCE001".to_string(),
target_fingerprint: "TARGET001".to_string(),
path_exists: true,
path_length: Some(3),
path_cost: Some(150.0),
intermediate_nodes: Some(vec![
"NODE1".to_string(),
"NODE2".to_string(),
]),
};
assert_eq!(path.source_fingerprint, "SOURCE001");
assert_eq!(path.target_fingerprint, "TARGET001");
assert!(path.path_exists);
assert_eq!(path.path_length, Some(3));
assert_eq!(path.path_cost, Some(150.0));
assert_eq!(path.intermediate_nodes.as_ref().unwrap().len(), 2);
}
#[test]
fn test_path_result_no_path() {
let path = PathResult {
source_fingerprint: "SOURCE002".to_string(),
target_fingerprint: "TARGET002".to_string(),
path_exists: false,
path_length: None,
path_cost: None,
intermediate_nodes: None,
};
assert_eq!(path.source_fingerprint, "SOURCE002");
assert_eq!(path.target_fingerprint, "TARGET002");
assert!(!path.path_exists);
assert!(path.path_length.is_none());
assert!(path.path_cost.is_none());
assert!(path.intermediate_nodes.is_none());
}
#[test]
fn test_path_analysis_result_default() {
let result = PathAnalysisResult::default();
assert!(result.path_results.is_empty());
assert!(result.total_paths_analyzed.is_none());
assert!(result.connected_community_pairs.is_none());
assert!(result.disconnected_community_pairs.is_none());
assert!(result.average_path_length.is_none());
assert!(result.max_path_length.is_none());
assert!(result.min_path_length.is_none());
}
#[test]
fn test_node_metrics_total_degree_calculation() {
let node = NodeMetrics {
fingerprint: "TEST".to_string(),
in_degree: 10,
out_degree: 15,
total_degree: 25,
};
assert_eq!(node.total_degree, 25);
assert_eq!(node.in_degree + node.out_degree, node.total_degree);
}
#[test]
fn test_graph_metrics_default() {
let metrics = GraphMetrics::default();
assert!(metrics.node_count.is_none());
assert!(metrics.relationship_count.is_none());
assert!(metrics.degree_distribution.is_none());
assert!(metrics.average_degree.is_none());
assert!(metrics.max_degree.is_none());
assert!(metrics.min_degree.is_none());
assert!(metrics.node_count.is_none());
assert!(metrics.relationship_count.is_none());
}
#[test]
fn test_graph_metrics_fields_access() {
let metrics = GraphMetrics {
node_count: Some(10),
relationship_count: Some(25),
degree_distribution: Some(HashMap::new()),
average_degree: Some(2.5),
max_degree: Some(5),
min_degree: Some(1),
};
assert_eq!(metrics.node_count.unwrap_or(0), 10);
assert_eq!(metrics.relationship_count.unwrap_or(0), 25);
assert_eq!(metrics.average_degree.unwrap_or(0.0), 2.5);
assert_eq!(metrics.max_degree.unwrap_or(0), 5);
assert_eq!(metrics.min_degree.unwrap_or(0), 1);
}
#[test]
fn test_degree_distribution() {
let mut degree_dist = HashMap::new();
degree_dist.insert(1, 5); degree_dist.insert(2, 3); degree_dist.insert(5, 1); let metrics = GraphMetrics {
node_count: Some(9), relationship_count: Some(16), degree_distribution: Some(degree_dist.clone()),
average_degree: Some(16.0 / 9.0),
max_degree: Some(5),
min_degree: Some(1),
};
assert!(metrics.node_count.is_some());
assert!(metrics.relationship_count.is_some());
assert_eq!(metrics.degree_distribution.unwrap(), degree_dist);
}
#[test]
fn test_node_metrics_equality() {
let node1 = NodeMetrics {
fingerprint: "RELAY001".to_string(),
in_degree: 5,
out_degree: 3,
total_degree: 8,
};
let node2 = NodeMetrics {
fingerprint: "RELAY001".to_string(),
in_degree: 5,
out_degree: 3,
total_degree: 8,
};
let node3 = NodeMetrics {
fingerprint: "RELAY002".to_string(),
in_degree: 5,
out_degree: 3,
total_degree: 8,
};
assert_eq!(node1, node2);
assert_ne!(node1, node3);
}
#[test]
fn test_graph_metrics_equality() {
let metrics1 = GraphMetrics {
node_count: Some(10),
relationship_count: Some(20),
degree_distribution: None,
average_degree: Some(2.0),
max_degree: Some(5),
min_degree: Some(1),
};
let metrics2 = GraphMetrics {
node_count: Some(10),
relationship_count: Some(20),
degree_distribution: None,
average_degree: Some(2.0),
max_degree: Some(5),
min_degree: Some(1),
};
assert_eq!(metrics1, metrics2);
}
}