Index: tests/utils.rs ================================================================== --- tests/utils.rs +++ tests/utils.rs @@ -1,51 +1,81 @@ use smtp2tg::utils::{ validate, RE_CLOSING, RE_DOMAIN, }; -use stacked_errors::Result; - -#[test] -fn test_validate_valid_html() -> Result<()> { - let html = "
Some valid HTML
"; - let escaped = validate(html)?; - assert_eq!(escaped, "<p>Some <b>valid</b> HTML</p>"); - Ok(()) -} - -#[test] -fn test_validate_closing_tag() -> Result<()> { - let html = "Some valid HTML
Link injection!"; - assert!(validate(html).is_err()); - assert!(validate("").is_err()); - assert!(validate("").is_err()); - assert!(validate("\n").is_err()); - assert!(validate("\t").is_err()); - Ok(()) -} - -#[test] -fn test_validate_empty_string() -> Result<()> { - assert_eq!(validate("").unwrap(), ""); - Ok(()) -} - -#[test] -fn test_validate_whitespace() -> Result<()> { - assert_eq!(validate(" \t\n").unwrap(), " \t\n"); // no escaping for whitespace - Ok(()) -} - -#[test] -fn test_regex_closing_tag_matches() { - assert!(RE_CLOSING.is_match("")); - assert!(RE_CLOSING.is_match("\t>")); - assert!(!RE_CLOSING.is_match("")); // Not a pre/code tag -} - -#[test] -fn test_regex_domain_matches() { - assert!(RE_DOMAIN.is_match("example.com")); - assert!(RE_DOMAIN.is_match("sub.example.co.uk")); - assert!(!RE_DOMAIN.is_match("invalid@domain.com")); + +use std::{ + borrow::Cow, + mem::discriminant, +}; + +use stacked_errors::Result; + +#[test] +fn test_validate_escaping_behavior () -> Result<()> { + let cases: &[(&str, CowSome valid HTML
", Cow::Owned("<p>Some <b>valid</b> HTML</p>".into())), + // Empty input is returned unchanged. + ("", Cow::Borrowed("")), + // Whitespace-only input needs no escaping. + (" \t\n", Cow::Borrowed(" \t\n")), + // `validate` returns `Cow<'a, str>` borrowed from its input lifetime `'a`. + // These two cases exercise both branches of that `Cow` to make sure the + // explicit lifetime introduced on `validate` still lets callers observe a + // zero-copy borrow when no escaping is required. + ("plain text without special html characters", Cow::Borrowed("plain text without special html characters")), + ("5 > 3 & 2 < 4", Cow::Owned("5 > 3 & 2 < 4".into())), + ]; + for (input, expected) in cases { + let result = validate(input)?; + assert_eq!(&result, expected, "unexpected output for input {input:?}"); + assert_eq!(discriminant(&result), discriminant(expected), "wrong Cow variant for input {input:?}"); + } + Ok(()) +} + +#[test] +fn test_validate_closing_tag_behavior () { + let cases = [ + (" pre >", true), + ("\tcode\t>", true), + ("", false), + ("", true), + ("", true), + ("\t", true), + ("\t>", true), + ("", false), // Not a pre/code tag + ("", true), + ("", true), + ("\n", true), + ("Some valid HTML
Link injection!", true), + ("", false),
+ ];
+ for (input, expected) in cases {
+ assert_eq!(RE_CLOSING.is_match(input), expected, "unexpected match result for {input:?}");
+ }
+}
+
+#[test]
+fn test_regex_domain_behavior() {
+ let cases = [
+ ("", false),
+ ("-example.com", false),
+ (".example.com", false),
+ ("123.456", true),
+ ("EXAMPLE.COM", false),
+ ("a", true),
+ ("a.b", true),
+ ("example-.com", false),
+ ("example..com", false),
+ ("example.com", true),
+ ("example.com.", false),
+ ("invalid@domain.com", false),
+ ("my-host.example.com", true),
+ ("sub.example.co.uk", true),
+ ];
+ for (input, expected) in cases {
+ assert_eq!(RE_DOMAIN.is_match(input), expected, "unexpected match result for {input:?}");
+ }
}