cloud-sdk-testkit 0.18.0

Provider-neutral mock transport and fixture boundary for cloud-sdk.
Documentation

cloud-sdk-testkit

Provider-neutral testing support for the main cloud-sdk crate and its provider crates. The default graph is no_std, allocation-free, network-free, filesystem-free, and runtime-free.

Install

[dev-dependencies]
cloud-sdk = "0.29.0"
cloud-sdk-testkit = "0.18.0"

Mock Transport

use cloud_sdk::Method;
use cloud_sdk::transport::{BlockingTransport, RequestTarget, TransportRequest};
use cloud_sdk_testkit::{
    ExpectedRequest, FixtureBody, MockExchange, MockTransport, ResponseFixture,
};

let Ok(target) = RequestTarget::new("/resources?page=1") else {
    return;
};
let Ok(body) = FixtureBody::new(br#"{"resources":[]}"#) else {
    return;
};
let exchanges = [MockExchange::new(
    ExpectedRequest::new(Method::Get, target),
    ResponseFixture::success(body),
)];
let transport = MockTransport::new(&exchanges);
let mut output = [0_u8; 64];

let Ok(response) = transport.send(
    TransportRequest::new(Method::Get, target),
    &mut output,
) else {
    return;
};

assert_eq!(response.status().get(), 200);
assert_eq!(response.body(), br#"{"resources":[]}"#);
assert!(transport.is_complete());

The same mock implements the executor-neutral async contract without adding a runtime dependency:

# async fn example() {
use cloud_sdk::Method;
use cloud_sdk::transport::{AsyncTransport, RequestTarget, TransportRequest};
use cloud_sdk_testkit::{
    ExpectedRequest, FixtureBody, MockExchange, MockTransport, ResponseFixture,
};

let Ok(target) = RequestTarget::new("/resources/42") else { return };
let Ok(body) = FixtureBody::new(br#"{"id":42}"#) else { return };
let exchanges = [MockExchange::new(
    ExpectedRequest::new(Method::Get, target),
    ResponseFixture::success(body),
)];
let transport = MockTransport::new(&exchanges);
let mut output = [0_u8; 32];
let Ok(response) = AsyncTransport::send(
    &transport,
    TransportRequest::new(Method::Get, target),
    &mut output,
).await else { return };

assert_eq!(response.body(), br#"{"id":42}"#);
# }
# fn main() {}

Each exchange is consumed only after the request matches and the complete response body fits. Method, target, body, exhaustion, and response-capacity failures are distinct and payload-free. Debug output redacts request targets, request bodies, and response bodies.

Prepared Request Assertions

Bind MockTransport with with_endpoint before executing a PreparedRequest. Endpoint mismatches fail before an exchange is consumed. ExpectedRequest::with_content_type checks the exact request header, while ResponseFixture::with_content_type models missing, accepted, unexpected, or malformed response metadata.

The mock implements ResponseStorageSanitizer deterministically, so prepared tests can assert that the complete caller buffer is cleared even when endpoint or fixture validation fails before a response is returned.

PreparedRequestRecord::capture records method, redacted target/body lengths, provider service, complete operation metadata, and response policy without copying request values. Tests can therefore assert that mutations and destructive operations were not mislabeled as read-only, safe, or retryable.

Fixture Builders

ResponseFixture builds deterministic success, paginated, action, rate-limit, and error responses. PaginationFixture, ActionFixture, and RateLimitFixture reject incoherent metadata before a fixture can be used. Use ResponseFixture::with_rate_limit and with_content_type to attach transport metadata to paginated, action, success, or error responses.

FixtureBody supports borrowed bytes and compact repeated-byte bodies up to 8 MiB plus one byte. Writes preflight capacity and leave undersized destination buffers unchanged.

Adversarial Corpus

adversarial_corpus() returns reusable cases for:

  • malformed JSON;
  • additive unknown fields;
  • missing required fields;
  • an oversized response represented without an 8 MiB static allocation;
  • invalid pagination metadata;
  • an invalid action state and progress value.

Provider crates consume applicable cases in their own parser tests. The Hetzner Serde boundary exercises this corpus without making the testkit depend on cloud-sdk-hetzner.

Features

Feature Default Effect
default yes Empty; keeps the testkit allocation-free, runtime-free, and no_std.
alloc no Enables allocation-bearing test helpers and cloud-sdk/alloc.
std no Enables alloc and standard-library integration without selecting a runtime.

Docs.rs builds with all features. The mock transport remains network-free in every configuration.

Security Notes

This crate is test infrastructure, not a production transport. Exact request matching uses ordinary byte equality and must not be exposed as a remote secret comparison oracle. Authentication, base URLs, headers, timeout policy, TLS, retry behavior, and secret ownership remain responsibilities of concrete transport adapters.

The testkit stores only borrowed expectations and fixture bodies. Callers must keep borrowed data alive and must still sanitize secret-bearing test buffers when their threat model requires it.