use assert_cmd::Command;
use tempfile::NamedTempFile;
use std::io::Write;
fn build_pcap(n: usize) -> Vec<u8> {
let mut pcap = Vec::new();
pcap.extend_from_slice(&0xA1B2C3D4u32.to_le_bytes());
pcap.extend_from_slice(&2u16.to_le_bytes()); pcap.extend_from_slice(&4u16.to_le_bytes()); pcap.extend_from_slice(&0i32.to_le_bytes()); pcap.extend_from_slice(&0u32.to_le_bytes()); pcap.extend_from_slice(&65535u32.to_le_bytes()); pcap.extend_from_slice(&1u32.to_le_bytes());
let pkt: &[u8] = &[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x08, 0x00, 0x45, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x40, 0x11, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x00, 0x02, 0x10, 0x00, 0x10, 0x01, 0x00, 0x08, 0x00, 0x00, ];
for i in 0..n {
let ts_sec = i as u32;
pcap.extend_from_slice(&ts_sec.to_le_bytes());
pcap.extend_from_slice(&0u32.to_le_bytes()); pcap.extend_from_slice(&(pkt.len() as u32).to_le_bytes());
pcap.extend_from_slice(&(pkt.len() as u32).to_le_bytes());
pcap.extend_from_slice(pkt);
}
pcap
}
fn write_pcap(n: usize) -> NamedTempFile {
let pcap = build_pcap(n);
let mut tmp = NamedTempFile::with_suffix(".pcap").unwrap();
tmp.write_all(&pcap).unwrap();
tmp
}
#[test]
fn read_single_packet_produces_valid_jsonl() {
let tmp = write_pcap(1);
let output = Command::cargo_bin("dsct")
.unwrap()
.args(["read", tmp.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let lines: Vec<&str> = stdout.trim().lines().collect();
assert_eq!(lines.len(), 1);
let v: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
assert_eq!(v["number"], 1);
assert!(v["timestamp"].is_string());
assert!(v["length"].is_number());
assert!(v["stack"].is_string());
assert!(v["layers"].is_array());
}
#[test]
fn read_count_limits_output() {
let tmp = write_pcap(10);
let output = Command::cargo_bin("dsct")
.unwrap()
.args(["read", "--count", "3", tmp.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert_eq!(stdout.trim().lines().count(), 3);
}
#[test]
fn read_offset_skips_packets() {
let tmp = write_pcap(5);
let output = Command::cargo_bin("dsct")
.unwrap()
.args([
"read",
"--offset",
"2",
"--count",
"2",
tmp.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let lines: Vec<&str> = stdout.trim().lines().collect();
assert_eq!(lines.len(), 2);
let v: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
assert_eq!(v["number"], 3);
}
#[test]
fn read_packet_number_selects_specific_packets() {
let tmp = write_pcap(10);
let output = Command::cargo_bin("dsct")
.unwrap()
.args(["read", "-n", "2,5,8", tmp.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let lines: Vec<&str> = stdout.trim().lines().collect();
assert_eq!(lines.len(), 3);
let numbers: Vec<u64> = lines
.iter()
.map(|l| {
let v: serde_json::Value = serde_json::from_str(l).unwrap();
v["number"].as_u64().unwrap()
})
.collect();
assert_eq!(numbers, vec![2, 5, 8]);
}
#[test]
fn read_filter_by_protocol() {
let tmp = write_pcap(3);
let output = Command::cargo_bin("dsct")
.unwrap()
.args(["read", "-f", "udp", tmp.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert_eq!(stdout.trim().lines().count(), 3);
let output = Command::cargo_bin("dsct")
.unwrap()
.args(["read", "-f", "dns", tmp.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert_eq!(stdout.trim().lines().count(), 0);
}
#[test]
fn read_verbose_includes_extra_fields() {
let tmp = write_pcap(1);
let default_output = Command::cargo_bin("dsct")
.unwrap()
.args(["read", tmp.path().to_str().unwrap()])
.output()
.unwrap();
let verbose_output = Command::cargo_bin("dsct")
.unwrap()
.args(["read", "--verbose", tmp.path().to_str().unwrap()])
.output()
.unwrap();
assert!(default_output.status.success());
assert!(verbose_output.status.success());
assert!(verbose_output.stdout.len() >= default_output.stdout.len());
}
#[test]
fn read_stdin_produces_output() {
let pcap = build_pcap(1);
let output = Command::cargo_bin("dsct")
.unwrap()
.args(["read", "-"])
.write_stdin(pcap)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert_eq!(stdout.trim().lines().count(), 1);
}
#[test]
fn read_no_limit_outputs_all_packets() {
let tmp = write_pcap(5);
let output = Command::cargo_bin("dsct")
.unwrap()
.args(["read", "--no-limit", tmp.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert_eq!(stdout.trim().lines().count(), 5);
}
fn packet_numbers(stdout: &str) -> Vec<u64> {
stdout
.trim()
.lines()
.map(|l| {
let v: serde_json::Value = serde_json::from_str(l).unwrap();
v["number"].as_u64().unwrap()
})
.collect()
}
#[test]
fn sample_rate_outputs_every_nth() {
let tmp = write_pcap(20);
let output = Command::cargo_bin("dsct")
.unwrap()
.args([
"read",
"--sample-rate",
"5",
"--no-limit",
tmp.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let nums = packet_numbers(&String::from_utf8(output.stdout).unwrap());
assert_eq!(nums, vec![1, 6, 11, 16]);
}
#[test]
fn sample_rate_with_count() {
let tmp = write_pcap(100);
let output = Command::cargo_bin("dsct")
.unwrap()
.args([
"read",
"--sample-rate",
"10",
"--count",
"3",
tmp.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let nums = packet_numbers(&String::from_utf8(output.stdout).unwrap());
assert_eq!(nums, vec![1, 11, 21]);
}
#[test]
fn sample_rate_with_offset() {
let tmp = write_pcap(100);
let output = Command::cargo_bin("dsct")
.unwrap()
.args([
"read",
"--sample-rate",
"10",
"--offset",
"2",
"--count",
"2",
tmp.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let nums = packet_numbers(&String::from_utf8(output.stdout).unwrap());
assert_eq!(nums, vec![21, 31]);
}
#[test]
fn sample_rate_zero_is_error() {
let tmp = write_pcap(5);
let output = Command::cargo_bin("dsct")
.unwrap()
.args(["read", "--sample-rate", "0", tmp.path().to_str().unwrap()])
.output()
.unwrap();
assert!(!output.status.success());
}
#[test]
fn sample_rate_one_is_identity() {
let tmp = write_pcap(5);
let output = Command::cargo_bin("dsct")
.unwrap()
.args([
"read",
"--sample-rate",
"1",
"--no-limit",
tmp.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let nums = packet_numbers(&String::from_utf8(output.stdout).unwrap());
assert_eq!(nums, vec![1, 2, 3, 4, 5]);
}
#[test]
fn sample_rate_larger_than_total() {
let tmp = write_pcap(5);
let output = Command::cargo_bin("dsct")
.unwrap()
.args([
"read",
"--sample-rate",
"10",
"--no-limit",
tmp.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let nums = packet_numbers(&String::from_utf8(output.stdout).unwrap());
assert_eq!(nums, vec![1]);
}