use std::fmt::Write;
use std::time::Duration;
use super::event::{EventTimeline, MockEvent};
use super::scenario::{Scenario, ScenarioBuilder, ScenarioStep};
#[must_use]
pub fn login_scenario(username: &str, password: &str) -> Scenario {
ScenarioBuilder::new("login")
.description("Standard login sequence")
.initial_output("Welcome to the system\n\n")
.step(ScenarioStep::new().respond("login: "))
.step(ScenarioStep::new().expect(username).respond("\nPassword: "))
.step(
ScenarioStep::new()
.expect(password)
.respond("\nLast login: Mon Jan 1 00:00:00\n$ "),
)
.exit_code(0)
.build()
}
#[must_use]
pub fn ssh_scenario(host: &str) -> Scenario {
let banner = format!(
"The authenticity of host '{host}' can't be established.\n\
RSA key fingerprint is SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.\n\
Are you sure you want to continue connecting (yes/no)? "
);
ScenarioBuilder::new("ssh")
.description("SSH connection with host key verification")
.step(ScenarioStep::new().respond(&banner))
.step(
ScenarioStep::new()
.expect("yes")
.respond("\nWarning: Permanently added '{}' to the list of known hosts.\n"),
)
.step(ScenarioStep::new().respond("Password: "))
.step(
ScenarioStep::new()
.delay_ms(100)
.respond("\nLast login: Mon Jan 1 00:00:00\n$ "),
)
.exit_code(0)
.build()
}
#[must_use]
pub fn shell_scenario(prompt: &str) -> Scenario {
ScenarioBuilder::new("shell")
.description("Interactive shell session")
.initial_output(prompt)
.exit_code(0)
.build()
}
#[must_use]
pub fn command_scenario(command: &str, output: &str, exit_code: i32) -> Scenario {
ScenarioBuilder::new("command")
.description("Command execution")
.step(
ScenarioStep::new()
.expect(command)
.delay_ms(50)
.respond(output),
)
.exit_code(exit_code)
.build()
}
#[must_use]
pub fn sudo_scenario(command: &str) -> Scenario {
ScenarioBuilder::new("sudo")
.description("Sudo password prompt")
.step(ScenarioStep::new().respond("[sudo] password: "))
.step(
ScenarioStep::new()
.delay_ms(100)
.respond(format!("\nExecuting: {command}\n")),
)
.exit_code(0)
.build()
}
#[must_use]
pub fn menu_scenario(options: &[&str]) -> Scenario {
let mut menu = String::from("Please select an option:\n");
for (i, option) in options.iter().enumerate() {
let _ = writeln!(menu, " {}. {}", i + 1, option);
}
menu.push_str("Enter your choice: ");
ScenarioBuilder::new("menu")
.description("Menu selection")
.step(ScenarioStep::new().respond(&menu))
.exit_code(0)
.build()
}
#[must_use]
pub fn yes_no_scenario(question: &str) -> Scenario {
ScenarioBuilder::new("yesno")
.description("Yes/No prompt")
.step(ScenarioStep::new().respond(format!("{question} [y/n]: ")))
.exit_code(0)
.build()
}
#[must_use]
pub fn file_transfer_scenario(filename: &str, size_kb: usize) -> Scenario {
let mut builder = ScenarioBuilder::new("transfer")
.description("File transfer with progress")
.initial_output(format!("Transferring {filename}...\n"));
let steps = 10;
let chunk_size = size_kb / steps;
for i in 1..=steps {
let progress = i * 10;
let transferred = chunk_size * i;
builder = builder.step(ScenarioStep::new().delay_ms(100).respond(format!(
"\r[{}{}] {}% ({}/{}KB)",
"=".repeat(i),
" ".repeat(steps - i),
progress,
transferred,
size_kb
)));
}
builder
.step(ScenarioStep::new().respond(format!("\n{filename} transferred successfully.\n")))
.exit_code(0)
.build()
}
#[must_use]
pub fn error_scenario(error_message: &str, exit_code: i32) -> Scenario {
ScenarioBuilder::new("error")
.description("Error scenario")
.step(ScenarioStep::new().respond(format!("Error: {error_message}\n")))
.exit_code(exit_code)
.build()
}
#[must_use]
pub fn timeout_scenario(delay: Duration) -> Scenario {
ScenarioBuilder::new("timeout")
.description("Delayed response scenario")
.step(ScenarioStep::new().delay(delay))
.exit_code(0)
.build()
}
#[must_use]
pub fn interactive_prompts(prompts: &[(&str, &str)]) -> Scenario {
let mut builder = ScenarioBuilder::new("interactive").description("Interactive prompts");
for (prompt, response) in prompts {
builder = builder.step(ScenarioStep::new().respond(*prompt));
builder = builder.step(ScenarioStep::new().expect(*response));
}
builder.exit_code(0).build()
}
#[must_use]
pub fn bash_session() -> EventTimeline {
EventTimeline::from_events(vec![
MockEvent::output_str("bash-5.0$ "),
MockEvent::Delay(Duration::from_millis(10)),
])
}
#[must_use]
pub fn python_repl() -> EventTimeline {
EventTimeline::from_events(vec![
MockEvent::output_str("Python 3.10.0 (default, Jan 1 2024, 00:00:00)\n"),
MockEvent::output_str("[GCC 9.3.0] on linux\n"),
MockEvent::output_str(
"Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n",
),
MockEvent::output_str(">>> "),
])
}
#[must_use]
pub fn node_repl() -> EventTimeline {
EventTimeline::from_events(vec![
MockEvent::output_str("Welcome to Node.js v18.0.0.\n"),
MockEvent::output_str("Type \".help\" for more information.\n"),
MockEvent::output_str("> "),
])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn login_scenario_creates_valid_scenario() {
let scenario = login_scenario("user", "pass");
assert_eq!(scenario.name(), "login");
assert!(!scenario.steps().is_empty());
}
#[test]
fn menu_scenario_creates_valid_menu() {
let scenario = menu_scenario(&["Option A", "Option B", "Option C"]);
let timeline = scenario.to_timeline();
assert!(!timeline.events().is_empty());
}
#[test]
fn file_transfer_has_progress() {
let scenario = file_transfer_scenario("test.txt", 1000);
let timeline = scenario.to_timeline();
assert!(timeline.events().len() > 10); }
}