aioduct 0.2.5

Async-native HTTP client built directly on hyper 1.x — no hyper-util, no legacy
Documentation
use super::*;
use std::net::IpAddr;

#[tokio::test]
async fn checkout_coalesced_finds_by_san() {
    let pool = ConnectionPool::<RequestBodySend>::new()
        .without_reaper()
        .with_max_idle_per_host(8)
        .with_idle_timeout(Duration::from_secs(30));
    let k = key_https("origin.example.com:443");

    let mut conn = make_h2_conn().await;
    conn.sans = std::sync::Arc::from(vec![
        "origin.example.com".into(),
        "cdn.example.com".into(),
        "api.example.com".into(),
    ]);
    conn.remote_addr = Some(std::net::SocketAddr::from(([10, 0, 0, 1], 443)));
    pool.checkin(k, conn);

    tokio::task::yield_now().await;

    let ip: IpAddr = [10, 0, 0, 1].into();
    let result = pool.checkout_coalesced(
        "cdn.example.com",
        std::net::SocketAddr::new(ip, 443),
        &ProxyRoute::DIRECT,
    );
    assert!(result.is_some(), "should find coalesced connection via SAN");
}

#[tokio::test]
async fn checkout_coalesced_rejects_h1() {
    let pool = ConnectionPool::<RequestBodySend>::new()
        .without_reaper()
        .with_max_idle_per_host(8)
        .with_idle_timeout(Duration::from_secs(30));
    let k = key_https("origin.example.com:443");

    let mut conn = make_h1_conn().await;
    conn.sans = std::sync::Arc::from(vec!["origin.example.com".into(), "cdn.example.com".into()]);
    conn.remote_addr = Some(std::net::SocketAddr::from(([10, 0, 0, 1], 443)));
    pool.checkin(k, conn);

    tokio::task::yield_now().await;

    let ip: IpAddr = [10, 0, 0, 1].into();
    let result = pool.checkout_coalesced(
        "cdn.example.com",
        std::net::SocketAddr::new(ip, 443),
        &ProxyRoute::DIRECT,
    );
    assert!(result.is_none(), "h1 connections should not be coalesced");
}

#[tokio::test]
async fn checkout_coalesced_rejects_different_ip() {
    let pool = ConnectionPool::<RequestBodySend>::new()
        .without_reaper()
        .with_max_idle_per_host(8)
        .with_idle_timeout(Duration::from_secs(30));
    let k = key_https("origin.example.com:443");

    let mut conn = make_h2_conn().await;
    conn.sans = std::sync::Arc::from(vec!["origin.example.com".into(), "cdn.example.com".into()]);
    conn.remote_addr = Some(std::net::SocketAddr::from(([10, 0, 0, 1], 443)));
    pool.checkin(k, conn);

    tokio::task::yield_now().await;

    let different_ip: IpAddr = [10, 0, 0, 2].into();
    let result = pool.checkout_coalesced(
        "cdn.example.com",
        std::net::SocketAddr::new(different_ip, 443),
        &ProxyRoute::DIRECT,
    );
    assert!(result.is_none(), "different IP should prevent coalescing");
}

#[tokio::test]
async fn checkout_coalesced_rejects_different_port() {
    let pool = ConnectionPool::<RequestBodySend>::new()
        .without_reaper()
        .with_max_idle_per_host(8)
        .with_idle_timeout(Duration::from_secs(30));
    let key = key_https("origin.example.com:443");
    let remote_addr = std::net::SocketAddr::from(([10, 0, 0, 1], 443));

    let mut conn = make_h2_conn().await;
    conn.sans = std::sync::Arc::from(vec!["origin.example.com".into(), "cdn.example.com".into()]);
    conn.remote_addr = Some(remote_addr);
    pool.checkin(key, conn);

    tokio::task::yield_now().await;

    let result = pool.checkout_coalesced(
        "cdn.example.com",
        std::net::SocketAddr::new(remote_addr.ip(), 8443),
        &ProxyRoute::DIRECT,
    );
    assert!(
        result.is_none(),
        "same-IP connections on another port must not be coalesced"
    );
}

#[tokio::test]
async fn checkout_coalesced_rejects_forced_pool_entries() {
    let pool = ConnectionPool::<RequestBodySend>::new()
        .without_reaper()
        .with_max_idle_per_host(8)
        .with_idle_timeout(Duration::from_secs(30));
    let remote_addr = std::net::SocketAddr::from(([10, 0, 0, 1], 443));
    let mut key = key_https("origin.example.com:443");
    key.forced_addr = Some(remote_addr);

    let mut conn = make_h2_conn().await;
    conn.sans = std::sync::Arc::from(vec!["origin.example.com".into(), "cdn.example.com".into()]);
    conn.remote_addr = Some(remote_addr);
    pool.checkin(key, conn);

    tokio::task::yield_now().await;

    let result = pool.checkout_coalesced("cdn.example.com", remote_addr, &ProxyRoute::DIRECT);
    assert!(
        result.is_none(),
        "forced connections must not satisfy ordinary coalescing"
    );
}

#[tokio::test]
async fn h2_coalescing_rejects_h3_endpoint_pool_entries() {
    let pool = ConnectionPool::<RequestBodySend>::new()
        .without_reaper()
        .with_max_idle_per_host(8)
        .with_idle_timeout(Duration::from_secs(30));
    let remote_addr = std::net::SocketAddr::from(([10, 0, 0, 1], 443));
    let mut key = key_https("origin.example.com:443");
    key.h3_endpoint = Some(("alt.example.com".to_owned(), 443));

    let mut conn = make_h2_conn().await;
    conn.sans = std::sync::Arc::from(vec!["origin.example.com".into(), "cdn.example.com".into()]);
    conn.remote_addr = Some(remote_addr);
    pool.checkin(key, conn);

    tokio::task::yield_now().await;

    let result = pool.checkout_coalesced("cdn.example.com", remote_addr, &ProxyRoute::DIRECT);
    assert!(result.is_none());
}

#[tokio::test]
async fn checkout_coalesced_skips_expired() {
    let pool = ConnectionPool::<RequestBodySend>::new()
        .without_reaper()
        .with_max_idle_per_host(8)
        .with_idle_timeout(Duration::from_millis(50));
    let k = key_https("origin.example.com:443");

    let mut conn = make_h2_conn().await;
    conn.sans = std::sync::Arc::from(vec!["origin.example.com".into(), "cdn.example.com".into()]);
    conn.remote_addr = Some(std::net::SocketAddr::from(([10, 0, 0, 1], 443)));
    pool.checkin(k, conn);

    tokio::time::sleep(Duration::from_millis(100)).await;

    let ip: IpAddr = [10, 0, 0, 1].into();
    let result = pool.checkout_coalesced(
        "cdn.example.com",
        std::net::SocketAddr::new(ip, 443),
        &ProxyRoute::DIRECT,
    );
    assert!(
        result.is_none(),
        "expired connection should not be returned"
    );
}

#[test]
fn checkout_coalesced_empty_pool_returns_none() {
    let pool = ConnectionPool::<RequestBodySend>::new()
        .without_reaper()
        .with_max_idle_per_host(8)
        .with_idle_timeout(Duration::from_secs(30));
    let ip: IpAddr = [10, 0, 0, 1].into();
    let result = pool.checkout_coalesced(
        "cdn.example.com",
        std::net::SocketAddr::new(ip, 443),
        &ProxyRoute::DIRECT,
    );
    assert!(result.is_none(), "empty pool should return None");
}

#[tokio::test]
async fn coalesced_checkout_skips_past_max_lifetime() {
    let pool = ConnectionPool::<RequestBodySend>::new()
        .without_reaper()
        .with_max_idle_per_host(8)
        .with_idle_timeout(Duration::from_secs(30))
        .with_max_lifetime(Duration::from_millis(1));
    let k = key_https("origin.example.com:443");

    let mut conn = make_h2_conn().await;
    conn.sans = std::sync::Arc::from(vec!["origin.example.com".into(), "cdn.example.com".into()]);
    conn.remote_addr = Some(std::net::SocketAddr::from(([10, 0, 0, 1], 443)));
    pool.checkin(k, conn);

    tokio::time::sleep(Duration::from_millis(50)).await;

    let ip: IpAddr = [10, 0, 0, 1].into();
    let result = pool.checkout_coalesced(
        "cdn.example.com",
        std::net::SocketAddr::new(ip, 443),
        &ProxyRoute::DIRECT,
    );
    assert!(
        result.is_none(),
        "connection past max lifetime should not be returned"
    );
}