use mockserver_client::{HttpRequest, MatcherValue};
use serde_json::{json, Value};
#[test]
fn unambiguous_stays_plain_string() {
let cases: &[(MatcherValue, &str)] = &[
(MatcherValue::literal("foo"), r#""foo""#),
(MatcherValue::not_literal("foo"), r#""!foo""#),
(MatcherValue::optional_literal("foo"), r#""?foo""#),
(MatcherValue::literal(""), r#""""#),
(MatcherValue::literal("foo!bar"), r#""foo!bar""#),
(MatcherValue::not_literal("!foo"), r#""!!foo""#),
];
for (value, want) in cases {
let got = serde_json::to_string(value).unwrap();
assert_eq!(&got, want, "serialize {value:?}");
}
}
#[test]
fn ambiguous_uses_object_form_with_explicit_not() {
let cases: &[(MatcherValue, Value)] = &[
(
MatcherValue::literal("!foo"),
json!({"not": false, "value": "!foo"}),
),
(
MatcherValue::literal("?foo"),
json!({"not": false, "value": "?foo"}),
),
(
MatcherValue::literal("?!foo"),
json!({"not": false, "value": "?!foo"}),
),
(
MatcherValue::literal("!?foo"),
json!({"not": false, "value": "!?foo"}),
),
(
MatcherValue::optional_literal("!foo"),
json!({"not": false, "optional": true, "value": "!foo"}),
),
];
for (value, want) in cases {
let got = serde_json::to_value(value).unwrap();
assert_eq!(&got, want, "serialize {value:?}");
}
}
#[test]
fn blank_never_uses_object_form() {
let cases: &[(MatcherValue, &str)] = &[
(MatcherValue::literal(""), ""),
(MatcherValue::literal(" "), ""),
(MatcherValue::not_literal(""), "!"),
];
for (value, want) in cases {
let got = serde_json::to_value(value).unwrap();
let got_str = got
.as_str()
.unwrap_or_else(|| panic!("serialize {value:?} = {got}, want a plain string"));
assert_eq!(got_str, *want, "serialize {value:?}");
}
}
#[test]
fn round_trips_both_wire_forms() {
for wire in [
json!("foo"),
json!("!foo"),
json!({"not": false, "value": "!foo"}),
json!({"not": true, "value": "!foo"}),
json!({"not": false, "optional": true, "value": "?foo"}),
] {
let a: MatcherValue = serde_json::from_value(wire.clone()).unwrap();
let reencoded = serde_json::to_value(&a).unwrap();
let b: MatcherValue = serde_json::from_value(reencoded).unwrap();
assert_eq!(a, b, "round-trip {wire}");
}
}
#[test]
fn negated_object_form_normalises_to_bare_marker() {
let from_object: MatcherValue =
serde_json::from_value(json!({"not": true, "value": "forbidden"})).unwrap();
assert_eq!(from_object, MatcherValue::not_literal("forbidden"));
assert_eq!(
serde_json::to_value(&from_object).unwrap(),
json!("!forbidden")
);
}
#[test]
fn plain_builders_preserve_marker_meaning() {
let req = HttpRequest::new()
.header("X-Neg", "!foo") .query_param("q", "?maybe") .cookie("ck", "plain");
let wire = serde_json::to_value(&req).unwrap();
assert_eq!(wire["headers"]["X-Neg"], json!(["!foo"]));
assert_eq!(wire["queryStringParameters"]["q"], json!(["?maybe"]));
assert_eq!(wire["cookies"]["ck"], json!("plain"));
}
#[test]
fn matcher_builders_emit_object_form_for_literals() {
let req = HttpRequest::new()
.path("/x")
.header_matcher("X-Tag", MatcherValue::literal("!foo"))
.query_param_matcher("q", MatcherValue::literal("?foo"))
.cookie_matcher("ck", MatcherValue::literal("!foo"))
.path_param_matcher("id", MatcherValue::literal("!foo"));
let wire = serde_json::to_value(&req).unwrap();
assert_eq!(
wire["headers"]["X-Tag"][0],
json!({"not": false, "value": "!foo"})
);
assert_eq!(
wire["queryStringParameters"]["q"][0],
json!({"not": false, "value": "?foo"})
);
assert_eq!(
wire["cookies"]["ck"],
json!({"not": false, "value": "!foo"})
);
assert_eq!(
wire["pathParameters"]["id"][0],
json!({"not": false, "value": "!foo"})
);
}
#[test]
fn matcher_builder_negation_stays_bare_marker() {
let req = HttpRequest::new().header_matcher("X-Env", MatcherValue::not_literal("dev"));
let wire = serde_json::to_value(&req).unwrap();
assert_eq!(wire["headers"]["X-Env"], json!(["!dev"]));
}
#[test]
fn object_form_round_trips_through_request_decode() {
let original = HttpRequest::new()
.path("/x")
.header_matcher("X-Tag", MatcherValue::literal("!foo"))
.cookie_matcher("ck", MatcherValue::literal("!foo"));
let wire = serde_json::to_value(&original).unwrap();
let decoded: HttpRequest = serde_json::from_value(wire.clone()).unwrap();
assert_eq!(
decoded.header_matchers.as_ref().unwrap()["X-Tag"],
vec![MatcherValue::literal("!foo")]
);
assert!(decoded.headers.is_none());
assert_eq!(
decoded.cookie_matchers.as_ref().unwrap()["ck"],
MatcherValue::literal("!foo")
);
assert!(decoded.cookies.is_none());
assert_eq!(serde_json::to_value(&decoded).unwrap(), wire);
}
#[test]
fn matcher_builder_migrates_existing_plain_values() {
let req = HttpRequest::new()
.path("/x")
.header("X-Plain", "keep-me")
.header_matcher("X-Tag", MatcherValue::literal("!foo"));
let wire = serde_json::to_value(&req).unwrap();
assert_eq!(wire["headers"]["X-Plain"], json!(["keep-me"]));
assert_eq!(
wire["headers"]["X-Tag"][0],
json!({"not": false, "value": "!foo"})
);
}
#[test]
fn path_param_matcher_replaces_plain_values_for_same_key() {
let req = HttpRequest::new()
.path("/x/{id}")
.path_param("id", "old")
.path_param_matcher("id", MatcherValue::literal("!new"));
let wire = serde_json::to_value(&req).unwrap();
assert_eq!(
wire["pathParameters"]["id"],
json!([{"not": false, "value": "!new"}])
);
}
#[test]
fn plain_form_decodes_unchanged() {
let wire = json!({
"path": "/x",
"headers": {"X-Tag": ["foo", "!bar"]},
"cookies": {"ck": "v"}
});
let decoded: HttpRequest = serde_json::from_value(wire.clone()).unwrap();
assert_eq!(
decoded.headers.as_ref().unwrap()["X-Tag"],
vec!["foo".to_string(), "!bar".to_string()]
);
assert!(decoded.header_matchers.is_none());
assert_eq!(decoded.cookies.as_ref().unwrap()["ck"], "v");
assert!(decoded.cookie_matchers.is_none());
assert_eq!(serde_json::to_value(&decoded).unwrap(), wire);
}