1use std::{cell::RefCell, rc::Rc, time::Duration};
4
5use crate::{FaultPointError, SimulatorFault, TestSimulator, faults::validate_point};
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum ScenarioTransition {
10 Started,
12 Paused,
14 Resumed,
16 DurableCommit,
18 ResponseStarted,
20 CleanupStarted,
22}
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum ScenarioTerminal {
27 Succeeded,
29 Rejected,
31 Cancelled,
33 TimedOut,
35 Failed,
37 Uncertain,
39}
40
41#[derive(Clone, Debug, Eq, PartialEq)]
43pub struct ScenarioReceiptEvent {
44 pub virtual_time: Duration,
46 pub generation_id: String,
48 pub operation_id: String,
50 pub transition: ScenarioTransition,
52 pub fault: Option<SimulatorFault>,
54 pub terminal: Option<ScenarioTerminal>,
56}
57
58#[derive(Clone, Debug)]
63pub struct ScenarioReceipt {
64 simulator: TestSimulator,
65 events: Rc<RefCell<Vec<ScenarioReceiptEvent>>>,
66}
67
68impl ScenarioReceipt {
69 pub(crate) fn new(simulator: TestSimulator) -> Self {
70 Self {
71 simulator,
72 events: Rc::new(RefCell::new(Vec::new())),
73 }
74 }
75
76 pub fn transition(
78 &self,
79 generation_id: &str,
80 operation_id: &str,
81 transition: ScenarioTransition,
82 ) -> Result<(), FaultPointError> {
83 self.record(generation_id, operation_id, transition, None, None)
84 }
85
86 pub fn fault(
88 &self,
89 generation_id: &str,
90 operation_id: &str,
91 transition: ScenarioTransition,
92 fault: SimulatorFault,
93 ) -> Result<(), FaultPointError> {
94 self.record(generation_id, operation_id, transition, Some(fault), None)
95 }
96
97 pub fn terminal(
99 &self,
100 generation_id: &str,
101 operation_id: &str,
102 terminal: ScenarioTerminal,
103 ) -> Result<(), FaultPointError> {
104 self.record(
105 generation_id,
106 operation_id,
107 ScenarioTransition::ResponseStarted,
108 None,
109 Some(terminal),
110 )
111 }
112
113 pub fn events(&self) -> Vec<ScenarioReceiptEvent> {
115 self.events.borrow().clone()
116 }
117
118 fn record(
119 &self,
120 generation_id: &str,
121 operation_id: &str,
122 transition: ScenarioTransition,
123 fault: Option<SimulatorFault>,
124 terminal: Option<ScenarioTerminal>,
125 ) -> Result<(), FaultPointError> {
126 validate_point(generation_id)?;
127 validate_point(operation_id)?;
128 self.events.borrow_mut().push(ScenarioReceiptEvent {
129 virtual_time: self.simulator.now(),
130 generation_id: generation_id.to_owned(),
131 operation_id: operation_id.to_owned(),
132 transition,
133 fault,
134 terminal,
135 });
136 Ok(())
137 }
138}
139
140impl TestSimulator {
141 pub fn receipt(&self) -> ScenarioReceipt {
143 ScenarioReceipt::new(self.clone())
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn receipt_records_virtual_time_faults_and_uncertain_terminal_state() {
153 let simulator = TestSimulator::new();
154 let receipt = simulator.receipt();
155
156 receipt
157 .transition(
158 "generation-1",
159 "oauth.consume-1",
160 ScenarioTransition::Started,
161 )
162 .unwrap();
163 simulator.advance(Duration::from_millis(5));
164 receipt
165 .transition(
166 "generation-1",
167 "oauth.consume-1",
168 ScenarioTransition::DurableCommit,
169 )
170 .unwrap();
171 receipt
172 .fault(
173 "generation-1",
174 "oauth.consume-1",
175 ScenarioTransition::ResponseStarted,
176 SimulatorFault::DroppedConnection,
177 )
178 .unwrap();
179 receipt
180 .terminal(
181 "generation-1",
182 "oauth.consume-1",
183 ScenarioTerminal::Uncertain,
184 )
185 .unwrap();
186
187 assert_eq!(
188 receipt.events(),
189 vec![
190 ScenarioReceiptEvent {
191 virtual_time: Duration::ZERO,
192 generation_id: "generation-1".to_owned(),
193 operation_id: "oauth.consume-1".to_owned(),
194 transition: ScenarioTransition::Started,
195 fault: None,
196 terminal: None,
197 },
198 ScenarioReceiptEvent {
199 virtual_time: Duration::from_millis(5),
200 generation_id: "generation-1".to_owned(),
201 operation_id: "oauth.consume-1".to_owned(),
202 transition: ScenarioTransition::DurableCommit,
203 fault: None,
204 terminal: None,
205 },
206 ScenarioReceiptEvent {
207 virtual_time: Duration::from_millis(5),
208 generation_id: "generation-1".to_owned(),
209 operation_id: "oauth.consume-1".to_owned(),
210 transition: ScenarioTransition::ResponseStarted,
211 fault: Some(SimulatorFault::DroppedConnection),
212 terminal: None,
213 },
214 ScenarioReceiptEvent {
215 virtual_time: Duration::from_millis(5),
216 generation_id: "generation-1".to_owned(),
217 operation_id: "oauth.consume-1".to_owned(),
218 transition: ScenarioTransition::ResponseStarted,
219 fault: None,
220 terminal: Some(ScenarioTerminal::Uncertain),
221 },
222 ]
223 );
224 }
225}