707 lines
17 KiB
Rust
707 lines
17 KiB
Rust
#![cfg(feature = "redactions")]
|
|
|
|
use insta::_macro_support::Selector;
|
|
#[cfg(feature = "csv")]
|
|
use insta::assert_csv_snapshot;
|
|
#[cfg(feature = "json")]
|
|
use insta::assert_json_snapshot;
|
|
#[cfg(feature = "ron")]
|
|
use insta::assert_ron_snapshot;
|
|
#[cfg(feature = "toml")]
|
|
use insta::assert_toml_snapshot;
|
|
#[cfg(feature = "yaml")]
|
|
use insta::assert_yaml_snapshot;
|
|
|
|
use insta::assert_debug_snapshot;
|
|
use serde::Serialize;
|
|
|
|
#[test]
|
|
fn test_selector_parser() {
|
|
macro_rules! assert_selector_snapshot {
|
|
($short:expr, $sel:expr) => {
|
|
assert_debug_snapshot!($short, Selector::parse($sel).unwrap());
|
|
};
|
|
}
|
|
|
|
assert_selector_snapshot!("foo_bar", ".foo.bar");
|
|
assert_selector_snapshot!("foo_bar_alt", ".foo[\"bar\"]");
|
|
assert_selector_snapshot!("foo_bar_full_range", ".foo.bar[]");
|
|
assert_selector_snapshot!("foo_bar_range_to", ".foo.bar[:10]");
|
|
assert_selector_snapshot!("foo_bar_range_from", ".foo.bar[10:]");
|
|
assert_selector_snapshot!("foo_bar_range", ".foo.bar[10:20]");
|
|
assert_selector_snapshot!("foo_bar_deep", ".foo.bar.**");
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct Email(String);
|
|
|
|
#[derive(Serialize)]
|
|
pub struct User {
|
|
id: u32,
|
|
username: String,
|
|
email: Email,
|
|
extra: String,
|
|
}
|
|
|
|
#[cfg(feature = "yaml")]
|
|
#[test]
|
|
fn test_with_random_value() {
|
|
assert_yaml_snapshot!(&User {
|
|
id: 42,
|
|
username: "john_doe".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
}, {
|
|
".id" => "[id]"
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "yaml")]
|
|
#[test]
|
|
fn test_with_random_value_inline_callback() {
|
|
assert_yaml_snapshot!(&User {
|
|
id: 23,
|
|
username: "john_doe".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
}, {
|
|
".id" => insta::dynamic_redaction(|value, path| {
|
|
similar_asserts::assert_eq!(path.to_string(), ".id");
|
|
similar_asserts::assert_eq!(value.as_u64().unwrap(), 23);
|
|
"[id]"
|
|
}),
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "yaml")]
|
|
#[test]
|
|
fn test_with_random_value_and_trailing_comma() {
|
|
assert_yaml_snapshot!(&User {
|
|
id: 11,
|
|
username: "john_doe".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
}, {
|
|
".id" => "[id]",
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "yaml")]
|
|
#[test]
|
|
fn test_with_random_value_and_match_comma() {
|
|
assert_yaml_snapshot!(
|
|
&User {
|
|
id: 11,
|
|
username: "john_doe".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
},
|
|
match .. {
|
|
".id" => "[id]",
|
|
}
|
|
);
|
|
assert_yaml_snapshot!(
|
|
&User {
|
|
id: 11,
|
|
username: "john_doe".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
},
|
|
match .. {
|
|
".id" => "[id]",
|
|
}, // comma here
|
|
);
|
|
assert_yaml_snapshot!(
|
|
&User {
|
|
id: 11,
|
|
username: "john_doe".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
},
|
|
match .. {
|
|
".id" => "[id]",
|
|
},
|
|
@r#"
|
|
id: "[id]"
|
|
username: john_doe
|
|
email: john@example.com
|
|
extra: ""
|
|
"#, // comma here
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "csv")]
|
|
#[test]
|
|
fn test_with_random_value_csv() {
|
|
assert_csv_snapshot!("user_csv", &User {
|
|
id: 44,
|
|
username: "julius_csv".to_string(),
|
|
email: Email("julius@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
}, {
|
|
".id" => "[id]"
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "csv")]
|
|
#[test]
|
|
fn test_with_random_value_csv_match() {
|
|
assert_csv_snapshot!(
|
|
&User {
|
|
id: 44,
|
|
username: "julius_csv".to_string(),
|
|
email: Email("julius@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
},
|
|
match .. {
|
|
".id" => "[id]",
|
|
}
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "ron")]
|
|
#[test]
|
|
fn test_with_random_value_ron() {
|
|
assert_ron_snapshot!("user_ron", &User {
|
|
id: 53,
|
|
username: "john_ron".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
}, {
|
|
".id" => "[id]"
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "ron")]
|
|
#[test]
|
|
fn test_with_random_value_ron_match() {
|
|
assert_ron_snapshot!(
|
|
&User {
|
|
id: 53,
|
|
username: "john_ron".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
},
|
|
match .. {
|
|
".id" => "[id]",
|
|
}
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "toml")]
|
|
#[test]
|
|
fn test_with_random_value_toml() {
|
|
assert_toml_snapshot!("user_toml", &User {
|
|
id: 53,
|
|
username: "john_ron".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
}, {
|
|
".id" => "[id]"
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "toml")]
|
|
#[test]
|
|
fn test_with_random_value_toml_match() {
|
|
assert_toml_snapshot!(
|
|
&User {
|
|
id: 53,
|
|
username: "john_ron".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
},
|
|
match .. {
|
|
".id" => "[id]",
|
|
}
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_with_random_value_json() {
|
|
assert_json_snapshot!("user_json", &User {
|
|
id: 9999,
|
|
username: "jason_doe".to_string(),
|
|
email: Email("jason@example.com".to_string()),
|
|
extra: "ssn goes here".to_string(),
|
|
}, {
|
|
".id" => "[id]",
|
|
".extra" => "[extra]"
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_with_random_value_json_match() {
|
|
assert_json_snapshot!(
|
|
&User {
|
|
id: 9999,
|
|
username: "jason_doe".to_string(),
|
|
email: Email("jason@example.com".to_string()),
|
|
extra: "ssn goes here".to_string(),
|
|
},
|
|
match .. {
|
|
".id" => "[id]",
|
|
".extra" => "[extra]",
|
|
}
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_with_random_value_json_settings() {
|
|
let mut settings = insta::Settings::new();
|
|
settings.add_redaction(".id", "[id]");
|
|
settings.add_redaction(".extra", "[extra]");
|
|
settings.bind(|| {
|
|
assert_json_snapshot!(
|
|
"user_json_settings",
|
|
&User {
|
|
id: 122,
|
|
username: "jason_doe".to_string(),
|
|
email: Email("jason@example.com".to_string()),
|
|
extra: "ssn goes here".to_string(),
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_with_callbacks() {
|
|
let mut settings = insta::Settings::new();
|
|
settings.add_dynamic_redaction(".id", |value, path| {
|
|
similar_asserts::assert_eq!(path.to_string(), ".id");
|
|
similar_asserts::assert_eq!(value.as_u64().unwrap(), 1234);
|
|
"[id]"
|
|
});
|
|
settings.bind(|| {
|
|
assert_json_snapshot!(
|
|
"user_json_settings_callback",
|
|
&User {
|
|
id: 1234,
|
|
username: "jason_doe".to_string(),
|
|
email: Email("jason@example.com".to_string()),
|
|
extra: "extra here".to_string(),
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_with_random_value_json_settings2() {
|
|
insta::with_settings!({redactions => vec![
|
|
(".id", "[id]".into()),
|
|
(".extra", "[extra]".into()),
|
|
]}, {
|
|
assert_json_snapshot!(
|
|
&User {
|
|
id: 975,
|
|
username: "jason_doe".to_string(),
|
|
email: Email("jason@example.com".to_string()),
|
|
extra: "ssn goes here".to_string(),
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_redact_newtype_struct() {
|
|
#[derive(Serialize)]
|
|
pub struct UserWrapper(User);
|
|
|
|
let wrapper = UserWrapper(User {
|
|
id: 42,
|
|
username: "john_doe".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
});
|
|
|
|
assert_json_snapshot!(wrapper, {
|
|
r#".id"# => "[id]"
|
|
}, @r#"
|
|
{
|
|
"id": "[id]",
|
|
"username": "john_doe",
|
|
"email": "john@example.com",
|
|
"extra": ""
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[cfg(feature = "yaml")]
|
|
#[test]
|
|
fn test_redact_newtype_enum() {
|
|
#[derive(Serialize)]
|
|
pub enum Role {
|
|
Admin(User),
|
|
Visitor { id: String, name: String },
|
|
}
|
|
|
|
let visitor = Role::Visitor {
|
|
id: "my-id".into(),
|
|
name: "my-name".into(),
|
|
};
|
|
assert_yaml_snapshot!(visitor, {
|
|
r#".id"# => "[id]",
|
|
}, @r#"
|
|
Visitor:
|
|
id: "[id]"
|
|
name: my-name
|
|
"#);
|
|
|
|
let admin = Role::Admin(User {
|
|
id: 42,
|
|
username: "john_doe".to_string(),
|
|
email: Email("john@example.com".to_string()),
|
|
extra: "".to_string(),
|
|
});
|
|
assert_yaml_snapshot!(admin, {
|
|
r#".id"# => "[id]",
|
|
}, @r#"
|
|
Admin:
|
|
id: "[id]"
|
|
username: john_doe
|
|
email: john@example.com
|
|
extra: ""
|
|
"#);
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_redact_recursive() {
|
|
#[derive(Serialize)]
|
|
pub struct Node {
|
|
id: u64,
|
|
next: Option<Box<Node>>,
|
|
}
|
|
|
|
let root = Node {
|
|
id: 0,
|
|
next: Some(Box::new(Node { id: 1, next: None })),
|
|
};
|
|
|
|
assert_json_snapshot!(root, {
|
|
".**.id" => "[id]",
|
|
}, @r#"
|
|
{
|
|
"id": "[id]",
|
|
"next": {
|
|
"id": "[id]",
|
|
"next": null
|
|
}
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_redact_recursive_array() {
|
|
// A deep wildcard recurses through arrays, not just nested structs: `.**.data`
|
|
// matches every `data` field at any depth inside the `nodes` vectors. A bare
|
|
// `.**` would instead match each `nodes` array itself and stop descending, so
|
|
// only the root would be redacted. See https://github.com/mitsuhiko/insta/issues/687.
|
|
#[derive(Serialize)]
|
|
pub struct Node {
|
|
data: f64,
|
|
nodes: Vec<Node>,
|
|
}
|
|
|
|
let root = Node {
|
|
data: 1.111_111,
|
|
nodes: vec![
|
|
Node {
|
|
data: 2.222_222,
|
|
nodes: vec![Node {
|
|
data: 3.333_333,
|
|
nodes: vec![],
|
|
}],
|
|
},
|
|
Node {
|
|
data: 4.444_444,
|
|
nodes: vec![],
|
|
},
|
|
],
|
|
};
|
|
|
|
assert_json_snapshot!(root, {
|
|
".**.data" => insta::rounded_redaction(2),
|
|
}, @r#"
|
|
{
|
|
"data": 1.11,
|
|
"nodes": [
|
|
{
|
|
"data": 2.22,
|
|
"nodes": [
|
|
{
|
|
"data": 3.33,
|
|
"nodes": []
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"data": 4.44,
|
|
"nodes": []
|
|
}
|
|
]
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[cfg(feature = "yaml")]
|
|
#[test]
|
|
fn test_struct_array_redaction() {
|
|
#[derive(Serialize)]
|
|
pub struct Product {
|
|
_id: String,
|
|
product_name: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct Checkout {
|
|
_id: String,
|
|
products: Vec<Product>,
|
|
}
|
|
|
|
let checkout = Checkout {
|
|
_id: "checkout/1".to_string(),
|
|
products: vec![
|
|
Product {
|
|
_id: "product/1".to_string(),
|
|
product_name: "a car".to_string(),
|
|
},
|
|
Product {
|
|
_id: "product/2".to_string(),
|
|
product_name: "a boat".to_string(),
|
|
},
|
|
],
|
|
};
|
|
|
|
assert_yaml_snapshot!(vec![checkout], {
|
|
"[]._id" => "[checkout_id]",
|
|
"[].products[]._id" => "[product_id]",
|
|
"[].products[].product_name" => "[product_name]",
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "yaml")]
|
|
#[test]
|
|
fn test_map_key_redaction() {
|
|
#[derive(Serialize, Hash, PartialEq, PartialOrd, Eq, Ord)]
|
|
struct Key {
|
|
bucket: u32,
|
|
value: u32,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct Foo {
|
|
hm: std::collections::HashMap<Key, u32>,
|
|
btm: std::collections::BTreeMap<(u32, u32), u32>,
|
|
}
|
|
|
|
let mut hm = std::collections::HashMap::new();
|
|
hm.insert(
|
|
Key {
|
|
bucket: 1,
|
|
value: 0,
|
|
},
|
|
42,
|
|
);
|
|
let mut btm = std::collections::BTreeMap::new();
|
|
btm.insert((0, 0), 23);
|
|
let foo_value = Foo { hm, btm };
|
|
|
|
assert_yaml_snapshot!(foo_value, {
|
|
".hm.$key.bucket" => "[bucket]",
|
|
".btm.$key" => "[key]",
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_ordering() {
|
|
#[derive(Debug, Serialize)]
|
|
pub struct User {
|
|
id: u64,
|
|
username: String,
|
|
flags: std::collections::HashSet<String>,
|
|
}
|
|
|
|
let mut settings = insta::Settings::new();
|
|
settings.add_redaction(".id", "[id]");
|
|
settings.sort_selector(".flags");
|
|
settings.bind(|| {
|
|
assert_json_snapshot!(
|
|
"user_json_flags",
|
|
&User {
|
|
id: 122,
|
|
username: "jason_doe".to_string(),
|
|
flags: vec!["zzz".into(), "foo".into(), "aha".into(), "is_admin".into()]
|
|
.into_iter()
|
|
.collect(),
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_ordering_newtype_set() {
|
|
#[derive(Debug, Serialize)]
|
|
pub struct MySet(std::collections::HashSet<String>);
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct User {
|
|
id: u64,
|
|
username: String,
|
|
flags: MySet,
|
|
}
|
|
|
|
assert_json_snapshot!(
|
|
"user_json_flags_alt",
|
|
&User {
|
|
id: 122,
|
|
username: "jason_doe".to_string(),
|
|
flags: MySet(vec!["zzz".into(), "foo".into(), "aha".into(), "is_admin".into()]
|
|
.into_iter()
|
|
.collect()),
|
|
},
|
|
{
|
|
"." => insta::sorted_redaction(),
|
|
".flags" => insta::sorted_redaction()
|
|
}
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "json")]
|
|
#[test]
|
|
fn test_rounded_redaction() {
|
|
#[derive(Debug, Serialize)]
|
|
pub struct MyPoint {
|
|
x: f64,
|
|
y: f64,
|
|
}
|
|
|
|
assert_json_snapshot!(
|
|
"rounded_redaction",
|
|
&MyPoint {
|
|
x: 1.0 / 3.0,
|
|
y: 6.0 / 3.0,
|
|
},
|
|
{
|
|
".x" => insta::rounded_redaction(4),
|
|
".y" => insta::rounded_redaction(4),
|
|
}
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "yaml")]
|
|
#[test]
|
|
fn test_named_redacted_with_debug_expr() {
|
|
// This test demonstrates the form with a name, redactions, and debug expression
|
|
// | File, redacted, named, debug expr | `assert_yaml_snapshot!("name", expr, {"." => sorted_redaction()}, "debug_expr")` |
|
|
|
|
#[derive(Serialize, Debug)]
|
|
pub struct ComplexObject {
|
|
id: u32,
|
|
items: Vec<String>,
|
|
metadata: std::collections::HashMap<String, u32>,
|
|
}
|
|
|
|
let mut metadata = std::collections::HashMap::new();
|
|
metadata.insert("count".to_string(), 42);
|
|
metadata.insert("version".to_string(), 123);
|
|
|
|
let complex_obj = ComplexObject {
|
|
id: 12345,
|
|
items: vec!["one".to_string(), "two".to_string(), "three".to_string()],
|
|
metadata,
|
|
};
|
|
|
|
// Now that we've added support for this form, we can test it directly
|
|
assert_yaml_snapshot!(
|
|
"named_redacted_debug_expr",
|
|
&complex_obj,
|
|
{
|
|
".id" => "[id]",
|
|
".metadata" => insta::sorted_redaction()
|
|
},
|
|
"This is a custom debug expression for the snapshot"
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "yaml")]
|
|
#[test]
|
|
fn test_named_redacted_supported_form() {
|
|
#[derive(Serialize, Debug)]
|
|
pub struct ComplexObject {
|
|
id: u32,
|
|
items: Vec<String>,
|
|
metadata: std::collections::HashMap<String, u32>,
|
|
}
|
|
|
|
let mut metadata = std::collections::HashMap::new();
|
|
metadata.insert("count".to_string(), 42);
|
|
metadata.insert("version".to_string(), 123);
|
|
|
|
let obj = ComplexObject {
|
|
id: 12345,
|
|
items: vec!["one".to_string(), "two".to_string(), "three".to_string()],
|
|
metadata,
|
|
};
|
|
|
|
assert_yaml_snapshot!(
|
|
"named_redacted_supported",
|
|
&obj,
|
|
{
|
|
".id" => "[id]",
|
|
".metadata" => insta::sorted_redaction()
|
|
}
|
|
);
|
|
}
|
|
|
|
#[cfg(all(feature = "yaml", feature = "redactions"))]
|
|
#[test]
|
|
fn test_metadata_redaction() {
|
|
#[derive(Serialize)]
|
|
struct Info {
|
|
secret: String,
|
|
public: String,
|
|
}
|
|
|
|
let mut settings = insta::Settings::new();
|
|
settings.add_redaction(".secret", "[REDACTED]");
|
|
settings.set_info(&Info {
|
|
secret: "sensitive_value".into(),
|
|
public: "visible".into(),
|
|
});
|
|
|
|
settings.bind(|| {
|
|
assert_yaml_snapshot!("metadata_redaction_test", &vec![1, 2, 3]);
|
|
});
|
|
}
|
|
|
|
#[cfg(all(feature = "yaml", feature = "redactions"))]
|
|
#[test]
|
|
fn test_metadata_raw_info_no_redaction() {
|
|
use insta::internals::Content;
|
|
|
|
let mut settings = insta::Settings::new();
|
|
settings.add_redaction(".secret", "[REDACTED]");
|
|
|
|
// Create content that would be redacted if redactions were applied
|
|
let content = Content::Map(vec![
|
|
(Content::from("secret"), Content::from("sensitive_value")),
|
|
(Content::from("public"), Content::from("visible")),
|
|
]);
|
|
|
|
settings.set_raw_info(&content);
|
|
|
|
settings.bind(|| {
|
|
assert_yaml_snapshot!("metadata_raw_info_no_redaction", &vec![1, 2, 3]);
|
|
});
|
|
}
|