use std::time::Duration;
use super::event::{EventTimeline, MockEvent};
#[derive(Debug, Clone)]
pub struct ScenarioStep {
pub expect: Option<String>,
pub response: Option<String>,
pub delay: Duration,
pub error: Option<String>,
}
impl Default for ScenarioStep {
fn default() -> Self {
Self {
expect: None,
response: None,
delay: Duration::ZERO,
error: None,
}
}
}
impl ScenarioStep {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn expect(mut self, pattern: impl Into<String>) -> Self {
self.expect = Some(pattern.into());
self
}
#[must_use]
pub fn respond(mut self, response: impl Into<String>) -> Self {
self.response = Some(response.into());
self
}
#[must_use]
pub const fn delay(mut self, duration: Duration) -> Self {
self.delay = duration;
self
}
#[must_use]
pub const fn delay_ms(mut self, ms: u64) -> Self {
self.delay = Duration::from_millis(ms);
self
}
#[must_use]
pub fn error(mut self, msg: impl Into<String>) -> Self {
self.error = Some(msg.into());
self
}
}
#[derive(Debug, Clone, Default)]
pub struct Scenario {
name: String,
description: String,
steps: Vec<ScenarioStep>,
initial_output: Option<String>,
exit_code: Option<i32>,
}
impl Scenario {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Default::default()
}
}
#[must_use]
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
#[must_use]
pub fn initial_output(mut self, output: impl Into<String>) -> Self {
self.initial_output = Some(output.into());
self
}
#[must_use]
pub fn step(mut self, step: ScenarioStep) -> Self {
self.steps.push(step);
self
}
#[must_use]
pub fn expect_respond(self, pattern: impl Into<String>, response: impl Into<String>) -> Self {
self.step(ScenarioStep::new().expect(pattern).respond(response))
}
#[must_use]
pub const fn exit_code(mut self, code: i32) -> Self {
self.exit_code = Some(code);
self
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn get_description(&self) -> &str {
&self.description
}
#[must_use]
pub fn steps(&self) -> &[ScenarioStep] {
&self.steps
}
#[must_use]
pub fn to_timeline(&self) -> EventTimeline {
let mut events = Vec::new();
if let Some(output) = &self.initial_output {
events.push(MockEvent::output_str(output));
}
for step in &self.steps {
if !step.delay.is_zero() {
events.push(MockEvent::delay(step.delay));
}
if let Some(error) = &step.error {
events.push(MockEvent::error(error.clone()));
}
if let Some(response) = &step.response {
events.push(MockEvent::output_str(response));
}
}
if let Some(code) = self.exit_code {
events.push(MockEvent::exit(code));
}
EventTimeline::from_events(events)
}
}
pub struct ScenarioBuilder {
scenario: Scenario,
}
impl ScenarioBuilder {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
scenario: Scenario::new(name),
}
}
#[must_use]
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.scenario = self.scenario.description(desc);
self
}
#[must_use]
pub fn initial_output(mut self, output: impl Into<String>) -> Self {
self.scenario = self.scenario.initial_output(output);
self
}
#[must_use]
pub fn login_prompt(self) -> Self {
self.step(ScenarioStep::new().respond("login: "))
}
#[must_use]
pub fn password_prompt(self) -> Self {
self.step(ScenarioStep::new().respond("Password: "))
}
#[must_use]
pub fn shell_prompt(self, prompt: impl Into<String>) -> Self {
self.step(ScenarioStep::new().respond(prompt))
}
#[must_use]
pub fn step(mut self, step: ScenarioStep) -> Self {
self.scenario = self.scenario.step(step);
self
}
#[must_use]
pub fn expect_respond(
mut self,
pattern: impl Into<String>,
response: impl Into<String>,
) -> Self {
self.scenario = self.scenario.expect_respond(pattern, response);
self
}
#[must_use]
pub fn exit_code(mut self, code: i32) -> Self {
self.scenario = self.scenario.exit_code(code);
self
}
#[must_use]
pub fn build(self) -> Scenario {
self.scenario
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scenario_basic() {
let scenario = Scenario::new("test")
.initial_output("Welcome\n")
.expect_respond("login:", "user\n")
.expect_respond("password:", "pass\n")
.exit_code(0);
assert_eq!(scenario.name(), "test");
assert_eq!(scenario.steps().len(), 2);
}
#[test]
fn scenario_to_timeline() {
let scenario = Scenario::new("test").initial_output("Hello\n").exit_code(0);
let timeline = scenario.to_timeline();
assert_eq!(timeline.events().len(), 2);
assert!(timeline.events()[0].is_output());
assert!(timeline.events()[1].is_exit());
}
#[test]
fn scenario_builder() {
let scenario = ScenarioBuilder::new("login")
.description("A login scenario")
.login_prompt()
.password_prompt()
.shell_prompt("$ ")
.exit_code(0)
.build();
assert_eq!(scenario.name(), "login");
assert_eq!(scenario.steps().len(), 3);
}
}