use super::*;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::sync::Arc;
use tokio::sync::RwLock;
#[tokio::test]
async fn test_lookup_private_ip() {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
let ip: Ipv4Addr = "192.168.1.1".parse().expect("valid IP");
let result = lookup_asn_with_cache(ip, &cache).await;
assert!(result.is_ok());
let asn_info = result.expect("should succeed");
assert_eq!(asn_info.asn, 0);
assert_eq!(asn_info.name, "Private Network");
}
#[tokio::test]
async fn test_lookup_cgnat_ip() {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
let ip: Ipv4Addr = "100.64.0.1".parse().expect("valid IP");
let result = lookup_asn_with_cache(ip, &cache).await;
let asn_info = result.expect("should succeed");
assert_eq!(asn_info.asn, 0);
assert_eq!(asn_info.name, "Carrier Grade NAT");
}
#[tokio::test]
async fn test_lookup_loopback() {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
let ip: Ipv4Addr = "127.0.0.1".parse().expect("valid IP");
let result = lookup_asn_with_cache(ip, &cache).await;
let asn_info = result.expect("should succeed");
assert_eq!(asn_info.asn, 0);
assert_eq!(asn_info.name, "Loopback");
}
#[tokio::test]
async fn test_cache_usage() {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
let ip: Ipv4Addr = "10.0.0.1".parse().expect("valid IP");
let result1 = lookup_asn_with_cache(ip, &cache).await;
assert!(result1.is_ok());
let result2 = lookup_asn_with_cache(ip, &cache).await;
assert!(result2.is_ok());
assert_eq!(result1.expect("r1").name, result2.expect("r2").name);
}
#[tokio::test]
async fn test_special_ips() {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
let test_cases = vec![
("0.0.0.0", "Special Use"),
("169.254.1.1", "Special Use"),
("255.255.255.255", "Special Use"),
("198.51.100.1", "Special Use"),
];
for (ip_str, expected_name) in test_cases {
let ip: Ipv4Addr = ip_str.parse().expect("valid IP");
let result = lookup_asn_with_cache(ip, &cache).await;
assert!(result.is_ok(), "Failed for IP: {ip_str}");
let asn_info = result.expect("should succeed");
assert_eq!(asn_info.asn, 0);
assert_eq!(asn_info.name, expected_name, "Wrong name for IP: {ip_str}");
}
}
#[tokio::test]
async fn test_lookup_public_ip() {
let result = tokio::time::timeout(std::time::Duration::from_secs(30), async {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
let test_cases = vec![
("8.8.8.8", 15169u32, "GOOGLE"),
("1.1.1.1", 13335u32, "CLOUDFLARENET"),
];
for (ip_str, expected_asn, expected_name_prefix) in test_cases {
let ip: Ipv4Addr = ip_str.parse().expect("valid IP");
let result = lookup_asn_with_cache(ip, &cache).await;
assert!(result.is_ok(), "ASN lookup failed for {ip_str}: {result:?}");
let asn_info = result.expect("ASN lookup should succeed");
assert_eq!(asn_info.asn, expected_asn, "Wrong ASN for {ip_str}");
assert!(
asn_info.name.contains(expected_name_prefix),
"ASN name for {ip_str} doesn't contain '{expected_name_prefix}': got '{}'",
asn_info.name
);
assert!(!asn_info.prefix.is_empty());
assert!(!asn_info.country_code.is_empty());
}
})
.await;
if result.is_err() {
eprintln!("test_lookup_public_ip timed out (expected under coverage instrumentation)");
}
}
#[tokio::test]
async fn test_concurrent_lookups() {
use tokio::task::JoinSet;
let ips = vec![
Ipv4Addr::new(192, 168, 1, 1),
Ipv4Addr::new(10, 0, 0, 1),
Ipv4Addr::new(172, 16, 0, 1),
Ipv4Addr::new(127, 0, 0, 1),
];
let mut set = JoinSet::new();
for ip in ips {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
set.spawn(async move { lookup_asn_with_cache(ip, &cache).await });
}
let mut results = Vec::new();
while let Some(result) = set.join_next().await {
results.push(result.expect("task should not panic"));
}
assert_eq!(results.len(), 4);
for result in results {
assert!(result.is_ok());
}
}
#[tokio::test]
async fn test_cache_multiple_ips_same_prefix() {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
let ip1: Ipv4Addr = "192.168.1.1".parse().expect("valid IP");
let ip2: Ipv4Addr = "192.168.1.2".parse().expect("valid IP");
let result1 = lookup_asn_with_cache(ip1, &cache).await;
assert!(result1.is_ok());
let result2 = lookup_asn_with_cache(ip2, &cache).await;
assert!(result2.is_ok());
assert_eq!(result1.expect("r1").name, "Private Network");
assert_eq!(result2.expect("r2").name, "Private Network");
}
#[test]
fn test_origin6_query_name_full_expansion() {
let addr: Ipv6Addr = "2001:4860:4860::8888".parse().expect("valid IP");
assert_eq!(
origin6_query_name(&addr),
"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.origin6.asn.cymru.com"
);
}
#[test]
fn test_origin6_query_name_loopback() {
let addr: Ipv6Addr = "::1".parse().expect("valid IP");
assert_eq!(
origin6_query_name(&addr),
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.origin6.asn.cymru.com"
);
}
#[test]
fn test_parse_cymru_origin_txt() {
let origin = parse_cymru_origin_txt("15169 | 2001:4860::/32 | US | arin | 2005-03-14")
.expect("valid payload");
assert_eq!(origin.asn, 15169);
assert_eq!(origin.prefix, "2001:4860::/32");
assert_eq!(origin.country_code, "US");
assert_eq!(origin.registry, "arin");
let origin = parse_cymru_origin_txt("15169 36040 | 8.8.8.0/24 | US | arin | 2023-12-28")
.expect("valid payload");
assert_eq!(origin.asn, 15169);
assert!(matches!(
parse_cymru_origin_txt("15169 | 2001:4860::/32"),
Err(AsnLookupError::InvalidFormat)
));
}
#[test]
fn test_special_ipv6_classification() {
let cases: &[(&str, Option<&str>)] = &[
("::1", Some("Loopback")),
("::", Some("Special Use")),
("fe80::1", Some("Special Use")),
("febf:ffff::1", Some("Special Use")),
("fc00::1", Some("Private Network")),
("fd12:3456:789a::1", Some("Private Network")),
("2001:db8::1", Some("Special Use")),
("ff02::1", Some("Special Use")),
("2001:4860:4860::8888", None),
("2606:4700::1111", None),
("fe00::1", None), ("fec0::1", None), ("2001:db9::1", None), ];
for (addr_str, expected) in cases {
let addr: Ipv6Addr = addr_str.parse().expect("valid IP");
assert_eq!(
special_ipv6_name(&addr),
*expected,
"wrong classification for {addr_str}"
);
}
}
#[tokio::test]
async fn test_lookup_v6_special_ranges_no_network() {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
let cases: &[(&str, &str)] = &[
("::1", "Loopback"),
("::", "Special Use"),
("fe80::1", "Special Use"),
("fd00::1", "Private Network"),
("2001:db8::1", "Special Use"),
("ff02::1", "Special Use"),
];
for (addr_str, expected_name) in cases {
let addr: Ipv6Addr = addr_str.parse().expect("valid IP");
let info = lookup_asn_v6_with_cache(addr, &cache)
.await
.expect("special range lookup should not fail");
assert_eq!(info.asn, 0, "ASN should be 0 for {addr_str}");
assert_eq!(info.name, *expected_name, "wrong name for {addr_str}");
assert_eq!(info.prefix, format!("{addr}/128"));
}
}
#[tokio::test]
async fn test_lookup_v6_v4_mapped_defers_to_v4_path() {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
let addr: Ipv6Addr = "::ffff:192.168.1.1".parse().expect("valid IP");
let info = lookup_asn_v6_with_cache(addr, &cache)
.await
.expect("v4-mapped lookup should not fail");
assert_eq!(info.asn, 0);
assert_eq!(info.name, "Private Network");
assert_eq!(info.prefix, "192.168.1.1/32");
}
#[tokio::test]
async fn test_lookup_v6_uses_cached_prefix_no_network() {
let cache = Arc::new(RwLock::new(crate::asn::cache::AsnCache::new()));
{
let cache_write = cache.write().await;
cache_write.insert_ipv6(
"2001:4860::/32".parse().expect("valid prefix"),
AsnInfo {
asn: 15169,
prefix: "2001:4860::/32".to_string(),
country_code: "US".to_string(),
registry: "arin".to_string(),
name: "GOOGLE".to_string(),
},
);
}
let addr: Ipv6Addr = "2001:4860:4860::8888".parse().expect("valid IP");
let info = lookup_asn_v6_with_cache(addr, &cache)
.await
.expect("cached lookup should succeed");
assert_eq!(info.asn, 15169);
assert_eq!(info.name, "GOOGLE");
}
#[test]
fn test_error_display() {
let errors = vec![
AsnLookupError::DnsError("timeout".to_string()),
AsnLookupError::InvalidFormat,
AsnLookupError::NotFound,
];
for error in errors {
let error_str = error.to_string();
assert!(!error_str.is_empty());
}
}