use std::net::IpAddr;
use iftoprs::capture::parser;
use iftoprs::data::flow::Direction;
use iftoprs::util::format::{readable_size, sparkline};
#[test]
fn sparkline_only_emits_one_column_glyphs() {
let data: Vec<u64> = (0u64..=8).collect();
let out = sparkline(&data, data.len());
for ch in out.chars() {
let ok = ch == ' ' || ('\u{2581}'..='\u{2588}').contains(&ch);
assert!(
ok,
"sparkline emitted illegal glyph {:?} (U+{:04X})",
ch, ch as u32
);
}
let space_count = out.chars().filter(|&c| c == ' ').count();
let block_count = out.chars().filter(|&c| c != ' ').count();
assert_eq!(
out.len(),
space_count + 3 * block_count,
"sparkline byte length disagrees with 1-byte space + 3-byte block accounting: {:?}",
out
);
}
#[test]
fn sparkline_with_u64_max_top_block_never_panics_or_misindexes() {
let out = sparkline(&[u64::MAX], 1);
assert_eq!(out, "█", "expected single tallest block for [u64::MAX]");
let out = sparkline(&[1, u64::MAX], 2);
let chars: Vec<char> = out.chars().collect();
assert_eq!(chars.len(), 2);
assert_eq!(
chars[0], '\u{2581}',
"tiny value beside u64::MAX should render bottom block"
);
assert_eq!(chars[1], '\u{2588}', "u64::MAX should render top block");
}
#[test]
fn readable_size_does_not_panic_on_nonfinite_inputs() {
let nan_bytes = readable_size(f64::NAN, true);
assert!(
nan_bytes.contains("NaN"),
"expected NaN tag in output: {:?}",
nan_bytes
);
assert!(
nan_bytes.ends_with("TB"),
"NaN should fall through to TB branch: {:?}",
nan_bytes
);
let nan_bits = readable_size(f64::NAN, false);
assert!(
nan_bits.contains("NaN"),
"expected NaN tag in bit output: {:?}",
nan_bits
);
assert!(
nan_bits.ends_with("Tb"),
"NaN should fall through to Tb branch: {:?}",
nan_bits
);
let pos_inf = readable_size(f64::INFINITY, true);
assert!(
pos_inf.contains("inf") || pos_inf.contains("Inf") || pos_inf.contains("INF"),
"expected inf in output: {:?}",
pos_inf
);
assert!(pos_inf.ends_with("TB"));
let neg_inf = readable_size(f64::NEG_INFINITY, true);
assert!(!neg_inf.is_empty(), "neg-inf should not render empty");
}
#[test]
fn parse_raw_ipv4_tcp_with_no_room_for_ports_yields_zero_ports_no_panic() {
let mut raw = vec![0u8; 20];
raw[0] = 0x45; raw[2] = 0;
raw[3] = 20; raw[9] = 6; raw[12..16].copy_from_slice(&[10, 0, 0, 1]);
raw[16..20].copy_from_slice(&[10, 0, 0, 2]);
let result = parser::parse_raw(&raw, None).expect("legal IPv4 header alone should still parse");
assert_eq!(
result.key.src_port, 0,
"no port bytes available → src_port must be 0"
);
assert_eq!(
result.key.dst_port, 0,
"no port bytes available → dst_port must be 0"
);
assert_eq!(result.len, 20);
}
#[test]
fn parse_raw_ipv4_ihl_max_reads_ports_at_offset_sixty() {
let mut raw = vec![0u8; 60 + 4];
raw[0] = 0x4F; raw[2] = 0;
raw[3] = 64; raw[9] = 17; raw[12..16].copy_from_slice(&[10, 0, 0, 1]);
raw[16..20].copy_from_slice(&[10, 0, 0, 2]);
raw[60] = 0xAB;
raw[61] = 0xCD; raw[62] = 0x12;
raw[63] = 0x34;
let result = parser::parse_raw(&raw, None).expect("IHL=15 packet should parse");
let (lo, hi) = if result.key.src_port < result.key.dst_port {
(result.key.src_port, result.key.dst_port)
} else {
(result.key.dst_port, result.key.src_port)
};
assert_eq!(lo, 0x1234, "lower port should be 0x1234");
assert_eq!(hi, 0xABCD, "higher port should be 0xABCD");
}
#[test]
fn parse_ethernet_ipv6_udp_direction_reversal_produces_same_canonical_key() {
fn make(src: [u8; 16], dst: [u8; 16], src_port: u16, dst_port: u16) -> Vec<u8> {
let mut pkt = vec![0u8; 14 + 48];
pkt[12] = 0x86;
pkt[13] = 0xDD; pkt[14] = 0x60; pkt[18] = 0;
pkt[19] = 8; pkt[20] = 17; pkt[21] = 64;
pkt[22..38].copy_from_slice(&src);
pkt[38..54].copy_from_slice(&dst);
pkt[54..56].copy_from_slice(&src_port.to_be_bytes());
pkt[56..58].copy_from_slice(&dst_port.to_be_bytes());
pkt
}
let mut a_ip = [0u8; 16];
a_ip[0] = 0x20;
a_ip[1] = 0x01;
a_ip[15] = 0x01; let mut b_ip = [0u8; 16];
b_ip[0] = 0x20;
b_ip[1] = 0x01;
b_ip[15] = 0x02;
let forward = make(a_ip, b_ip, 5000, 53);
let reverse = make(b_ip, a_ip, 53, 5000);
let local: IpAddr = "2001::".parse().unwrap();
let f = parser::parse_ethernet(&forward, Some((local, 16))).unwrap();
let r = parser::parse_ethernet(&reverse, Some((local, 16))).unwrap();
assert_eq!(
f.key, r.key,
"canonical IPv6/UDP key must match under direction reversal"
);
assert_ne!(
f.direction, r.direction,
"wire reversal must produce opposite directions even when both endpoints are local"
);
assert_eq!(f.direction, Direction::Sent);
assert_eq!(r.direction, Direction::Received);
}