wakezilla 0.2.0

A Wake-on-LAN proxy server written in Rust
Documentation
use std::io::ErrorKind;
use std::sync::Arc;

use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
use tokio::sync::Mutex;

use wakezilla::forward;

#[tokio::test]
async fn turn_off_remote_machine_sends_post_request() {
    let listener = match TcpListener::bind("127.0.0.1:0").await {
        Ok(listener) => listener,
        Err(err) if err.kind() == ErrorKind::PermissionDenied => {
            eprintln!(
                "skipping forward test because binding TCP sockets is not permitted: {}",
                err
            );
            return;
        }
        Err(err) => panic!("failed to bind http test listener: {err}"),
    };
    let addr = listener.local_addr().expect("failed to read listener addr");

    let received = Arc::new(Mutex::new(None));
    let received_clone = received.clone();

    let server_task = tokio::spawn(async move {
        if let Ok((mut socket, _)) = listener.accept().await {
            let mut buf = vec![0u8; 1024];
            if let Ok(n) = socket.read(&mut buf).await {
                if n > 0 {
                    let request = String::from_utf8_lossy(&buf[..n]).to_string();
                    *received_clone.lock().await = Some(request);
                }
            }
            let _ = socket
                .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok")
                .await;
        }
    });

    forward::turn_off_remote_machine(&addr.ip().to_string(), addr.port())
        .await
        .expect("turn_off_remote_machine should succeed");

    server_task.await.expect("server task panicked");

    let request = received.lock().await.clone().expect("no request captured");
    assert!(request.starts_with("POST /machines/turn-off"));

    let host_line = request
        .lines()
        .find(|line| line.to_ascii_lowercase().starts_with("host:"))
        .unwrap_or_else(|| panic!("Host header missing in request: {request}"));

    let host_value = host_line.split_once(':').map(|(_, value)| value.trim());
    let expected_ip = addr.ip().to_string();
    let expected_with_port = format!("{}:{}", expected_ip, addr.port());
    assert!(
        matches!(host_value, Some(value) if value.eq_ignore_ascii_case(&expected_ip) || value.eq_ignore_ascii_case(&expected_with_port)),
        "unexpected host header: {host_line}"
    );
}