use crate::{
factory,
store::{Store, VariableStore},
RatError, TestResult,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub environment: HashMap<String, serde_json::Value>,
pub tests: Vec<TestConfig>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TestConfig {
#[serde(flatten)]
pub base: TestBaseConfiguration,
#[serde(flatten)]
pub multi_step: MultiStepTestConfig,
#[serde(flatten)]
pub endpoint: TestEndpoint,
}
impl TestConfig {
pub fn log<F>(&self, log: F)
where
F: Fn() -> (),
{
if Some(true) == self.base.verbose {
log()
}
}
pub fn parse(self) -> TestConfiguration {
(self.base, self.endpoint, self.multi_step)
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TestBaseConfiguration {
pub name: String,
pub verbose: Option<bool>,
pub assertions: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TestEndpoint {
pub method: String,
pub url: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MultiStepTestConfig {
pub outputs: HashMap<String, String>,
}
impl TestBaseConfiguration {
pub fn assert<S: Store>(self, store: &S) -> TestResult {
let mut test = true;
let results: Vec<String> = self
.assertions
.iter()
.map(|t| {
let assertion = factory::match_and_replace(store, t.as_ref());
let test_result = evalexpr::eval(&assertion) == Ok(evalexpr::Value::Boolean(true));
test &= test_result;
format!("{} {}", TestResult::report_test(test_result), assertion)
})
.collect();
TestResult {
name: self.name,
result: test,
assertions: results,
}
}
}
pub type TestConfiguration = (TestBaseConfiguration, TestEndpoint, MultiStepTestConfig);
impl Config {
pub fn read() -> Result<Self, RatError> {
let contents = std::fs::read("./config.json")?;
Self::new(contents)
}
pub fn new<T: AsRef<[u8]>>(contents: T) -> Result<Self, RatError> {
let config = serde_json::from_slice(contents.as_ref())?;
Ok(config)
}
pub fn load() -> Result<(VariableStore, Vec<TestConfiguration>), RatError> {
Self::init_logging();
let config = Self::read()?;
let store: VariableStore = config.environment.into();
let tests = config.tests.into_iter().map(|t| t.parse()).collect();
Ok((store, tests))
}
pub fn init_logging() {
use chrono::{DateTime, Utc};
use simplelog::{
ColorChoice, CombinedLogger, ConfigBuilder, TermLogger, TerminalMode, WriteLogger,
};
let datetime: DateTime<Utc> = chrono::offset::Utc::now();
CombinedLogger::init(vec![
TermLogger::new(
log::LevelFilter::Info,
ConfigBuilder::default().build(),
TerminalMode::Mixed,
ColorChoice::Auto,
),
WriteLogger::new(
log::LevelFilter::Debug,
ConfigBuilder::default()
.build(),
std::fs::File::create(format!("{}.log", datetime.format("%Y-%m-%dT%H"))).unwrap(),
),
])
.unwrap();
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test() {
let config = Config::new(multi_step());
assert!(config.is_ok());
let config = config.unwrap();
assert_eq!(config.tests[0].base.name, "get first todo");
assert_eq!(config.tests[0].endpoint.url, "{{base}}/todos/1");
assert_eq!(config.tests[0].multi_step.outputs.len(), 2);
assert_eq!(
config.tests[0]
.multi_step
.outputs
.get("title")
.and_then(|s| Some(s.as_str())),
Some("{{r.body.title}}")
);
}
fn multi_step() -> &'static str {
r#"{
"environment": {
"base": "https://jsonplaceholder.typicode.com"
},
"tests": [
{
"name": "get first todo",
"method": "GET",
"url": "{{base}}/todos/1",
"verbose": true,
"assertions": [
"{{r.status}} == 200",
"{{r.headers.content-length}} > 0",
"\"{{r.headers.content-type}}\" == \"application/json; charset=utf-8\"",
"\"{{r.body.title}}\" == \"delectus aut autem\""
],
"outputs": {
"title": "{{r.body.title}}",
"userId": "{{r.body.userId}}"
}
},
{
"name": "get posts using last steps userId from last step",
"method": "GET",
"url": "{{base}}/posts?userId={{userId}}",
"verbose": true,
"assertions": [
"{{userId}} == 1",
"\"{{title}}\" == \"delectus aut autem\"",
"{{r.status}} == 200",
"\"{{r.headers.content-type}}\" == \"application/json; charset=utf-8\"",
"\"{{r.body.[0].title}}\" == \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\""
],
"outputs": {}
}
]
}
"#
}
}