#![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>, } 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, } 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, } 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, 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, } 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); #[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, metadata: std::collections::HashMap, } 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, metadata: std::collections::HashMap, } 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]); }); }