use momus_core::ast::*;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct TestPlanBuilder {
name: String,
base_url: String,
default_headers: HashMap<String, String>,
steps: Vec<Step>,
setup: Vec<Step>,
teardown: Vec<Step>,
}
impl TestPlanBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
base_url: String::new(),
default_headers: HashMap::new(),
steps: Vec::new(),
setup: Vec::new(),
teardown: Vec::new(),
}
}
pub fn base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn default_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.default_headers.insert(key.into(), value.into());
self
}
pub fn step(mut self, step: Step) -> Self {
self.steps.push(step);
self
}
pub fn setup(mut self, step: Step) -> Self {
self.setup.push(step);
self
}
pub fn teardown(mut self, step: Step) -> Self {
self.teardown.push(step);
self
}
pub fn build(self) -> TestPlan {
TestPlan {
name: self.name,
base_url: self.base_url,
default_headers: self.default_headers,
steps: self.steps,
setup: self.setup,
teardown: self.teardown,
}
}
}
pub fn request(name: impl Into<String>) -> RequestStepBuilder {
RequestStepBuilder {
name: name.into(),
method: Method::Get,
url: String::new(),
headers: HashMap::new(),
body: None,
assert: Vec::new(),
save_as: String::new(),
soft_fail: false,
dataset: None,
}
}
#[derive(Debug, Clone)]
pub struct RequestStepBuilder {
name: String,
method: Method,
url: String,
headers: HashMap<String, String>,
body: Option<serde_json::Value>,
assert: Vec<Assertion>,
save_as: String,
soft_fail: bool,
dataset: Option<momus_core::dataset::DatasetConfig>,
}
impl RequestStepBuilder {
pub fn get(mut self, url: impl Into<String>) -> Self {
self.method = Method::Get;
self.url = url.into();
self
}
pub fn post(mut self, url: impl Into<String>) -> Self {
self.method = Method::Post;
self.url = url.into();
self
}
pub fn put(mut self, url: impl Into<String>) -> Self {
self.method = Method::Put;
self.url = url.into();
self
}
pub fn delete(mut self, url: impl Into<String>) -> Self {
self.method = Method::Delete;
self.url = url.into();
self
}
pub fn patch(mut self, url: impl Into<String>) -> Self {
self.method = Method::Patch;
self.url = url.into();
self
}
pub fn body(mut self, body: serde_json::Value) -> Self {
self.body = Some(body);
self
}
pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(key.into(), value.into());
self
}
pub fn assert(mut self, assertion: Assertion) -> Self {
self.assert.push(assertion);
self
}
pub fn save_as(mut self, name: impl Into<String>) -> Self {
self.save_as = name.into();
self
}
pub fn soft_fail(mut self) -> Self {
self.soft_fail = true;
self
}
pub fn dataset(mut self, config: momus_core::dataset::DatasetConfig) -> Self {
self.dataset = Some(config);
self
}
pub fn build(self) -> Step {
Step::Request(RequestStep {
name: self.name,
method: self.method,
url: self.url,
headers: self.headers,
body: self.body,
assert: self.assert,
save_as: self.save_as,
soft_fail: self.soft_fail,
dataset: self.dataset,
})
}
}
pub fn sequence(name: impl Into<String>) -> SequenceStepBuilder {
SequenceStepBuilder {
name: name.into(),
steps: Vec::new(),
continue_on_failure: false,
}
}
#[derive(Debug, Clone)]
pub struct SequenceStepBuilder {
name: String,
steps: Vec<Step>,
continue_on_failure: bool,
}
impl SequenceStepBuilder {
pub fn step(mut self, step: Step) -> Self {
self.steps.push(step);
self
}
pub fn continue_on_failure(mut self) -> Self {
self.continue_on_failure = true;
self
}
pub fn build(self) -> Step {
Step::Sequence(SequenceStep {
name: self.name,
steps: self.steps,
continue_on_failure: self.continue_on_failure,
})
}
}
pub fn parallel(steps: Vec<Step>) -> Step {
Step::Parallel(ParallelStep { steps })
}
pub fn noop(description: impl Into<String>) -> Step {
Step::Noop {
description: description.into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_simple_plan() {
let plan = TestPlanBuilder::new("test")
.base_url("http://localhost:8080")
.default_header("Accept", "application/json")
.step(
request("health")
.get("/health")
.assert(Assertion::Status(200))
.assert(Assertion::valid_json())
.build(),
)
.build();
assert_eq!(plan.name, "test");
assert_eq!(plan.base_url, "http://localhost:8080");
assert_eq!(plan.default_headers.len(), 1);
assert_eq!(plan.total_tests(), 1);
}
#[test]
fn test_builder_sequence() {
let plan = TestPlanBuilder::new("crud")
.base_url("http://localhost:8080")
.step(
sequence("items")
.step(
request("create")
.post("/items")
.body(serde_json::json!({"name": "test"}))
.assert(Assertion::Status(201))
.save_as("created")
.build(),
)
.step(
request("read")
.get("/items/{steps.created.id}")
.assert(Assertion::Status(200))
.build(),
)
.continue_on_failure()
.build(),
)
.build();
assert_eq!(plan.total_tests(), 2);
}
#[test]
fn test_builder_parallel() {
let plan = TestPlanBuilder::new("parallel")
.base_url("http://localhost:8080")
.step(parallel(vec![
request("a")
.get("/a")
.assert(Assertion::Status(200))
.build(),
request("b")
.get("/b")
.assert(Assertion::Status(200))
.build(),
]))
.build();
assert_eq!(plan.total_tests(), 2);
}
#[test]
fn test_builder_setup_teardown() {
let plan = TestPlanBuilder::new("with-setup")
.base_url("http://localhost:8080")
.setup(
request("init")
.post("/init")
.assert(Assertion::Status(200))
.build(),
)
.step(
request("test")
.get("/test")
.assert(Assertion::Status(200))
.build(),
)
.teardown(
request("cleanup")
.delete("/cleanup")
.assert(Assertion::Status(204))
.build(),
)
.build();
assert_eq!(plan.setup.len(), 1);
assert_eq!(plan.steps.len(), 1);
assert_eq!(plan.teardown.len(), 1);
}
}