cloud-sdk-reqwest 0.19.0

Optional provider-neutral reqwest transport boundary for cloud-sdk.
Documentation
use std::string::String;
use std::time::Duration;

use cloud_sdk::Method;
use cloud_sdk::transport::{BlockingTransport, BoundTransport, RequestTarget, TransportRequest};
use cloud_sdk_sanitization::SecretBuffer;

use super::{BearerToken, build_loopback};
use crate::test_server::{spawn_concurrent_pair, spawn_sequence_with_first_delay};

#[test]
fn blocking_client_is_clone_send_sync_and_endpoint_bound() {
    fn assert_shared<T: Clone + Send + Sync>() {}
    assert_shared::<super::super::BlockingClient>();

    let Some(client) = build_loopback("http://127.0.0.1:9/v1") else {
        return;
    };
    let identity = client.endpoint_identity();
    assert!(identity.is_ok());
    if let Ok(identity) = identity {
        assert_eq!(identity.host(), "127.0.0.1");
        assert_eq!(identity.effective_port(), 9);
        assert_eq!(identity.base_path(), "/v1");
    }
    let before = client.endpoint_identity();
    let replacement = BearerToken::new("replacement-token");
    assert!(replacement.is_ok());
    if let Ok(replacement) = replacement {
        assert!(client.rotate_bearer_token(replacement).is_ok());
    }
    assert_eq!(client.endpoint_identity(), before);
}

#[test]
fn blocking_shared_handle_supports_overlapping_caller_bounded_requests() {
    let server = spawn_concurrent_pair("200 OK", b"ok");
    let Ok(server) = server else { return };
    let Some(client) = build_loopback(&server.endpoint) else {
        return;
    };
    let Ok(target) = RequestTarget::new("/concurrent") else {
        return;
    };

    std::thread::scope(|scope| {
        let first = scope.spawn(|| send_once(&client, target));
        let second = scope.spawn(|| send_once(&client, target));
        assert!(matches!(first.join(), Ok(true)));
        assert!(matches!(second.join(), Ok(true)));
    });

    assert!(server.request.recv_timeout(Duration::from_secs(2)).is_ok());
    assert!(server.request.recv_timeout(Duration::from_secs(2)).is_ok());
}

#[test]
fn blocking_rotation_keeps_in_flight_snapshot_and_changes_new_requests() {
    let server = spawn_sequence_with_first_delay("200 OK", b"ok", Duration::from_millis(150));
    let Ok(server) = server else { return };
    let Some(client) = build_loopback(&server.endpoint) else {
        return;
    };
    let Ok(target) = RequestTarget::new("/rotation") else {
        return;
    };

    std::thread::scope(|scope| {
        let first = scope.spawn(|| send_once(&client, target));
        let first_request = server.request.recv_timeout(Duration::from_secs(2));
        assert!(first_request.is_ok());
        if let Ok(first_request) = first_request {
            assert!(has_authorization(&first_request.bytes, "test-token"));
        }

        let mut replacement = *b"rotated-token";
        assert!(
            client
                .rotate_bearer_token_from_mut_bytes(&mut replacement)
                .is_ok()
        );
        assert_eq!(replacement, [0; 13]);
        assert!(matches!(first.join(), Ok(true)));
    });

    assert!(send_once(&client, target));
    let second_request = server.request.recv_timeout(Duration::from_secs(2));
    assert!(second_request.is_ok());
    if let Ok(second_request) = second_request {
        assert!(has_authorization(&second_request.bytes, "rotated-token"));
        assert!(!has_authorization(&second_request.bytes, "test-token"));
    }
}

#[test]
fn blocking_guarded_rotation_clears_source_and_is_shared_by_clones() {
    let Some(client) = build_loopback("http://127.0.0.1:9/v1") else {
        return;
    };
    let clone = client.clone();
    let mut source = *b"guarded-token";
    let result = clone.rotate_bearer_token_from_secret_buffer(SecretBuffer::new(&mut source));
    assert!(result.is_ok());
    assert_eq!(source, [0; 13]);
    assert_eq!(client.endpoint_identity(), clone.endpoint_identity());
}

fn send_once(client: &super::super::BlockingClient, target: RequestTarget<'_>) -> bool {
    let mut output = [0xa5_u8; 8];
    let response = client.send(TransportRequest::new(Method::Get, target), &mut output);
    response.is_ok_and(|response| response.status().is_success() && response.body() == b"ok")
}

fn has_authorization(request: &[u8], token: &str) -> bool {
    let wire = String::from_utf8_lossy(request).to_ascii_lowercase();
    wire.contains(&std::format!("authorization: bearer {token}\r\n"))
}