use std::{cell::RefCell, rc::Rc, time::Duration};
use crate::{FaultPointError, SimulatorFault, TestSimulator, faults::validate_point};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ScenarioTransition {
Started,
Paused,
Resumed,
DurableCommit,
ResponseStarted,
CleanupStarted,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ScenarioTerminal {
Succeeded,
Rejected,
Cancelled,
TimedOut,
Failed,
Uncertain,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ScenarioReceiptEvent {
pub virtual_time: Duration,
pub generation_id: String,
pub operation_id: String,
pub transition: ScenarioTransition,
pub fault: Option<SimulatorFault>,
pub terminal: Option<ScenarioTerminal>,
}
#[derive(Clone, Debug)]
pub struct ScenarioReceipt {
simulator: TestSimulator,
events: Rc<RefCell<Vec<ScenarioReceiptEvent>>>,
}
impl ScenarioReceipt {
pub(crate) fn new(simulator: TestSimulator) -> Self {
Self {
simulator,
events: Rc::new(RefCell::new(Vec::new())),
}
}
pub fn transition(
&self,
generation_id: &str,
operation_id: &str,
transition: ScenarioTransition,
) -> Result<(), FaultPointError> {
self.record(generation_id, operation_id, transition, None, None)
}
pub fn fault(
&self,
generation_id: &str,
operation_id: &str,
transition: ScenarioTransition,
fault: SimulatorFault,
) -> Result<(), FaultPointError> {
self.record(generation_id, operation_id, transition, Some(fault), None)
}
pub fn terminal(
&self,
generation_id: &str,
operation_id: &str,
terminal: ScenarioTerminal,
) -> Result<(), FaultPointError> {
self.record(
generation_id,
operation_id,
ScenarioTransition::ResponseStarted,
None,
Some(terminal),
)
}
pub fn events(&self) -> Vec<ScenarioReceiptEvent> {
self.events.borrow().clone()
}
fn record(
&self,
generation_id: &str,
operation_id: &str,
transition: ScenarioTransition,
fault: Option<SimulatorFault>,
terminal: Option<ScenarioTerminal>,
) -> Result<(), FaultPointError> {
validate_point(generation_id)?;
validate_point(operation_id)?;
self.events.borrow_mut().push(ScenarioReceiptEvent {
virtual_time: self.simulator.now(),
generation_id: generation_id.to_owned(),
operation_id: operation_id.to_owned(),
transition,
fault,
terminal,
});
Ok(())
}
}
impl TestSimulator {
pub fn receipt(&self) -> ScenarioReceipt {
ScenarioReceipt::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn receipt_records_virtual_time_faults_and_uncertain_terminal_state() {
let simulator = TestSimulator::new();
let receipt = simulator.receipt();
receipt
.transition(
"generation-1",
"oauth.consume-1",
ScenarioTransition::Started,
)
.unwrap();
simulator.advance(Duration::from_millis(5));
receipt
.transition(
"generation-1",
"oauth.consume-1",
ScenarioTransition::DurableCommit,
)
.unwrap();
receipt
.fault(
"generation-1",
"oauth.consume-1",
ScenarioTransition::ResponseStarted,
SimulatorFault::DroppedConnection,
)
.unwrap();
receipt
.terminal(
"generation-1",
"oauth.consume-1",
ScenarioTerminal::Uncertain,
)
.unwrap();
assert_eq!(
receipt.events(),
vec![
ScenarioReceiptEvent {
virtual_time: Duration::ZERO,
generation_id: "generation-1".to_owned(),
operation_id: "oauth.consume-1".to_owned(),
transition: ScenarioTransition::Started,
fault: None,
terminal: None,
},
ScenarioReceiptEvent {
virtual_time: Duration::from_millis(5),
generation_id: "generation-1".to_owned(),
operation_id: "oauth.consume-1".to_owned(),
transition: ScenarioTransition::DurableCommit,
fault: None,
terminal: None,
},
ScenarioReceiptEvent {
virtual_time: Duration::from_millis(5),
generation_id: "generation-1".to_owned(),
operation_id: "oauth.consume-1".to_owned(),
transition: ScenarioTransition::ResponseStarted,
fault: Some(SimulatorFault::DroppedConnection),
terminal: None,
},
ScenarioReceiptEvent {
virtual_time: Duration::from_millis(5),
generation_id: "generation-1".to_owned(),
operation_id: "oauth.consume-1".to_owned(),
transition: ScenarioTransition::ResponseStarted,
fault: None,
terminal: Some(ScenarioTerminal::Uncertain),
},
]
);
}
}