Skip to main content

cloud_sdk_testkit/mock/
local.rs

1use core::{cell::Cell, fmt};
2
3use cloud_sdk::authentication::{
4    AuthenticatedRequest, BoundCredentialTransport, CredentialBinding,
5    LocalAsyncAuthenticatedTransport,
6};
7use cloud_sdk::transport::{
8    AsyncResponseStaging, BoundTransport, EndpointIdentity, EndpointIdentityError,
9    LocalAsyncTransport, ResponseCompletion, TransportRequest,
10};
11
12use super::{MockError, MockExchange, MockTransport};
13
14/// Ordered mock transport whose futures are intentionally local-only.
15///
16/// The `Cell` marker makes this type `!Sync`, so futures borrowing it cannot be
17/// sent between threads. It exercises browser, embedded, and single-threaded
18/// executor integrations without adding an allocator or runtime dependency.
19///
20/// ```compile_fail
21/// use cloud_sdk_testkit::LocalMockTransport;
22/// fn require_sync<T: Sync>() {}
23/// require_sync::<LocalMockTransport<'static>>();
24/// ```
25pub struct LocalMockTransport<'a> {
26    inner: MockTransport<'a>,
27    local_marker: Cell<()>,
28}
29
30impl<'a> LocalMockTransport<'a> {
31    /// Creates a local-only mock over an ordered exchange slice.
32    #[must_use]
33    pub const fn new(exchanges: &'a [MockExchange<'a>]) -> Self {
34        Self {
35            inner: MockTransport::new(exchanges),
36            local_marker: Cell::new(()),
37        }
38    }
39
40    /// Binds the mock permanently to one normalized endpoint identity.
41    #[must_use]
42    pub const fn with_endpoint(mut self, endpoint: EndpointIdentity<'a>) -> Self {
43        self.inner = self.inner.with_endpoint(endpoint);
44        self
45    }
46
47    /// Selects a deterministic credential lineage for association tests.
48    #[must_use]
49    pub const fn with_credential_binding(mut self, binding: CredentialBinding) -> Self {
50        self.inner = self.inner.with_credential_binding(binding);
51        self
52    }
53
54    /// Returns the number of exchanges not yet consumed.
55    #[must_use]
56    pub fn remaining(&self) -> usize {
57        self.inner.remaining()
58    }
59
60    /// Reports whether every expected exchange was consumed.
61    #[must_use]
62    pub fn is_complete(&self) -> bool {
63        self.inner.is_complete()
64    }
65}
66
67impl LocalAsyncTransport for LocalMockTransport<'_> {
68    type Error = MockError;
69
70    async fn send_local<'transport, 'request, 'writer, 'buffer>(
71        &'transport self,
72        request: TransportRequest<'request>,
73        mut response: AsyncResponseStaging<'writer, 'buffer>,
74    ) -> Result<ResponseCompletion, Self::Error>
75    where
76        'transport: 'writer,
77        'request: 'writer,
78        'buffer: 'writer,
79    {
80        self.local_marker.get();
81        self.inner.stage_inner(request, &mut response)
82    }
83}
84
85impl LocalAsyncAuthenticatedTransport for LocalMockTransport<'_> {
86    type Error = MockError;
87
88    async fn send_authenticated_local<'transport, 'request, 'policy, 'writer, 'buffer>(
89        &'transport self,
90        request: AuthenticatedRequest<'request, 'policy>,
91        mut response: AsyncResponseStaging<'writer, 'buffer>,
92    ) -> Result<ResponseCompletion, Self::Error>
93    where
94        'transport: 'writer,
95        'request: 'writer,
96        'policy: 'writer,
97        'buffer: 'writer,
98    {
99        self.local_marker.get();
100        self.inner
101            .stage_inner(request.transport_request(), &mut response)
102    }
103}
104
105impl BoundTransport for LocalMockTransport<'_> {
106    fn endpoint_identity(&self) -> Result<EndpointIdentity<'_>, EndpointIdentityError> {
107        self.inner.endpoint_identity()
108    }
109}
110
111impl BoundCredentialTransport for LocalMockTransport<'_> {
112    fn credential_binding(&self) -> CredentialBinding {
113        self.inner.credential_binding()
114    }
115}
116
117impl fmt::Debug for LocalMockTransport<'_> {
118    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119        formatter
120            .debug_struct("LocalMockTransport")
121            .field("remaining", &self.remaining())
122            .finish_non_exhaustive()
123    }
124}