#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use crate::*;
use clap::Parser;
use ftr::{ProbeProtocol, SocketMode};
use std::net::IpAddr;
#[test]
fn test_get_version() {
let version = get_version();
assert!(!version.is_empty());
#[cfg(debug_assertions)]
assert!(version.ends_with("-UNRELEASED"));
#[cfg(not(debug_assertions))]
assert!(!version.contains("UNRELEASED"));
}
#[tokio::test]
async fn test_resolve_target_ip_address() {
let result = resolve_target("8.8.8.8").await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "8.8.8.8".parse::<IpAddr>().unwrap());
let result = resolve_target("::1").await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "::1".parse::<IpAddr>().unwrap());
}
#[tokio::test]
async fn test_resolve_target_hostname() {
let result = resolve_target("localhost").await;
assert!(result.is_ok());
let ip = result.unwrap();
assert!(ip.is_loopback());
}
#[tokio::test]
async fn test_resolve_target_invalid() {
let result = resolve_target("this.domain.definitely.does.not.exist.invalid").await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Error resolving host"));
}
#[test]
fn test_display_json_results() {
use ftr::{ClassifiedHopInfo, IspInfo, SegmentType, TracerouteResult};
use std::net::Ipv4Addr;
use std::time::Duration;
let result = TracerouteResult {
target: "example.com".to_string(),
target_ip: IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)),
hops: vec![ClassifiedHopInfo {
ttl: 1,
segment: SegmentType::Lan,
hostname: Some("router.local".to_string()),
addr: Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))),
asn_info: None,
rtt: Some(Duration::from_millis(5)),
}],
isp_info: Some(IspInfo {
public_ip: IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),
asn: 12345,
name: "Test ISP".to_string(),
hostname: None,
}),
destination_asn: None,
protocol_used: ftr::ProbeProtocol::Icmp,
socket_mode_used: ftr::SocketMode::Raw,
destination_reached: true,
total_duration: Duration::from_secs(1),
};
let json_result = display_json_results(result);
assert!(json_result.is_ok());
}
#[test]
fn test_display_text_results_with_enrichment() {
use ftr::{AsnInfo, ClassifiedHopInfo, SegmentType, TracerouteResult};
use std::net::Ipv4Addr;
use std::time::Duration;
let result = TracerouteResult {
target: "example.com".to_string(),
target_ip: IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)),
hops: vec![ClassifiedHopInfo {
ttl: 1,
segment: SegmentType::Lan,
hostname: Some("router.local".to_string()),
addr: Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))),
asn_info: Some(AsnInfo {
asn: 12345,
prefix: "192.168.0.0/16".to_string(),
country_code: "US".to_string(),
registry: "ARIN".to_string(),
name: "LOCAL-NET".to_string(),
}),
rtt: Some(Duration::from_millis(5)),
}],
isp_info: None,
destination_asn: None,
protocol_used: ftr::ProbeProtocol::Icmp,
socket_mode_used: ftr::SocketMode::Raw,
destination_reached: true,
total_duration: Duration::from_secs(1),
};
display_text_results(result, false, false);
}
#[test]
fn test_args_parsing() {
let args = Args::parse_from(["ftr", "google.com"]);
assert_eq!(args.host, "google.com");
assert_eq!(args.start_ttl, 1);
assert_eq!(args.max_hops, 30);
assert_eq!(args.probe_timeout_ms, 1000);
assert_eq!(args.queries, 1);
assert!(!args.json);
assert!(!args.no_enrich);
assert!(!args.no_rdns);
let args = Args::parse_from([
"ftr",
"example.com",
"--start-ttl",
"5",
"--max-hops",
"20",
"--queries",
"3",
"--json",
"--no-enrich",
"--port",
"80",
]);
assert_eq!(args.host, "example.com");
assert_eq!(args.start_ttl, 5);
assert_eq!(args.max_hops, 20);
assert_eq!(args.queries, 3);
assert!(args.json);
assert!(args.no_enrich);
assert_eq!(args.port, 80);
let args = Args::parse_from([
"ftr",
"test.com",
"--protocol",
"udp",
"--socket-mode",
"raw",
]);
assert!(matches!(args.protocol, Some(ProtocolArg::Udp)));
assert!(matches!(args.socket_mode, Some(SocketModeArg::Raw)));
}
#[test]
fn test_json_output_structure() {
use ftr::{AsnInfo, ClassifiedHopInfo, SegmentType, TracerouteResult};
use std::net::Ipv4Addr;
use std::time::Duration;
let result = TracerouteResult {
target: "example.com".to_string(),
target_ip: IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)),
hops: vec![
ClassifiedHopInfo {
ttl: 1,
segment: SegmentType::Lan,
hostname: Some("router.local".to_string()),
addr: Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))),
asn_info: None,
rtt: Some(Duration::from_millis(5)),
},
ClassifiedHopInfo {
ttl: 2,
segment: SegmentType::Isp,
hostname: None,
addr: Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))),
asn_info: Some(AsnInfo {
asn: 12345,
prefix: "10.0.0.0/8".to_string(),
country_code: "US".to_string(),
registry: "ARIN".to_string(),
name: "ISP".to_string(),
}),
rtt: Some(Duration::from_millis(15)),
},
],
isp_info: None,
destination_asn: None,
protocol_used: ftr::ProbeProtocol::Icmp,
socket_mode_used: ftr::SocketMode::Raw,
destination_reached: true,
total_duration: Duration::from_secs(1),
};
let json_result = display_json_results(result);
assert!(json_result.is_ok());
}
#[test]
fn test_json_hop_structure() {
let hop = JsonHop {
ttl: 5,
segment: Some("Isp".to_string()),
address: Some("8.8.8.8".to_string()),
hostname: Some("dns.google".to_string()),
asn_info: Some(ftr::AsnInfo {
asn: 15169,
prefix: "8.8.8.0/24".to_string(),
country_code: "US".to_string(),
registry: "ARIN".to_string(),
name: "GOOGLE".to_string(),
}),
rtt_ms: Some(25.5),
};
let json = serde_json::to_string(&hop);
assert!(json.is_ok());
let empty_hop = JsonHop {
ttl: 10,
segment: None,
address: None,
hostname: None,
asn_info: None,
rtt_ms: None,
};
let json = serde_json::to_string(&empty_hop);
assert!(json.is_ok());
}
#[test]
fn test_protocol_arg_conversion() {
let icmp = ProtocolArg::Icmp;
let udp = ProtocolArg::Udp;
let proto_icmp = match icmp {
ProtocolArg::Icmp => ProbeProtocol::Icmp,
ProtocolArg::Udp => ProbeProtocol::Udp,
};
assert_eq!(proto_icmp, ProbeProtocol::Icmp);
let proto_udp = match udp {
ProtocolArg::Icmp => ProbeProtocol::Icmp,
ProtocolArg::Udp => ProbeProtocol::Udp,
};
assert_eq!(proto_udp, ProbeProtocol::Udp);
}
#[test]
fn test_socket_mode_arg_conversion() {
let raw = SocketModeArg::Raw;
let dgram = SocketModeArg::Dgram;
let mode_raw = match raw {
SocketModeArg::Raw => SocketMode::Raw,
SocketModeArg::Dgram => SocketMode::Dgram,
};
assert_eq!(mode_raw, SocketMode::Raw);
let mode_dgram = match dgram {
SocketModeArg::Raw => SocketMode::Raw,
SocketModeArg::Dgram => SocketMode::Dgram,
};
assert_eq!(mode_dgram, SocketMode::Dgram);
}
#[test]
fn test_display_text_results_without_enrichment() {
use ftr::{ClassifiedHopInfo, SegmentType, TracerouteResult};
use std::net::Ipv4Addr;
use std::time::Duration;
let result = TracerouteResult {
target: "example.com".to_string(),
target_ip: IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)),
hops: vec![
ClassifiedHopInfo {
ttl: 1,
segment: SegmentType::Unknown,
hostname: None,
addr: Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))),
asn_info: None,
rtt: Some(Duration::from_millis(5)),
},
ClassifiedHopInfo {
ttl: 2,
segment: SegmentType::Unknown,
hostname: None,
addr: None, asn_info: None,
rtt: None,
},
],
isp_info: None,
destination_asn: None,
protocol_used: ftr::ProbeProtocol::Icmp,
socket_mode_used: ftr::SocketMode::Raw,
destination_reached: false,
total_duration: Duration::from_secs(1),
};
display_text_results(result, false, false);
}
}