oxihttp 0.1.0

OxiHTTP Pure-Rust HTTP facade for the COOLJAPAN ecosystem.
Documentation
//! Property-based fuzz tests for the OxiHTTP server.
//!
//! Sends randomly generated byte sequences to the TCP listener and asserts the
//! server does not panic, regardless of the input.

#[cfg(feature = "server")]
mod server_fuzz {
    use proptest::prelude::*;
    use std::io::Write as _;
    use std::net::TcpStream;
    use std::time::Duration;

    /// Synchronously bind a server on an OS-assigned port, run the provided
    /// closure with the bound address, then signal the server to shut down.
    ///
    /// Returns `Ok(())` when both the closure and the server exit cleanly.
    fn with_fuzz_server<F>(f: F)
    where
        F: FnOnce(std::net::SocketAddr),
    {
        let rt = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .expect("tokio runtime");

        let addr = rt.block_on(async {
            let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();

            let router = oxihttp_server::Router::new().get("/", |_req| async {
                oxihttp_server::response::text_response("ok")
            });

            let (bound_addr, server_handle) = oxihttp_server::Server::bind("127.0.0.1:0")
                .with_graceful_shutdown(async move {
                    let _ = shutdown_rx.await;
                })
                .serve_with_addr(router)
                .await
                .expect("server bind");

            // Small sleep to ensure the listener is ready before we return.
            tokio::time::sleep(Duration::from_millis(5)).await;

            // Return the address and spawned handles so the caller can use them.
            (bound_addr, shutdown_tx, server_handle)
        });

        let (bound_addr, shutdown_tx, server_handle) = addr;

        // Run the test closure synchronously.
        f(bound_addr);

        // Signal shutdown and wait for the server to stop.
        let _ = shutdown_tx.send(());
        let _ = rt.block_on(server_handle);
    }

    proptest! {
        // Limit cases to keep CI time reasonable; shrinking is also capped.
        #![proptest_config(ProptestConfig {
            cases: 64,
            max_shrink_iters: 16,
            ..ProptestConfig::default()
        })]

        /// Sending arbitrary bytes to the server must not cause a panic.
        ///
        /// The server is expected to close the connection or respond with an
        /// HTTP error (400 Bad Request is typical for malformed input).  What
        /// matters is that the server process/thread stays alive.
        #[test]
        fn test_malformed_http_request_no_panic(
            bytes in prop::collection::vec(any::<u8>(), 0..1024)
        ) {
            with_fuzz_server(|addr| {
                // Connect with a short timeout so a test that hangs fails fast.
                let Ok(mut stream) = TcpStream::connect_timeout(
                    &addr,
                    Duration::from_millis(500),
                ) else {
                    // If we cannot connect it means the server was not ready;
                    // that is acceptable — we simply skip this iteration.
                    return;
                };

                // Send the random bytes; ignore write errors (broken pipe, etc.).
                let _ = stream.set_write_timeout(Some(Duration::from_millis(200)));
                let _ = stream.write_all(&bytes);

                // Read whatever the server sends back (or nothing).
                let _ = stream.set_read_timeout(Some(Duration::from_millis(200)));
                let mut buf = [0u8; 4096];
                // A read error here is fine — the server may have closed the
                // connection immediately.
                let _ = std::io::Read::read(&mut stream, &mut buf);

                // If we reach here the server did not panic.
            });
        }
    }
}