whoseportisitanyway 1.1.2

Cross-platform TUI for discovering which ports are in use, who owns them, and what they're for
Documentation
use crate::model::{Classification, Ownership, PortEntry};

pub fn assess(entries: &mut [PortEntry], watched_ports: &[u16]) {
    for entry in entries.iter_mut() {
        entry.ownership = determine_ownership(entry, watched_ports);
    }
}

fn determine_ownership(entry: &PortEntry, watched_ports: &[u16]) -> Ownership {
    if watched_ports.is_empty() {
        return Ownership::Untracked;
    }

    if !watched_ports.contains(&entry.port) {
        return Ownership::Untracked;
    }

    match entry.classification {
        Classification::DevServer => Ownership::Owned,
        Classification::Docker
        | Classification::SshTunnel
        | Classification::System
        | Classification::Database
        | Classification::Proxy
        | Classification::Browser
        | Classification::MessageQueue
        | Classification::BuildTool
        | Classification::LanguageServer
        | Classification::Unknown => Ownership::Blocked,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::{PortState, Protocol};

    fn make_entry(port: u16, classification: Classification) -> PortEntry {
        PortEntry {
            port,
            protocol: Protocol::Tcp,
            pid: 100,
            process_name: "test".to_string(),
            command_line: "test".to_string(),
            state: PortState::Listen,
            classification,
            project: None,
            local_addr: format!("127.0.0.1:{port}"),
            all_addrs: vec![format!("127.0.0.1:{port}")],
            ownership: Ownership::Untracked,
            uid: None,
            user: None,
            remote_addr: None,
        }
    }

    #[test]
    fn empty_watched_ports_means_untracked() {
        let entry = make_entry(3000, Classification::DevServer);
        assert_eq!(determine_ownership(&entry, &[]), Ownership::Untracked);
    }

    #[test]
    fn unwatched_port_is_untracked() {
        let entry = make_entry(3000, Classification::DevServer);
        assert_eq!(determine_ownership(&entry, &[8080]), Ownership::Untracked);
    }

    #[test]
    fn watched_dev_server_is_owned() {
        let entry = make_entry(3000, Classification::DevServer);
        assert_eq!(determine_ownership(&entry, &[3000]), Ownership::Owned);
    }

    #[test]
    fn watched_docker_is_blocked() {
        let entry = make_entry(3000, Classification::Docker);
        assert_eq!(determine_ownership(&entry, &[3000]), Ownership::Blocked);
    }

    #[test]
    fn watched_database_is_blocked() {
        let entry = make_entry(5432, Classification::Database);
        assert_eq!(determine_ownership(&entry, &[5432]), Ownership::Blocked);
    }

    #[test]
    fn watched_system_is_blocked() {
        let entry = make_entry(22, Classification::System);
        assert_eq!(determine_ownership(&entry, &[22]), Ownership::Blocked);
    }

    #[test]
    fn watched_unknown_is_blocked() {
        let entry = make_entry(9999, Classification::Unknown);
        assert_eq!(determine_ownership(&entry, &[9999]), Ownership::Blocked);
    }

    #[test]
    fn all_non_dev_classifications_are_blocked() {
        let blocked_types = [
            Classification::Docker,
            Classification::SshTunnel,
            Classification::System,
            Classification::Database,
            Classification::Proxy,
            Classification::Browser,
            Classification::MessageQueue,
            Classification::BuildTool,
            Classification::LanguageServer,
            Classification::Unknown,
        ];
        for class in blocked_types {
            let entry = make_entry(3000, class);
            assert_eq!(
                determine_ownership(&entry, &[3000]),
                Ownership::Blocked,
                "expected Blocked for {:?}",
                entry.classification
            );
        }
    }

    #[test]
    fn assess_mutates_ownership() {
        let mut entries = vec![
            make_entry(3000, Classification::DevServer),
            make_entry(5432, Classification::Database),
            make_entry(9999, Classification::Unknown),
        ];
        assess(&mut entries, &[3000, 5432]);
        assert_eq!(entries[0].ownership, Ownership::Owned);
        assert_eq!(entries[1].ownership, Ownership::Blocked);
        assert_eq!(entries[2].ownership, Ownership::Untracked);
    }
}