Skip to main content

telltale_runtime/effects/
interpreter_testing.rs

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