use std::env;
use std::path::Path;
use geoipsed::geoip::GeoIPSed;
use geoipsed::{Extractor, ExtractorBuilder, Tag, Tagged};
use termcolor::ColorChoice;
fn setup_test_env() {
let test_dir = Path::new("tests/maxmind");
assert!(
test_dir.exists(),
"Test directory 'tests/maxmind' must exist"
);
let asn_file = test_dir.join("GeoLite2-ASN.mmdb");
let city_file = test_dir.join("GeoLite2-City.mmdb");
assert!(asn_file.exists(), "Test file GeoLite2-ASN.mmdb must exist");
assert!(
city_file.exists(),
"Test file GeoLite2-City.mmdb must exist"
);
}
fn create_test_extractor() -> Extractor {
ExtractorBuilder::new()
.ipv4(true)
.ipv6(true)
.private_ips(true)
.loopback_ips(true)
.broadcast_ips(true)
.build()
.expect("Failed to build extractor")
}
fn create_test_geoipsed() -> GeoIPSed {
let test_dir = Path::new("tests/maxmind");
let geoipsed = GeoIPSed::new(
Some(test_dir.to_str().unwrap().into()),
None,
ColorChoice::Never,
false,
)
.expect("Failed to create GeoIPSed instance");
assert!(test_dir.join("GeoLite2-ASN.mmdb").exists());
assert!(test_dir.join("GeoLite2-City.mmdb").exists());
geoipsed
}
#[test]
fn test_basic_ipv4_lookup() {
setup_test_env();
let geoipsed = create_test_geoipsed();
let test_ip = "67.43.156.1";
let ip = test_ip.parse().unwrap();
let result = geoipsed.lookup(ip, test_ip);
assert_eq!(result, "<67.43.156.1|AS35908_|BT|>");
}
#[test]
fn test_basic_ipv6_lookup() {
setup_test_env();
let geoipsed = create_test_geoipsed();
let test_ip = "240b::beef:0:24";
let ip = test_ip.parse().unwrap();
let result = geoipsed.lookup(ip, test_ip);
assert_eq!(result, "<240b::beef:0:24|AS2516_KDDI_KDDI_CORPORATION||>");
}
#[test]
fn test_template_customization() {
setup_test_env();
env::set_var("TEST_TIMEZONE_TEMPLATE", "1");
let test_dir = Path::new("tests/maxmind");
let geoipsed = GeoIPSed::new(
Some(test_dir.to_str().unwrap().into()),
Some("testing {ip}@{timezone}".to_string()),
ColorChoice::Never,
false,
)
.expect("Failed to create GeoIPSed instance");
let test_ip = "81.2.69.205";
let ip = test_ip.parse().unwrap();
let result = geoipsed.lookup(ip, test_ip);
if result != "testing_81.2.69.205@Europe/London" {
assert!(
result.contains("testing")
&& result.contains("81.2.69.205")
&& (result.contains("Europe/London") || result.contains("GB")),
"Expected timezone or country info in result, got: {}",
result
);
}
}
#[test]
fn test_tagged_text() {
setup_test_env();
let extractor = create_test_extractor();
let geoipsed = create_test_geoipsed();
let input = "hello 67.43.156.1 world";
let bytes = input.as_bytes();
let mut tagged = Tagged::new(bytes);
for range in extractor.find_iter(bytes) {
let ip_str = std::str::from_utf8(&bytes[range.clone()]).unwrap();
let ip = ip_str.parse().unwrap();
let decorated = geoipsed.lookup(ip, ip_str);
tagged = tagged.tag(
Tag::new(ip_str, ip_str)
.with_range(range)
.with_decoration(decorated),
);
}
let mut output = Vec::new();
tagged
.write(&mut output)
.expect("Failed to write tagged output");
let output_str = String::from_utf8(output).expect("Failed to convert output to string");
assert_eq!(output_str, "hello <67.43.156.1|AS35908_|BT|> world");
}
#[test]
fn test_multiple_ips() {
env::remove_var("TEST_TIMEZONE_TEMPLATE");
let extractor = create_test_extractor();
let geoipsed = create_test_geoipsed();
let input = "IP1: 81.2.69.205, IP2: 175.16.199.37";
let bytes = input.as_bytes();
let mut tagged = Tagged::new(bytes);
for range in extractor.find_iter(bytes) {
let ip_str = std::str::from_utf8(&bytes[range.clone()]).unwrap();
let ip = ip_str.parse().unwrap();
let decorated = geoipsed.lookup(ip, ip_str);
tagged = tagged.tag(
Tag::new(ip_str, ip_str)
.with_range(range)
.with_decoration(decorated),
);
}
let mut output = Vec::new();
tagged
.write(&mut output)
.expect("Failed to write tagged output");
let output_str = String::from_utf8(output).expect("Failed to convert output to string");
let expected_standard =
"IP1: <81.2.69.205|AS0_|GB|London>, IP2: <175.16.199.37|AS0_|CN|Changchun>";
let expected_timezone =
"IP1: testing_81.2.69.205@Europe/London, IP2: testing_175.16.199.52@Asia/Harbin";
assert!(
output_str == expected_standard || output_str == expected_timezone,
"Expected one of:\n{}\nOR\n{}\n\nGot:\n{}",
expected_standard,
expected_timezone,
output_str
);
}
#[test]
fn test_invalid_ip() {
setup_test_env();
let extractor = create_test_extractor();
let input = "hello 999.999.999.999 world";
let bytes = input.as_bytes();
let matches: Vec<_> = extractor.find_iter(bytes).collect();
assert!(matches.is_empty());
}
#[test]
fn test_only_routable_filters_non_routable_ips() {
setup_test_env();
let test_dir = Path::new("tests/maxmind");
let geoipsed_routable = GeoIPSed::new(
Some(test_dir.to_str().unwrap().into()),
None,
ColorChoice::Never,
true, )
.expect("Failed to create GeoIPSed instance with only_routable");
let ip_routable = "67.43.156.1".parse().unwrap();
let result_routable = geoipsed_routable.lookup(ip_routable, "67.43.156.1");
assert!(
result_routable.contains("AS35908"),
"Expected routable IP to be decorated, got: {}",
result_routable
);
let ip_non_routable = "175.16.199.37".parse().unwrap();
let result_non_routable = geoipsed_routable.lookup(ip_non_routable, "175.16.199.37");
assert_eq!(
result_non_routable, "175.16.199.37",
"Expected non-routable IP to be returned undecorated"
);
}
#[test]
fn test_only_routable_ipv6() {
setup_test_env();
let test_dir = Path::new("tests/maxmind");
let geoipsed_routable = GeoIPSed::new(
Some(test_dir.to_str().unwrap().into()),
None,
ColorChoice::Never,
true, )
.expect("Failed to create GeoIPSed instance");
let ip_routable = "240b::beef:0:24".parse().unwrap();
let result_routable = geoipsed_routable.lookup(ip_routable, "240b::beef:0:24");
assert!(
result_routable.contains("AS2516"),
"Expected routable IPv6 to be decorated, got: {}",
result_routable
);
let ip_non_routable = "2001:480::52".parse().unwrap();
let result_non_routable = geoipsed_routable.lookup(ip_non_routable, "2001:480::52");
assert_eq!(
result_non_routable, "2001:480::52",
"Expected non-routable IPv6 to be returned undecorated"
);
}
#[test]
fn test_without_only_routable_shows_all_ips() {
setup_test_env();
let test_dir = Path::new("tests/maxmind");
let geoipsed_no_filter = GeoIPSed::new(
Some(test_dir.to_str().unwrap().into()),
None,
ColorChoice::Never,
false, )
.expect("Failed to create GeoIPSed instance");
let ip_routable = "67.43.156.1".parse().unwrap();
let result_routable = geoipsed_no_filter.lookup(ip_routable, "67.43.156.1");
assert!(
result_routable.contains("AS35908"),
"Expected routable IP to be decorated"
);
let ip_non_routable = "175.16.199.37".parse().unwrap();
let result_non_routable = geoipsed_no_filter.lookup(ip_non_routable, "175.16.199.37");
assert!(
result_non_routable.contains("AS0"),
"Expected non-routable IP to still be decorated when only_routable=false, got: {}",
result_non_routable
);
}
#[test]
fn test_only_routable_with_custom_template() {
setup_test_env();
let test_dir = Path::new("tests/maxmind");
let geoipsed_routable = GeoIPSed::new(
Some(test_dir.to_str().unwrap().into()),
Some("{ip}:{asnnum}".to_string()),
ColorChoice::Never,
true, )
.expect("Failed to create GeoIPSed instance");
let ip_routable = "67.43.156.1".parse().unwrap();
let result = geoipsed_routable.lookup(ip_routable, "67.43.156.1");
assert!(
result.contains("67.43.156.1:35908"),
"Expected custom template to be applied for routable IP, got: {}",
result
);
let ip_non_routable = "175.16.199.37".parse().unwrap();
let result = geoipsed_routable.lookup(ip_non_routable, "175.16.199.37");
assert_eq!(
result, "175.16.199.37",
"Expected non-routable IP to bypass template"
);
}