use std::fs;
use std::io::Write;
use std::process::{Command, Stdio};
use tempfile::TempDir;
mod test_config;
#[allow(unused_imports)]
use test_config::*;
#[test]
fn test_cli_basic_ipv4_encoding() {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", "192.168.1.1"])
.output()
.expect("Failed to execute CLI");
assert!(
output.status.success(),
"CLI command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let encoded = stdout.trim();
let words: Vec<&str> = encoded.split(' ').collect();
assert_eq!(words.len(), 4, "Should produce exactly 4 words for IPv4");
for word in words {
assert!(!word.is_empty(), "Word should not be empty");
assert!(
!word.is_empty(),
"Word should be at least 1 character (GOLD wordlist includes single-character words)"
);
}
}
#[test]
fn test_cli_basic_ipv6_encoding() {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", "::1"])
.output()
.expect("Failed to execute CLI");
assert!(
output.status.success(),
"CLI command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let encoded = stdout.trim();
let words: Vec<&str> = encoded.split(' ').collect();
assert!(
words.len() == 6 || words.len() == 9 || words.len() == 12,
"IPv6 should produce 6, 9, or 12 words, got: {}",
words.len()
);
}
#[test]
fn test_cli_socket_address_encoding() {
let test_cases = vec![
"192.168.1.1:8080",
"127.0.0.1:443",
"[::1]:8080",
"[2001:db8::1]:443",
];
for addr in test_cases {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", addr])
.output()
.expect("Failed to execute CLI");
assert!(
output.status.success(),
"CLI command failed for {}: {}",
addr,
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let encoded = stdout.trim();
assert!(!encoded.is_empty(), "Output should not be empty for {addr}");
let word_count = encoded.split(' ').count();
if addr.contains('[') {
assert!(
word_count == 6 || word_count == 9 || word_count == 12,
"IPv6 should have 6, 9, or 12 words for {addr}, got {word_count}"
);
} else {
assert_eq!(word_count, 4, "IPv4 should have exactly 4 words for {addr}");
}
}
}
#[test]
fn test_cli_word_decoding() {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", "192.168.1.1"])
.output()
.expect("Failed to execute CLI");
assert!(output.status.success());
let encoded = String::from_utf8_lossy(&output.stdout).trim().to_string();
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", &encoded])
.output()
.expect("Failed to execute CLI");
assert!(
output.status.success(),
"CLI decoding failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let decoded_output = String::from_utf8_lossy(&output.stdout);
let decoded = decoded_output.trim();
assert!(
decoded.contains("192.168.1.1"),
"Decoded address should contain original IP: {decoded}"
);
}
#[test]
fn test_cli_verbose_output() {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", "-v", "192.168.1.1"])
.output()
.expect("Failed to execute CLI");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.len() > 20, "Verbose output should be longer");
}
#[test]
fn test_cli_help_output() {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", "--help"])
.output()
.expect("Failed to execute CLI");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Usage") || stdout.contains("USAGE"));
assert!(stdout.contains("4wn") || stdout.contains("four-word"));
}
#[test]
fn test_cli_version_output() {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", "--version"])
.output()
.expect("Failed to execute CLI");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let version = env!("CARGO_PKG_VERSION");
assert!(
stdout.contains(version) || stdout.contains("version"),
"Expected version {version} in output: {stdout}"
);
}
#[test]
fn test_cli_invalid_input_handling() {
let invalid_inputs = vec![
"999.999.999.999", "xyz123.invalid", "", "192.168.1.1:99999", "::gg", "word1.word2.word3", "a.b.c", "one two three", "123.456.789", ];
for input in invalid_inputs {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", input])
.output()
.expect("Failed to execute CLI");
assert!(
!output.status.success(),
"CLI should fail for invalid input: {input}"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.is_empty(),
"Should provide error message for invalid input: {input}"
);
}
}
#[test]
fn test_cli_batch_processing() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let input_file = temp_dir.path().join("addresses.txt");
let addresses = ["192.168.1.1", "10.0.0.1", "127.0.0.1", "::1", "2001:db8::1"];
let content = addresses.join("\n");
fs::write(&input_file, content).expect("Failed to write input file");
let output = Command::new("cargo")
.args([
"run",
"--bin",
"4wn",
"--",
"--file",
input_file.to_str().unwrap(),
])
.output()
.expect("Failed to execute CLI");
let _stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if !output.status.success() {
assert!(
stderr.contains("not supported")
|| stderr.contains("unknown")
|| stderr.contains("help"),
"Should provide helpful error message for unsupported features"
);
}
}
#[test]
fn test_cli_output_formats() {
let test_formats = vec![
("--format", "json"),
("--format", "yaml"),
("--format", "plain"),
("--output", "json"),
("-o", "json"),
];
for (flag, format) in test_formats {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", flag, format, "192.168.1.1"])
.output()
.expect("Failed to execute CLI");
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("not supported")
|| stderr.contains("unknown")
|| stderr.contains("help"),
"Should provide helpful error message for unsupported format: {format}"
);
}
}
}
#[test]
fn test_cli_performance_mode() {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", "--benchmark", "192.168.1.1"])
.output()
.expect("Failed to execute CLI");
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("not supported")
|| stderr.contains("unknown")
|| stderr.contains("help"),
"Should provide helpful error message for unsupported benchmark mode"
);
}
}
#[test]
fn test_cli_error_recovery() {
let mut child = Command::new("cargo")
.args(["run", "--bin", "4wn"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to spawn CLI");
if let Some(mut stdin) = child.stdin.take() {
let _ = stdin.write_all(b"invalid.input\n");
let _ = stdin.write_all(b"192.168.1.1\n");
let _ = stdin.write_all(b"quit\n");
}
let output = child.wait_with_output().expect("Failed to wait for CLI");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stdout.is_empty() || !stderr.is_empty(),
"CLI should produce some output"
);
}
#[test]
fn test_cli_concurrent_access() {
use std::sync::Arc;
use std::thread;
let addresses = Arc::new(vec![
"192.168.1.1",
"10.0.0.1",
"127.0.0.1",
"::1",
"2001:db8::1",
]);
let mut handles = vec![];
for i in 0..5 {
let addresses = Arc::clone(&addresses);
let handle = thread::spawn(move || {
let addr = addresses[i];
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", addr])
.output()
.expect("Failed to execute CLI");
(
addr,
output.status.success(),
String::from_utf8_lossy(&output.stdout).trim().to_string(),
)
});
handles.push(handle);
}
for handle in handles {
let (addr, success, output) = handle.join().expect("Thread failed");
assert!(success, "CLI failed for address: {addr}");
assert!(!output.is_empty(), "CLI produced empty output for: {addr}");
}
}
#[test]
fn test_cli_memory_usage() {
for i in 0..100 {
let addr = format!("192.168.1.{i}");
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", &addr])
.output()
.expect("Failed to execute CLI");
assert!(output.status.success(), "CLI failed for address: {addr}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!stdout.trim().is_empty(),
"CLI produced empty output for: {addr}"
);
}
}
#[test]
fn test_cli_large_input_handling() {
let long_input = "a".repeat(1000);
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", &long_input])
.output()
.expect("Failed to execute CLI");
assert!(
!output.status.success(),
"CLI should reject very long input"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.is_empty(),
"Should provide error message for long input"
);
}
#[test]
fn test_cli_unicode_handling() {
let unicode_inputs = vec!["192.168.1.1🚀", "réseau.test", "网络.测试", "тест.сеть"];
for input in unicode_inputs {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", input])
.output()
.expect("Failed to execute CLI");
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.is_empty(),
"Should provide error message for unicode input: {input}"
);
}
}
}
#[test]
fn test_cli_signal_handling() {
let mut child = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", "--interactive"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to spawn CLI");
std::thread::sleep(std::time::Duration::from_millis(100));
let _ = child.kill();
let output = child.wait_with_output().expect("Failed to wait for CLI");
assert!(!output.status.success(), "CLI should terminate when killed");
}
#[test]
fn test_cli_configuration_file() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let config_file = temp_dir.path().join("config.toml");
let config_content = r#"
[default]
format = "json"
verbose = true
[encoding]
compression = "high"
dictionary = "16k"
"#;
fs::write(&config_file, config_content).expect("Failed to write config file");
let output = Command::new("cargo")
.args([
"run",
"--bin",
"4wn",
"--",
"--config",
config_file.to_str().unwrap(),
"192.168.1.1",
])
.output()
.expect("Failed to execute CLI");
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("not supported")
|| stderr.contains("unknown")
|| stderr.contains("help"),
"Should provide helpful error message for unsupported config file"
);
}
}
#[test]
fn test_cli_environment_variables() {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", "192.168.1.1"])
.env("FWN_VERBOSE", "true")
.env("FWN_FORMAT", "json")
.env("FWN_COMPRESSION", "high")
.output()
.expect("Failed to execute CLI");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if !output.status.success() {
assert!(
!stderr.is_empty(),
"Should provide error message when failing"
);
} else {
assert!(
!stdout.trim().is_empty(),
"Should provide output when succeeding"
);
}
}
#[test]
fn test_cli_workflow_integration() {
let addresses = vec![
"192.168.1.1:8080",
"10.0.0.1:22",
"127.0.0.1:443",
"[::1]:8080",
];
let mut encoded_addresses = Vec::new();
for addr in &addresses {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", addr])
.output()
.expect("Failed to execute CLI");
assert!(output.status.success(), "Encoding failed for: {addr}");
let encoded = String::from_utf8_lossy(&output.stdout).trim().to_string();
encoded_addresses.push(encoded);
}
for (i, encoded) in encoded_addresses.iter().enumerate() {
let output = Command::new("cargo")
.args(["run", "--bin", "4wn", "--", encoded])
.output()
.expect("Failed to execute CLI");
assert!(output.status.success(), "Decoding failed for: {encoded}");
let decoded_output = String::from_utf8_lossy(&output.stdout);
let decoded = decoded_output.trim();
let original_ip = addresses[i].split(':').next().unwrap();
assert!(
decoded.contains(original_ip),
"Decoded address should contain original IP: {} -> {} -> {}",
addresses[i],
encoded,
decoded
);
}
}