Expand description
provider-neutral no_std testkit for cloud-sdk.
Deterministic mock transport, prepared-request records, bounded response fixtures, and adversarial corpora.
§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.42.0"
cloud-sdk-testkit = "0.24.2"§Mock Transport
use cloud_sdk::Method;
use cloud_sdk::transport::{
BlockingTransport, RequestTarget, ResponseBuffer, 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 output_capacity = output.len();
let mut response_headers = [0_u8; cloud_sdk::transport::MAX_RESPONSE_HEADER_BYTES];
let mut response =
ResponseBuffer::new(&mut output, output_capacity, &mut response_headers);
if transport
.send(
TransportRequest::new(Method::Get, target),
response.writer(),
)
.is_err()
{
return;
}
assert!(response
.with_response(|view| {
view.status().get() == 200
&& view.body() == br#"{"resources":[]}"#
})
.is_ok_and(core::convert::identity));
assert!(transport.is_complete());The same mock implements the executor-neutral async contract without adding a runtime dependency:
use cloud_sdk::Method;
use cloud_sdk::transport::{
AsyncTransport, RequestTarget, ResponseBuffer, 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 output_capacity = output.len();
let mut response_headers = [0_u8; cloud_sdk::transport::MAX_RESPONSE_HEADER_BYTES];
let mut response =
ResponseBuffer::new(&mut output, output_capacity, &mut response_headers);
if AsyncTransport::send(
&transport,
TransportRequest::new(Method::Get, target),
response.writer(),
)
.await
.is_err()
{
return;
}
assert!(response
.with_response(|view| view.body() == br#"{"id":42}"#)
.is_ok_and(core::convert::identity));§Raw Delivery Faults
RawFaultExecutor injects a deterministic NotSent, PossiblySent,
ResponseStarted, or unknown-delivery failure into both raw executor traits.
Unknown delivery deliberately becomes PossiblySent:
use cloud_sdk_testkit::{RawFault, RawFaultExecutor};
let executor = RawFaultExecutor::new(RawFault::Unknown);
assert_eq!(executor, RawFaultExecutor::new(RawFault::Unknown));Each exchange is consumed only after method, target, ordered headers, body, and complete response capacity match. Failures are distinct and payload-free. Debug output redacts request targets, header values, 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_headers checks the exact ordered request-header block.
ResponseFixture::with_headers adds complete bounded raw metadata, while
with_content_type models missing, accepted, unexpected, or malformed typed
content metadata.
Core clears the complete caller buffer independently of the mock, so prepared tests can assert cleanup even when endpoint or fixture validation fails before a response is returned. The mock’s sanitizer implementation remains available only for tests that deliberately exercise the additive hook.
PreparedRequestRecord::capture records method, redacted target/body lengths,
provider service and endpoint policy, 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. Core secret-capable header types do not expose ordinary equality. The mock uses a private exact byte matcher solely for deterministic expectations; it 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.
Structs§
- Action
Fixture - Action metadata for polling tests.
- Adversarial
Fixture - Named adversarial response body.
- Expected
Request - Expected request fields for one mock exchange.
- Mock
Exchange - One expected request and deterministic response.
- Mock
Transport - Ordered no-allocation mock implementation of
BlockingTransport. - Pagination
Fixture - Pagination metadata for a deterministic response.
- Prepared
Request Record - Non-secret record of one prepared request for policy assertions.
- Rate
Limit Fixture - Rate-limit metadata fixture.
- RawFault
Error - Payload-free deterministic raw fault.
- RawFault
Executor - No-allocation executor that fails at one selected delivery phase.
- Response
Fixture - Provider-neutral response body plus optional interpreted metadata.
Enums§
- Action
State - Provider-neutral action lifecycle fixture.
- Adversarial
Kind - Adversarial response category.
- Fixture
Body - Borrowed or compact repeated-byte fixture body.
- Fixture
Body Error - Fixture body construction or write error.
- Fixture
Kind - Fixture response category.
- Fixture
Metadata Error - Fixture metadata validation error.
- Mock
Error - Deterministic mock transport failure.
- RawFault
- Failure point injected by
RawFaultExecutor. - Response
Fixture Error - Response fixture construction error.
Constants§
- DEFAULT_
RESPONSE_ LIMIT - Common response limit used by the initial provider response boundary.
- MAX_
FIXTURE_ BODY_ BYTES - Maximum fixture body length, including one byte beyond the common 8 MiB response-policy ceiling for oversized-input tests.
Functions§
- adversarial_
corpus - Creates the fixed six-case adversarial response corpus.