use std::hash::{Hash, Hasher};
use itertools::Itertools;
use json_value_merge::Merge;
use serde_json::{json, Value};
use crate::{
gen::string::StringRndGenerator,
model::request::{body::BodyPatternStub, RequestStub},
verify::mapping::jsonpath::JsonGeneratorIterator,
};
use super::super::jsonpath::JsonPathGenerator;
impl From<&RequestStub> for Vec<u8> {
fn from(stub: &RequestStub) -> Self {
stub.body_patterns
.iter()
.map(PartialBody::from)
.find(|it| !it.is_partial())
.and_then(PartialBody::to_bytes)
.unwrap_or_else(|| {
let merged = stub
.body_patterns
.iter()
.map(PartialBody::from)
.unique()
.fold(Value::default(), |mut acc, it| {
if let Some(value) = it.to_partial_value() {
acc.merge(value);
}
acc
});
serde_json::to_vec::<Value>(&merged).unwrap()
})
}
}
#[derive(Default, Eq, Clone)]
struct PartialBody {
path: Option<String>,
bytes: Option<Vec<u8>>,
value: Option<Value>,
}
lazy_static! {
pub static ref EMPTY_JSON_OBJECT: Value = serde_json::json!({});
}
impl PartialBody {
fn is_partial(&self) -> bool {
self.path.is_some()
}
#[allow(clippy::wrong_self_convention)]
fn to_bytes(self) -> Option<Vec<u8>> {
if !self.is_partial() {
self.bytes
.to_owned()
.or_else(|| self.to_value().as_ref().and_then(|it| serde_json::to_vec::<Value>(it).ok()))
} else {
None
}
}
#[allow(clippy::wrong_self_convention)]
fn to_value(self) -> Option<Value> {
if !self.is_partial() {
self.value
} else {
None
}
}
fn to_partial_value(&self) -> Option<Value> {
self.path
.as_deref()
.and_then(|path| JsonPathGenerator(path).next(self.value.clone().unwrap_or_else(|| json!({}))))
}
}
impl From<&BodyPatternStub> for PartialBody {
fn from(stub: &BodyPatternStub) -> Self {
if let Some(binary_equal_to) = stub.binary_equal_to.as_ref() {
use base64::Engine as _;
base64::prelude::BASE64_STANDARD
.decode(binary_equal_to)
.unwrap_or_else(|_| panic!("'{binary_equal_to}' must be Base64 encoded"))
.into()
} else if let Some(expression) = stub.expression.as_ref() {
if let Some(equal_to_json) = stub.equal_to_json.as_ref() {
Self {
path: Some(expression.to_string()),
value: Some(equal_to_json.to_owned()),
..Default::default()
}
} else if let Some(contains) = stub.contains.as_ref() {
let value = StringRndGenerator::generate_string_containing(contains.to_string());
Self {
path: Some(expression.to_string()),
value: Some(Value::String(value)),
..Default::default()
}
} else {
Self::default()
}
} else if let Some(eq) = stub.equal_to_json.as_ref() {
eq.to_owned().into()
} else if let Some(json_path) = stub.matches_json_path.as_ref() {
Self {
path: Some(json_path.to_owned()),
..Default::default()
}
} else {
Self::default()
}
}
}
impl From<Vec<u8>> for PartialBody {
fn from(bytes: Vec<u8>) -> Self {
Self {
bytes: Some(bytes),
..Default::default()
}
}
}
impl From<Value> for PartialBody {
fn from(value: Value) -> Self {
Self {
value: Some(value),
..Default::default()
}
}
}
impl PartialEq for PartialBody {
fn eq(&self, other: &Self) -> bool {
self.path.eq(&other.path)
}
}
impl Hash for PartialBody {
fn hash<H: Hasher>(&self, state: &mut H) {
self.path.hash(state);
}
}
#[cfg(test)]
mod verify_body_tests {
use serde_json::{json, Value};
use super::*;
mod equal_to_json {
use super::*;
#[test]
fn equal_to_json_should_generate_strictly_equal() {
let json = json!({"name": "john", "age": 42});
let stub = BodyPatternStub {
equal_to_json: Some(json.clone()),
..Default::default()
};
assert_eq!(PartialBody::from(&stub).to_value().unwrap(), json);
}
}
mod binary_equal_to {
use super::*;
#[test]
fn binary_equal_to_should_generate_strictly_equal() {
let stub = BodyPatternStub {
binary_equal_to: Some(String::from("AQID")),
..Default::default()
};
assert_eq!(PartialBody::from(&stub).to_bytes().unwrap(), vec![1, 2, 3]);
}
#[should_panic(expected = "'!!!' must be Base64 encoded")]
#[test]
fn binary_equal_to_should_fail_when_not_base64() {
let _ = PartialBody::from(&BodyPatternStub {
binary_equal_to: Some(String::from("!!!")),
..Default::default()
});
}
}
mod expression {
use super::*;
#[test]
fn expression_contains_should_generate_containing() {
let by_contains = BodyPatternStub {
expression: Some(String::from("$.name")),
contains: Some(String::from("a")),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![by_contains],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
let name = body.as_object().unwrap().get("name").unwrap();
assert!(name.as_str().unwrap().contains('a'));
}
#[test]
fn expression_equal_to_json_should_generate_strictly_equal() {
let owner = json!({"name": "john", "age": 42});
let by_eq = BodyPatternStub {
expression: Some(String::from("$.owner")),
equal_to_json: Some(owner.clone()),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![by_eq],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
assert_eq!(body, json!({ "owner": owner }));
}
}
mod many_expression {
use super::*;
#[test]
fn many_expression_equal_to_json_should_generate_combined() {
let alice = json!({"name": "alice"});
let sender = BodyPatternStub {
expression: Some(String::from("$.sender")),
equal_to_json: Some(alice.clone()),
..Default::default()
};
let bob = json!({"name": "bob"});
let receiver = BodyPatternStub {
expression: Some(String::from("$.receiver")),
equal_to_json: Some(bob.clone()),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![sender, receiver],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
assert_eq!(body, json!({"sender": alice, "receiver": bob}));
}
#[test]
fn many_expression_equal_to_json_should_merge_paths() {
let alice = json!({"name": "alice"});
let alice_stub = BodyPatternStub {
expression: Some(String::from("$.person.alice")),
equal_to_json: Some(alice.clone()),
..Default::default()
};
let bob = json!({"name": "bob"});
let bob_stub = BodyPatternStub {
expression: Some(String::from("$.person.bob")),
equal_to_json: Some(bob.clone()),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![alice_stub, bob_stub],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
assert_eq!(body, json!({"person": {"alice": alice, "bob": bob}}));
}
#[test]
fn many_expression_equal_to_json_and_contains_should_generate_combined() {
let alice = json!({"name": "alice"});
let sender = BodyPatternStub {
expression: Some(String::from("$.sender")),
equal_to_json: Some(alice.clone()),
..Default::default()
};
let receiver = BodyPatternStub {
expression: Some(String::from("$.receiver")),
contains: Some(String::from("b")),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![sender, receiver],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
let body = body.as_object().unwrap();
assert_eq!(body.get("sender").unwrap(), &alice);
assert!(body.get("receiver").unwrap().as_str().unwrap().contains('b'));
}
#[test]
fn many_contains_should_generate_combined() {
let sender = BodyPatternStub {
expression: Some(String::from("$.sender")),
contains: Some(String::from("s")),
..Default::default()
};
let receiver = BodyPatternStub {
expression: Some(String::from("$.receiver")),
contains: Some(String::from("r")),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![sender, receiver],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
let body = body.as_object().unwrap();
assert!(body.get("sender").unwrap().as_str().unwrap().contains('s'));
assert!(body.get("receiver").unwrap().as_str().unwrap().contains('r'));
}
}
mod json_path {
use super::*;
#[test]
fn matches_json_path_should_generate_containing_empty_json() {
let jsonpath = BodyPatternStub {
matches_json_path: Some(String::from("$.name")),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![jsonpath],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
assert_eq!(body, json!({"name": {}}));
}
#[test]
fn matches_json_path_and_expression_should_generate_valid_json() {
let owner = json!({"name": "john", "age": 42});
let by_jsonpath = BodyPatternStub {
matches_json_path: Some(String::from("$.other")),
..Default::default()
};
let by_eq = BodyPatternStub {
expression: Some(String::from("$.owner")),
equal_to_json: Some(owner.clone()),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![by_jsonpath, by_eq],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
assert_eq!(body, json!({"other": {}, "owner": owner}));
}
}
mod json_path_filtering {
use super::*;
mod eq {
use super::*;
#[test]
fn matches_json_path_eq_should_generate_containing_filters() {
let jsonpath_alice = BodyPatternStub {
matches_json_path: Some(String::from("$.users[?(@.name == 'alice')]")),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![jsonpath_alice],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
assert_eq!(body, json!({"users": [{"name": "alice"}]}));
}
#[test]
fn matches_many_json_path_eq_should_generate_containing_filters() {
let jsonpath_alice = BodyPatternStub {
matches_json_path: Some(String::from("$.users[?(@.name == 'alice')]")),
..Default::default()
};
let jsonpath_bob = BodyPatternStub {
matches_json_path: Some(String::from("$.users[?(@.name == 'bob')]")),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![jsonpath_alice, jsonpath_bob],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
assert_eq!(body, json!({"users": [{"name": "alice"}, {"name": "bob"}]}));
}
}
}
mod precedence {
use super::*;
#[test]
fn binary_equal_to_should_have_precedence_over_equal_to_json() {
let priority = BodyPatternStub {
binary_equal_to: Some(String::from("AQID")),
..Default::default()
};
let other = BodyPatternStub {
equal_to_json: Some(json!({"name": "jdoe"})),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![priority, other],
..Default::default()
};
assert_eq!(Vec::<u8>::from(&stub).to_vec(), vec![1, 2, 3]);
}
#[test]
fn binary_equal_to_should_have_precedence_over_expression() {
let priority = BodyPatternStub {
binary_equal_to: Some(String::from("AQID")),
..Default::default()
};
let other = BodyPatternStub {
expression: Some(String::from("$.owner")),
equal_to_json: Some(json!({"name": "jdoe"})),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![priority, other],
..Default::default()
};
assert_eq!(Vec::<u8>::from(&stub).to_vec(), vec![1, 2, 3]);
}
#[test]
fn equal_to_json_should_have_precedence_over_expression() {
let jdoe = json!({"name": "jdoe"});
let priority = BodyPatternStub {
equal_to_json: Some(jdoe.clone()),
..Default::default()
};
let other = BodyPatternStub {
expression: Some(String::from("$.owner")),
equal_to_json: Some(jdoe.clone()),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![priority, other],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
assert_eq!(body, jdoe);
}
#[test]
fn expression_equal_to_json_should_have_precedence_over_expression_contains() {
let jdoe = json!({"name": "jdoe"});
let priority = BodyPatternStub {
expression: Some(String::from("$.owner")),
equal_to_json: Some(jdoe.clone()),
..Default::default()
};
let other = BodyPatternStub {
expression: Some(String::from("$.owner")),
contains: Some(String::from("a")),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![priority, other],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
assert_eq!(body, json!({ "owner": jdoe }));
}
#[test]
fn expression_should_have_precedence_over_matches_json_path() {
let jdoe = json!({"name": "jdoe"});
let priority = BodyPatternStub {
expression: Some(String::from("$.owner")),
equal_to_json: Some(jdoe.clone()),
..Default::default()
};
let other = BodyPatternStub {
matches_json_path: Some(String::from("$.owner")),
..Default::default()
};
let stub = RequestStub {
body_patterns: vec![priority, other],
..Default::default()
};
let body = serde_json::from_slice::<Value>(&Vec::<u8>::from(&stub)).unwrap();
assert_eq!(body, json!({ "owner": jdoe }));
}
}
}