Skip to main content

telltale_runtime/effects/
interpreter_testing.rs

1//! Mock handler with scripted responses for interpreter testing.
2
3use super::{
4    async_trait, ChoreoHandler, ChoreoResult, ChoreographyError, DeserializeOwned, RoleId,
5    Serialize,
6};
7use crate::effects::contract::{
8    DeliveryModel, DocumentedHandlerContract, ExtensionDispatchContract, ExtensionDispatchMode,
9    HandlerContractProfile, HandlerContractTier, ProtocolSemanticContract, RetryPolicy,
10    TimeoutPolicy, TransportPolicyContract,
11};
12use std::collections::VecDeque;
13
14/// A mock handler that records operations and provides scripted responses
15pub struct MockHandler<R: RoleId> {
16    /// The role this handler represents (kept for debugging/future use)
17    _role: R,
18    recorded_operations: Vec<MockOperation<R>>,
19    scripted_responses: VecDeque<MockResponse<<R as RoleId>::Label>>,
20}
21
22#[derive(Debug, Clone, PartialEq)]
23pub enum MockOperation<R: RoleId> {
24    Send { to: R, msg_type: String },
25    Recv { from: R },
26    Choose { at: R, label: <R as RoleId>::Label },
27    Offer { from: R },
28}
29
30#[derive(Debug, Clone)]
31pub enum MockResponse<L> {
32    Message(Vec<u8>),
33    Label(L),
34    Error(String),
35}
36
37impl<R: RoleId> MockHandler<R> {
38    pub fn new(role: R) -> Self {
39        Self {
40            _role: role,
41            recorded_operations: Vec::new(),
42            scripted_responses: VecDeque::new(),
43        }
44    }
45
46    pub fn add_response(&mut self, response: MockResponse<<R as RoleId>::Label>) {
47        self.scripted_responses.push_back(response);
48    }
49
50    pub fn operations(&self) -> &[MockOperation<R>] {
51        &self.recorded_operations
52    }
53
54    pub fn clear_operations(&mut self) {
55        self.recorded_operations.clear();
56    }
57}
58
59impl<R: RoleId> DocumentedHandlerContract for MockHandler<R> {
60    fn contract_profile() -> HandlerContractProfile {
61        HandlerContractProfile {
62            handler_name: std::any::type_name::<Self>(),
63            tier: HandlerContractTier::ObservationalHarness,
64            semantics: ProtocolSemanticContract {
65                typed_send_recv_roundtrip: false,
66                exact_choice_label_preservation: true,
67                fail_closed_transport_errors: true,
68                timeouts_scoped_to_enforcing_role: true,
69                deterministic_for_regression: true,
70                can_materialize_values: true,
71            },
72            transport: TransportPolicyContract {
73                delivery_model: DeliveryModel::ScriptedHarness,
74                retry_policy: RetryPolicy::None,
75                timeout_policy: TimeoutPolicy::PassThrough,
76            },
77            extension_dispatch: ExtensionDispatchContract {
78                mode: ExtensionDispatchMode::Unsupported,
79                fail_closed_when_unregistered: false,
80                type_exact_before_side_effects: false,
81            },
82            notes: vec![
83                "values and labels come only from an explicit scripted response queue",
84                "missing scripted responses fail closed instead of synthesizing protocol progress",
85            ],
86        }
87    }
88}
89
90#[async_trait]
91impl<R: RoleId + 'static> ChoreoHandler for MockHandler<R> {
92    type Role = R;
93    type Endpoint = ();
94
95    async fn send<M: Serialize + Send + Sync>(
96        &mut self,
97        _ep: &mut Self::Endpoint,
98        to: Self::Role,
99        _msg: &M,
100    ) -> ChoreoResult<()> {
101        self.recorded_operations.push(MockOperation::Send {
102            to,
103            msg_type: std::any::type_name::<M>().to_string(),
104        });
105        Ok(())
106    }
107
108    async fn recv<M: DeserializeOwned + Send>(
109        &mut self,
110        _ep: &mut Self::Endpoint,
111        from: Self::Role,
112    ) -> ChoreoResult<M> {
113        self.recorded_operations.push(MockOperation::Recv { from });
114
115        if let Some(MockResponse::Message(bytes)) = self.scripted_responses.pop_front() {
116            bincode::deserialize(&bytes)
117                .map_err(|e| ChoreographyError::Serialization(e.to_string()))
118        } else {
119            Err(ChoreographyError::Transport(
120                "No scripted response available".into(),
121            ))
122        }
123    }
124
125    async fn choose(
126        &mut self,
127        _ep: &mut Self::Endpoint,
128        at: Self::Role,
129        label: <Self::Role as RoleId>::Label,
130    ) -> ChoreoResult<()> {
131        self.recorded_operations
132            .push(MockOperation::Choose { at, label });
133        Ok(())
134    }
135
136    async fn offer(
137        &mut self,
138        _ep: &mut Self::Endpoint,
139        from: Self::Role,
140    ) -> ChoreoResult<<Self::Role as RoleId>::Label> {
141        self.recorded_operations.push(MockOperation::Offer { from });
142
143        if let Some(MockResponse::Label(label)) = self.scripted_responses.pop_front() {
144            Ok(label)
145        } else {
146            Err(ChoreographyError::Transport(
147                "No scripted label available".into(),
148            ))
149        }
150    }
151
152    async fn with_timeout<F, T>(
153        &mut self,
154        _ep: &mut Self::Endpoint,
155        _at: Self::Role,
156        _dur: std::time::Duration,
157        body: F,
158    ) -> ChoreoResult<T>
159    where
160        F: std::future::Future<Output = ChoreoResult<T>> + Send,
161    {
162        body.await
163    }
164}