waitup 1.0.0

Wait for TCP ports and HTTP endpoints to be available. Essential for Docker, K8s, and CI/CD pipelines to ensure services are ready before proceeding.
Documentation
#![allow(
    clippy::unwrap_used,
    clippy::expect_used,
    reason = "test code where panics are acceptable"
)]

use std::process::Command;
use tokio::net::TcpListener;

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn successful_tcp_connection() {
        // Start a test server
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
        let addr = listener.local_addr().unwrap();

        // Spawn server task
        tokio::spawn(async move {
            let (_stream, _addr) = listener.accept().await.unwrap();
        });

        // Test waitup
        let output = Command::new("./target/debug/waitup")
            .args([
                &format!("127.0.0.1:{}", addr.port()),
                "--timeout",
                "5s",
                "--quiet",
            ])
            .output()
            .expect("Failed to execute waitup");

        assert!(output.status.success());
    }

    #[tokio::test]
    async fn timeout_failure() {
        let output = Command::new("./target/debug/waitup")
            .args(["127.0.0.1:65534", "--timeout", "1s", "--quiet"])
            .output()
            .expect("Failed to execute waitup");

        assert!(!output.status.success());
        assert_eq!(output.status.code(), Some(1));
    }

    #[tokio::test]
    async fn dns_resolution() {
        let output = Command::new("./target/debug/waitup")
            .args(["google.com:80", "--timeout", "10s", "--quiet"])
            .output()
            .expect("Failed to execute waitup");

        assert!(output.status.success());
    }

    #[tokio::test]
    async fn multiple_targets_any() {
        // Start one test server
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
        let addr = listener.local_addr().unwrap();

        tokio::spawn(async move {
            let (_stream, _addr) = listener.accept().await.unwrap();
        });

        let output = Command::new("./target/debug/waitup")
            .args([
                &format!("127.0.0.1:{}", addr.port()),
                "127.0.0.1:65534", // This will fail
                "--any",
                "--timeout",
                "5s",
                "--quiet",
            ])
            .output()
            .expect("Failed to execute waitup");

        assert!(output.status.success());
    }

    #[tokio::test]
    async fn command_execution() {
        // Start a test server
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
        let addr = listener.local_addr().unwrap();

        tokio::spawn(async move {
            let (_stream, _addr) = listener.accept().await.unwrap();
        });

        let output = Command::new("./target/debug/waitup")
            .args([
                &format!("127.0.0.1:{}", addr.port()),
                "--timeout",
                "5s",
                "--quiet",
                "--",
                "echo",
                "command executed",
            ])
            .output()
            .expect("Failed to execute waitup");

        assert!(output.status.success());
    }

    #[test]
    fn invalid_target_format() {
        let output = Command::new("./target/debug/waitup")
            .args(["invalid-target", "--quiet"])
            .output()
            .expect("Failed to execute waitup");

        assert!(!output.status.success());
        assert_eq!(output.status.code(), Some(2));
    }

    #[test]
    fn invalid_timeout_format() {
        let output = Command::new("./target/debug/waitup")
            .args(["localhost:8080", "--timeout", "invalid", "--quiet"])
            .output()
            .expect("Failed to execute waitup");

        assert!(!output.status.success());
        assert_eq!(output.status.code(), Some(2));
    }
}