use proptest::prelude::*;
use crate::optimizer::{RouteOptimizer, types::Node, types::Way};
#[proptest]
fn prop_eulerian_circuit_is_connected(
node_count in 2..20usize,
) {
let mut optimizer = RouteOptimizer::new();
let nodes: Vec<_> = (0..node_count)
.map(|i| Node::new(format!("node{}", i), 45.0 + (i as f64) * 0.01, -73.0 - (i as f64) * 0.01))
.collect();
let ways: Vec<_> = nodes.windows(2)
.enumerate()
.map(|(i, window)| {
Way::new(
format!("way{}", i),
vec![window[0].id.clone(), window[1].id.clone()]
)
})
.collect();
prop_assert_eq!(nodes.len(), node_count);
prop_assert_eq!(ways.len(), node_count.saturating_sub(1));
}
#[proptest]
fn prop_all_edges_in_graph(
node_count in 3..20usize,
) {
let nodes: Vec<_> = (0..node_count)
.map(|i| Node::new(format!("node{}", i), 45.0 + (i as f64) * 0.01, -73.0 - (i as f64) * 0.01))
.collect();
let ways: Vec<_> = nodes.windows(2)
.enumerate()
.map(|(i, window)| {
Way::new(
format!("way{}", i),
vec![window[0].id.clone(), window[1].id.clone()]
)
})
.collect();
prop_assert_eq!(ways.len(), node_count.saturating_sub(1));
}
#[proptest]
fn prop_distance_is_symmetric(
lat1 in -90.0f64..90.0,
lon1 in -180.0f64..180.0,
lat2 in -90.0f64..90.0,
lon2 in -180.0f64..180.0,
) {
let node1 = Node::new("", lat1, lon1);
let node2 = Node::new("", lat2, lon2);
let dist1 = node1.distance_to(&node2);
let dist2 = node2.distance_to(&node1);
prop_assert!((dist1 - dist2).abs() < 1e-6,
"Distance calculation should be symmetric: {} vs {}", dist1, dist2);
}
#[proptest]
fn prop_distance_to_self_is_zero(
lat in -90.0f64..90.0,
lon in -180.0f64..180.0,
) {
let node = Node::new("", lat, lon);
let distance = node.distance_to(&node);
prop_assert!((distance - 0.0).abs() < 1e-6,
"Distance to self should be zero: {}", distance);
}
#[proptest]
fn prop_bearing_returns_valid_angle(
lat1 in -90.0f64..90.0,
lon1 in -180.0f64..180.0,
lat2 in -90.0f64..90.0,
lon2 in -180.0f64..180.0,
) {
let node1 = Node::new("", lat1, lon1);
let node2 = Node::new("", lat2, lon2);
let bearing = node1.bearing_to(&node2);
prop_assert!(bearing >= 0.0 && bearing < 360.0,
"Bearing should be in [0, 360): {}", bearing);
}
#[proptest]
fn prop_bearing_opposite_reverse(
lat1 in -80.0f64..80.0,
lon1 in -170.0f64..170.0,
lat2 in -80.0f64..80.0,
lon2 in -170.0f64..170.0,
) {
let node1 = Node::new("", lat1, lon1);
let node2 = Node::new("", lat2, lon2);
let bearing1 = node1.bearing_to(&node2);
let bearing2 = node2.bearing_to(&node1);
let expected_reverse = (bearing1 + 180.0) % 360.0;
let diff = (bearing2 - expected_reverse).abs().min(360.0 - (bearing2 - expected_reverse).abs());
prop_assert!(diff < 10.0,
"Reverse bearing should be opposite: {} vs {} (expected: {})",
bearing2, bearing1, expected_reverse);
}
#[proptest]
fn prop_node_equality(
id in "[a-z]{5,10}",
lat in -90.0f64..90.0,
lon in -180.0f64..180.0,
) {
let node1 = Node::new(&id, lat, lon);
let node2 = Node::new(&id, lat, lon);
prop_assert_eq!(node1.id, node2.id);
prop_assert_eq!(node1.lat, node2.lat);
prop_assert_eq!(node1.lon, node2.lon);
}
#[proptest]
fn prop_way_with_tags(
id in "[a-z]{3,8}",
node_count in 2..5usize,
tag_key in "[a-z]{3,8}",
tag_value in "[a-z0-9]{3,10}",
) {
let nodes: Vec<String> = (0..node_count)
.map(|i| format!("node{}", i))
.collect();
let way = Way::new(&id, nodes.clone())
.with_tag(&tag_key, &tag_value);
prop_assert_eq!(way.id, id);
prop_assert_eq!(way.nodes.len(), node_count);
prop_assert!(way.tags.contains_key(&tag_key));
prop_assert_eq!(way.tags.get(&tag_key), Some(&tag_value.to_string()));
}
#[proptest]
fn prop_route_point_from_node(
id in "[a-z0-9]{3,10}",
lat in -90.0f64..90.0,
lon in -180.0f64..180.0,
) {
let node = Node::new(&id, lat, lon);
let point: crate::optimizer::types::RoutePoint = node.clone().into();
prop_assert_eq!(point.latitude, node.lat);
prop_assert_eq!(point.longitude, node.lon);
prop_assert_eq!(point.node_id, Some(id));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_distance_between_known_points() {
let node1 = Node::new("", 40.7128, -74.0060); let node2 = Node::new("", 34.0522, -118.2437);
let distance = node1.distance_to(&node2);
assert!((distance - 3944000.0).abs() < 10000.0,
"Distance NYC to LA should be ~3944 km: {} m", distance);
}
#[test]
fn test_bearing_north() {
let node1 = Node::new("", 0.0, 0.0);
let node2 = Node::new("", 1.0, 0.0);
let bearing = node1.bearing_to(&node2);
assert!((bearing - 0.0).abs() < 1.0,
"Bearing due north should be ~0 degrees: {}", bearing);
}
#[test]
fn test_bearing_east() {
let node1 = Node::new("", 0.0, 0.0);
let node2 = Node::new("", 0.0, 1.0);
let bearing = node1.bearing_to(&node2);
assert!((bearing - 90.0).abs() < 1.0,
"Bearing due east should be ~90 degrees: {}", bearing);
}
}