use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Assertion {
AllOf(Vec<Assertion>),
AnyOf(Vec<Assertion>),
Not(Box<Assertion>),
Status(u16),
StatusIn(Vec<u16>),
Header {
name: String,
predicate: ValuePredicate,
},
BodyLength(BodyLengthPredicate),
JsonPath {
path: String,
predicate: JsonPredicate,
},
Schema {
schema: serde_json::Value,
},
ValidJson,
ContentType(String),
ResponseTime(u64),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ValuePredicate {
Eq(String),
Contains(String),
Regex(String),
Present,
Absent,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum JsonPredicate {
Exists,
NotExists,
Eq(serde_json::Value),
NotEq(serde_json::Value),
Cmp { op: CmpOp, value: serde_json::Value },
Length(LengthPredicate),
Every(Box<JsonPredicate>),
Some(Box<JsonPredicate>),
Count(CountPredicate),
Schema(serde_json::Value),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CmpOp {
Gt,
Lt,
Ge,
Le,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BodyLengthPredicate {
Eq(usize),
Min(usize),
Max(usize),
Range { min: usize, max: usize },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LengthPredicate {
Eq(usize),
Min(usize),
Max(usize),
Range { min: usize, max: usize },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CountPredicate {
Eq(usize),
Min(usize),
Max(usize),
Range { min: usize, max: usize },
}
impl Assertion {
pub fn status(code: u16) -> Self {
Assertion::Status(code)
}
pub fn status_in(codes: Vec<u16>) -> Self {
Assertion::StatusIn(codes)
}
pub fn json_path_exists(path: impl Into<String>) -> Self {
Assertion::JsonPath {
path: path.into(),
predicate: JsonPredicate::Exists,
}
}
pub fn json_path_eq(path: impl Into<String>, value: serde_json::Value) -> Self {
Assertion::JsonPath {
path: path.into(),
predicate: JsonPredicate::Eq(value),
}
}
pub fn header(name: impl Into<String>, predicate: ValuePredicate) -> Self {
Assertion::Header {
name: name.into(),
predicate,
}
}
pub fn content_type(ct: impl Into<String>) -> Self {
Assertion::ContentType(ct.into())
}
pub fn valid_json() -> Self {
Assertion::ValidJson
}
pub fn schema(schema: serde_json::Value) -> Self {
Assertion::Schema { schema }
}
pub fn response_time(max_millis: u64) -> Self {
Assertion::ResponseTime(max_millis)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssertionResult {
pub description: String,
pub passed: bool,
pub message: Option<String>,
#[serde(default)]
pub children: Vec<AssertionResult>,
}
impl AssertionResult {
pub fn pass(description: impl Into<String>) -> Self {
Self {
description: description.into(),
passed: true,
message: None,
children: vec![],
}
}
pub fn fail(description: impl Into<String>, message: impl Into<String>) -> Self {
Self {
description: description.into(),
passed: false,
message: Some(message.into()),
children: vec![],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_assertion_constructors() {
let a = Assertion::status(200);
assert_eq!(a, Assertion::Status(200));
let b = Assertion::status_in(vec![200, 304]);
assert_eq!(b, Assertion::StatusIn(vec![200, 304]));
}
#[test]
fn json_path_constructors() {
let a = Assertion::json_path_exists("$.resourceType");
assert_eq!(
a,
Assertion::JsonPath {
path: "$.resourceType".into(),
predicate: JsonPredicate::Exists,
}
);
let b = Assertion::json_path_eq("$.total", serde_json::json!(42));
assert_eq!(
b,
Assertion::JsonPath {
path: "$.total".into(),
predicate: JsonPredicate::Eq(serde_json::json!(42)),
}
);
}
#[test]
fn assertion_result_pass() {
let r = AssertionResult::pass("status is 200");
assert!(r.passed);
assert!(r.message.is_none());
}
#[test]
fn assertion_result_fail() {
let r = AssertionResult::fail("status is 200", "got 404");
assert!(!r.passed);
assert_eq!(r.message.unwrap(), "got 404");
}
#[test]
fn assertion_result_with_children() {
let r = AssertionResult {
description: "all of".into(),
passed: false,
message: Some("failed: status is 200".into()),
children: vec![AssertionResult::fail("status is 200", "got 404")],
};
assert!(!r.passed);
assert_eq!(r.children.len(), 1);
assert!(!r.children[0].passed);
}
#[test]
fn test_assertion_serialization_roundtrip() {
let assertions = vec![
Assertion::Status(200),
Assertion::StatusIn(vec![200, 304]),
Assertion::Header {
name: "content-type".into(),
predicate: ValuePredicate::Contains("json".into()),
},
Assertion::BodyLength(BodyLengthPredicate::Min(10)),
Assertion::JsonPath {
path: "$.resourceType".into(),
predicate: JsonPredicate::Eq(serde_json::json!("Patient")),
},
Assertion::Schema {
schema: serde_json::json!({"type": "object"}),
},
Assertion::ValidJson,
Assertion::ContentType("json".into()),
Assertion::ResponseTime(500),
Assertion::AllOf(vec![Assertion::Status(200), Assertion::ValidJson]),
Assertion::AnyOf(vec![Assertion::Status(200), Assertion::Status(304)]),
Assertion::Not(Box::new(Assertion::Status(404))),
];
for assertion in &assertions {
let json = serde_json::to_string(assertion).unwrap();
let deserialized: Assertion = serde_json::from_str(&json).unwrap();
assert_eq!(
*assertion, deserialized,
"round-trip failed for {:?}",
assertion
);
}
}
#[test]
fn test_value_predicate_serialization_roundtrip() {
let predicates = vec![
ValuePredicate::Eq("value".into()),
ValuePredicate::Contains("sub".into()),
ValuePredicate::Regex("^pattern$".into()),
ValuePredicate::Present,
ValuePredicate::Absent,
];
for predicate in &predicates {
let json = serde_json::to_string(predicate).unwrap();
let deserialized: ValuePredicate = serde_json::from_str(&json).unwrap();
assert_eq!(*predicate, deserialized);
}
}
#[test]
fn test_json_predicate_serialization_roundtrip() {
let predicates = vec![
JsonPredicate::Exists,
JsonPredicate::NotExists,
JsonPredicate::Eq(serde_json::json!("test")),
JsonPredicate::NotEq(serde_json::json!(42)),
JsonPredicate::Cmp {
op: CmpOp::Gt,
value: serde_json::json!(10),
},
JsonPredicate::Length(LengthPredicate::Eq(3)),
JsonPredicate::Every(Box::new(JsonPredicate::Exists)),
JsonPredicate::Some(Box::new(JsonPredicate::Eq(serde_json::json!(1)))),
JsonPredicate::Count(CountPredicate::Min(1)),
JsonPredicate::Schema(serde_json::json!({"type": "object"})),
];
for predicate in &predicates {
let json = serde_json::to_string(predicate).unwrap();
let deserialized: JsonPredicate = serde_json::from_str(&json).unwrap();
assert_eq!(*predicate, deserialized);
}
}
#[test]
fn test_predicate_serialization_roundtrip() {
let predicates: Vec<BodyLengthPredicate> = vec![
BodyLengthPredicate::Eq(100),
BodyLengthPredicate::Min(10),
BodyLengthPredicate::Max(1000),
BodyLengthPredicate::Range { min: 10, max: 100 },
];
for pred in &predicates {
let json = serde_json::to_string(pred).unwrap();
let deserialized: BodyLengthPredicate = serde_json::from_str(&json).unwrap();
assert_eq!(*pred, deserialized);
}
let length_preds: Vec<LengthPredicate> = vec![
LengthPredicate::Eq(5),
LengthPredicate::Min(1),
LengthPredicate::Max(10),
LengthPredicate::Range { min: 1, max: 10 },
];
for pred in &length_preds {
let json = serde_json::to_string(pred).unwrap();
let deserialized: LengthPredicate = serde_json::from_str(&json).unwrap();
assert_eq!(*pred, deserialized);
}
let count_preds: Vec<CountPredicate> = vec![
CountPredicate::Eq(3),
CountPredicate::Min(0),
CountPredicate::Max(100),
CountPredicate::Range { min: 1, max: 5 },
];
for pred in &count_preds {
let json = serde_json::to_string(pred).unwrap();
let deserialized: CountPredicate = serde_json::from_str(&json).unwrap();
assert_eq!(*pred, deserialized);
}
}
#[test]
fn test_assertion_result_serialization_roundtrip() {
let result = AssertionResult {
description: "all of".into(),
passed: false,
message: Some("failed: status is 200".into()),
children: vec![AssertionResult::fail("status is 200", "got 404")],
};
let json = serde_json::to_string(&result).unwrap();
let deserialized: AssertionResult = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.description, result.description);
assert_eq!(deserialized.passed, result.passed);
assert_eq!(deserialized.children.len(), result.children.len());
}
}