use std::net::IpAddr;
use ufwctl::{Firewall, Protocol, RuleStatus};
fn section(n: u32, label: &str) {
println!("\n─── [{n}] {label} ───────────────────────────────────────");
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("╔══════════════════════════════════════════════════════╗");
println!("║ ufwctl Integration Test Suite ║");
println!("╚══════════════════════════════════════════════════════╝");
let fw = Firewall::new();
section(1, "Installation check");
let installed = fw.is_installed()?;
println!(" ufw installed: {installed}");
if !installed {
println!(" → ufw not found on this system. Skipping all tests.");
return Ok(());
}
section(2, "Firewall status");
let enabled = fw.is_enabled()?;
println!(" ufw enabled: {enabled}");
let raw = fw.raw_status()?;
let first = raw.lines().next().unwrap_or("(empty)");
println!(" raw status line: {first}");
section(3, "Port allow / status / delete (TCP)");
println!(" allowing 5050/tcp …");
fw.port(5050).allow().tcp().apply()?;
let s = fw.port(5050).status().tcp()?;
println!(" status after allow: {s:?}");
assert_eq!(s, RuleStatus::Allowed, "5050/tcp should be Allowed");
println!(" deleting 5050/tcp …");
let deleted = fw.port(5050).delete().tcp().apply()?;
println!(" deleted: {deleted}");
assert!(deleted, "5050/tcp should have been deleted");
let s = fw.port(5050).status().tcp()?;
println!(" status after delete: {s:?}");
assert_eq!(s, RuleStatus::None, "5050/tcp should be gone");
section(4, "Port allow / status / delete (Both)");
println!(" allowing 6060 both …");
fw.port(6060).allow().both().apply()?;
println!(" tcp status: {:?}", fw.port(6060).status().tcp()?);
println!(" udp status: {:?}", fw.port(6060).status().udp()?);
println!(" both status: {:?}", fw.port(6060).status().both()?);
println!(" deleting 6060 both …");
let deleted = fw.port(6060).delete().both().apply()?;
println!(" deleted: {deleted}");
assert!(deleted, "6060 both should have been deleted");
let s = fw.port(6060).status().both()?;
println!(" status after delete: {s:?}");
section(5, "Port deny");
println!(" denying 9090/udp …");
fw.port(9090).deny().udp().apply()?;
let s = fw.port(9090).status().udp()?;
println!(" status: {s:?}");
assert_eq!(s, RuleStatus::Denied, "9090/udp should be Denied");
println!(" deleting 9090/udp …");
let deleted = fw.port(9090).delete().udp().apply()?;
println!(" deleted: {deleted}");
assert!(deleted, "9090/udp should have been deleted");
let s = fw.port(9090).status().udp()?;
println!(" status after delete: {s:?}");
assert_eq!(s, RuleStatus::None, "9090/udp should be gone");
section(6, "IPv4 allow / status / delete");
let v4 = "10.255.255.254";
println!(" allowing {v4} both …");
fw.ipv4(v4)?.allow().both().apply()?;
let s = fw.ipv4(v4)?.status().both()?;
println!(" {v4} both status: {s:?}");
assert_eq!(s, RuleStatus::Allowed, "{v4} should be Allowed after allow");
println!(" deleting {v4} both …");
let deleted = fw.ipv4(v4)?.delete().both().apply()?;
println!(" deleted: {deleted}");
assert!(deleted, "{v4} should have been deleted");
let s = fw.ipv4(v4)?.status().both()?;
println!(" {v4} both status after delete: {s:?}");
section(7, "IPv4 deny");
let v4d = "10.255.255.253";
println!(" denying {v4d} both …");
fw.ipv4(v4d)?.deny().both().apply()?;
let s = fw.ipv4(v4d)?.status().both()?;
println!(" {v4d} status: {s:?}");
assert_eq!(s, RuleStatus::Denied, "{v4d} should be Denied");
println!(" deleting {v4d} both …");
let deleted = fw.ipv4(v4d)?.delete().both().apply()?;
println!(" deleted: {deleted}");
assert!(deleted, "{v4d} should have been deleted");
let s = fw.ipv4(v4d)?.status().both()?;
println!(" {v4d} status after delete: {s:?}");
section(8, "IPv6 allow / status / delete");
let v6 = "::1";
println!(" allowing {v6} both …");
fw.ipv6(v6)?.allow().both().apply()?;
let s = fw.ipv6(v6)?.status().both()?;
println!(" {v6} both status: {s:?}");
println!(" deleting {v6} both …");
let deleted = fw.ipv6(v6)?.delete().both().apply()?;
println!(" deleted: {deleted}");
let s = fw.ipv6(v6)?.status().both()?;
println!(" {v6} both status after delete: {s:?}");
section(9, "Rule listing");
let ports = fw.ports()?;
println!(" port rules: {}", ports.len());
for r in &ports {
println!(" {}/{:?} → {:?}", r.port, r.protocol, r.status);
}
let ips = fw.ips()?;
println!(" ip rules: {}", ips.len());
for r in &ips {
println!(" {} / {:?} → {:?}", r.ip, r.protocol, r.status);
}
section(10, "Delete idempotent (non-existing rule)");
let deleted = fw.port(59999).delete().tcp().apply()?;
println!(" delete non-existing port: {deleted} (should be false)");
assert!(!deleted, "non-existing rule delete should return false");
let deleted = fw.ipv4("10.255.255.255")?.delete().both().apply()?;
println!(" delete non-existing IPv4: {deleted} (should be false)");
assert!(!deleted, "non-existing IPv4 delete should return false");
section(11, "Address validation");
assert!(fw.ipv4("192.168.1.1").is_ok(), "valid IPv4");
assert!(fw.ipv4("not-an-ip").is_err(), "garbage IPv4");
assert!(fw.ipv4("::1").is_err(), "IPv6 in ipv4()");
assert!(fw.ipv6("::1").is_ok(), "valid IPv6");
assert!(fw.ipv6("not-an-ip").is_err(), "garbage IPv6");
assert!(fw.ipv6("192.168.1.1").is_err(), "IPv4 in ipv6()");
assert!(fw.ipv4("10.0.0.1").is_ok(), "private IPv4");
assert!(fw.ipv6("2001:db8::1").is_ok(), "doc IPv6");
println!(" all validation checks passed");
section(12, "Protocol helpers");
assert_eq!(Protocol::Tcp.as_str(), "tcp");
assert_eq!(Protocol::Udp.as_str(), "udp");
assert_eq!(Protocol::Both.as_str(), "both");
assert!(Protocol::Tcp.is_tcp());
assert!(!Protocol::Udp.is_tcp());
assert!(Protocol::Udp.is_udp());
assert!(!Protocol::Tcp.is_udp());
assert_eq!(Protocol::Tcp.to_string(), "tcp");
assert_eq!(Protocol::Udp.to_string(), "udp");
assert_eq!(Protocol::Both.to_string(), "both");
println!(" all protocol helper checks passed");
section(13, "Public type construction");
let _port_rule = ufwctl::PortRule {
port: 22,
protocol: Protocol::Tcp,
status: RuleStatus::Allowed,
};
let ip: IpAddr = "10.0.0.1".parse()?;
let _ip_rule = ufwctl::IpRule {
ip,
protocol: Protocol::Both,
status: RuleStatus::Denied,
};
println!(" type construction works");
println!("\n╔══════════════════════════════════════════════════════╗");
println!("║ All integration tests PASSED ║");
println!("╚══════════════════════════════════════════════════════╝");
Ok(())
}