telltale_runtime/effects/
interpreter_testing.rs1use 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
14pub struct MockHandler<R: RoleId> {
16 _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}