use super::*;
use crate::determinism::DeterminismConfig;
#[cfg(test)]
mod tests {
use super::*;
fn create_test_canonicalizer() -> DB_C14N {
let config = DeterminismConfig::default();
DB_C14N::new(config)
}
fn create_test_canonicalizer_with_version(version: &str) -> DB_C14N {
let config = DeterminismConfig::default();
DB_C14N::with_version(config, version.to_string())
}
#[test]
fn test_xml_declaration() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>test</root>"#;
let result = canonicalizer.canonicalize(input).unwrap();
assert!(result.starts_with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
}
#[test]
fn test_attribute_sorting() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root z="z" a="a" m="m">test</root>"#;
let result = canonicalizer.canonicalize(input).unwrap();
assert!(result.contains(r#"<root a="a" m="m" z="z">"#));
}
#[test]
fn test_whitespace_normalization() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
test content
</root>"#;
let result = canonicalizer.canonicalize(input).unwrap();
assert!(result.contains("test content"));
}
#[test]
fn test_indentation() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root><child>content</child></root>"#;
let result = canonicalizer.canonicalize(input).unwrap();
let lines: Vec<&str> = result.lines().collect();
assert!(lines.iter().any(|line| line.starts_with(" <child>")));
}
#[test]
fn test_no_trailing_whitespace() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
<child>content</child>
</root>"#;
let result = canonicalizer.canonicalize(input).unwrap();
for line in result.lines() {
assert_eq!(
line,
line.trim_end(),
"Line has trailing whitespace: '{}'",
line
);
}
}
#[test]
fn test_ern_version_detection() {
let canonicalizer = create_test_canonicalizer();
assert_eq!(
canonicalizer
.detect_version(r#"<root xmlns="http://ddex.net/xml/ern/382">test</root>"#),
"3.8.2"
);
assert_eq!(
canonicalizer.detect_version(r#"<root xmlns="http://ddex.net/xml/ern/42">test</root>"#),
"4.2"
);
assert_eq!(
canonicalizer.detect_version(r#"<root xmlns="http://ddex.net/xml/ern/43">test</root>"#),
"4.3"
);
}
#[test]
fn test_namespace_prefix_locking_43() {
let canonicalizer = create_test_canonicalizer_with_version("4.3");
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:ddex="http://ddex.net/xml/ern/43" xmlns:avs="http://ddex.net/xml/avs">
<ddex:Release>test</ddex:Release>
</root>"#;
let result = canonicalizer.canonicalize(input).unwrap();
assert!(result.contains(r#"xmlns:ern="http://ddex.net/xml/ern/43""#));
}
#[test]
fn test_element_ordering_message_header() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<MessageHeader>
<MessageSender>sender</MessageSender>
<MessageId>id</MessageId>
<MessageType>type</MessageType>
</MessageHeader>"#;
let result = canonicalizer.canonicalize(input).unwrap();
let lines: Vec<&str> = result.lines().collect();
let id_pos = lines
.iter()
.position(|l| l.contains("<MessageId>"))
.unwrap();
let type_pos = lines
.iter()
.position(|l| l.contains("<MessageType>"))
.unwrap();
let sender_pos = lines
.iter()
.position(|l| l.contains("<MessageSender>"))
.unwrap();
assert!(id_pos < type_pos);
assert!(type_pos < sender_pos);
}
#[test]
fn test_deterministic_output() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root z="z" a="a">
<child2>content2</child2>
<child1>content1</child1>
</root>"#;
let result1 = canonicalizer.canonicalize(input).unwrap();
let result2 = canonicalizer.canonicalize(input).unwrap();
let result3 = canonicalizer.canonicalize(input).unwrap();
assert_eq!(result1, result2);
assert_eq!(result2, result3);
}
#[test]
fn test_canonical_hash() {
let canonicalizer = create_test_canonicalizer();
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>test</root>"#;
let canonical = canonicalizer.canonicalize(xml).unwrap();
let hash1 = canonicalizer.canonical_hash(&canonical).unwrap();
let hash2 = canonicalizer.canonical_hash(&canonical).unwrap();
assert_eq!(hash1, hash2);
assert_eq!(hash1.len(), 64); }
#[test]
fn test_empty_elements() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
<empty/>
<also-empty></also-empty>
</root>"#;
let result = canonicalizer.canonicalize(input).unwrap();
assert!(result.contains("<empty/>") || result.contains("<empty></empty>"));
}
#[test]
fn test_comments_preservation() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
<!-- This is a comment -->
<child>content</child>
</root>"#;
let result = canonicalizer.canonicalize(input).unwrap();
assert!(result.contains("<!-- This is a comment -->"));
}
#[test]
fn test_complex_ddex_structure() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<ern:NewReleaseMessage xmlns:ern="http://ddex.net/xml/ern/43" xmlns:avs="http://ddex.net/xml/avs">
<MessageHeader>
<MessageSender>sender</MessageSender>
<MessageId>MSG123</MessageId>
<MessageType>NewReleaseMessage</MessageType>
<MessageCreatedDateTime>2023-01-01T00:00:00Z</MessageCreatedDateTime>
</MessageHeader>
<ReleaseList>
<Release>
<ReleaseId>
<GRid>A123456789</GRid>
</ReleaseId>
<ReferenceTitle>
<TitleText>Test Album</TitleText>
</ReferenceTitle>
</Release>
</ReleaseList>
</ern:NewReleaseMessage>"#;
let result = canonicalizer.canonicalize(input).unwrap();
assert!(result.starts_with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
assert!(result.contains("<MessageHeader>"));
assert!(result.contains("<Release>"));
assert!(result.contains(" <MessageHeader>"));
assert!(result.contains(" <MessageId>"));
}
#[test]
fn test_round_trip_fidelity() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root attr="value">
<child>content</child>
</root>"#;
let canonical1 = canonicalizer.canonicalize(input).unwrap();
let canonical2 = canonicalizer.canonicalize(&canonical1).unwrap();
assert_eq!(canonical1, canonical2);
}
#[test]
fn test_line_ending_normalization() {
let canonicalizer = create_test_canonicalizer();
let input_crlf = "<root>\r\n <child>content</child>\r\n</root>";
let input_cr = "<root>\r <child>content</child>\r</root>";
let input_lf = "<root>\n <child>content</child>\n</root>";
let result_crlf = canonicalizer.canonicalize(input_crlf).unwrap();
let result_cr = canonicalizer.canonicalize(input_cr).unwrap();
let result_lf = canonicalizer.canonicalize(input_lf).unwrap();
assert!(!result_crlf.contains("\r\n"));
assert!(!result_cr.contains("\r"));
assert!(result_lf.contains("\n"));
assert_eq!(result_crlf, result_lf);
assert_eq!(result_cr, result_lf);
}
#[test]
fn test_attribute_escaping() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root attr=""quoted" & escaped">
<child><content></child>
</root>"#;
let result = canonicalizer.canonicalize(input).unwrap();
assert!(result.contains(""") || result.contains("\""));
assert!(result.contains("&") || result.contains("&"));
assert!(result.contains("<") || result.contains("<"));
assert!(result.contains(">") || result.contains(">"));
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
use insta::assert_snapshot;
#[test]
fn test_ern_43_canonicalization_snapshot() {
let canonicalizer = create_test_canonicalizer_with_version("4.3");
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<ern:NewReleaseMessage xmlns:ern="http://ddex.net/xml/ern/43" xmlns:avs="http://ddex.net/xml/avs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<MessageHeader>
<MessageSender>TestSender</MessageSender>
<MessageRecipient>TestRecipient</MessageRecipient>
<MessageId>MSG_001</MessageId>
<MessageType>NewReleaseMessage</MessageType>
<MessageCreatedDateTime>2023-12-01T10:00:00Z</MessageCreatedDateTime>
<MessageControlType>LiveMessage</MessageControlType>
</MessageHeader>
<ReleaseList>
<Release>
<ReleaseDetailsByTerritory>
<TerritoryCode>Worldwide</TerritoryCode>
</ReleaseDetailsByTerritory>
<ReleaseId>
<GRid>A1234567890123456789</GRid>
</ReleaseId>
<ReferenceTitle>
<TitleText>Sample Release Title</TitleText>
</ReferenceTitle>
<ReleaseReference>REL_001</ReleaseReference>
</Release>
</ReleaseList>
</ern:NewReleaseMessage>"#;
let result = canonicalizer.canonicalize(input).unwrap();
assert_snapshot!(result);
}
#[test]
fn test_byte_identical_output() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<test xmlns:z="urn:z" xmlns:a="urn:a" z:attr="z" a:attr="a">
<z:element>content</z:element>
<a:element>content</a:element>
</test>"#;
let result1 = canonicalizer.canonicalize(input).unwrap();
let result2 = canonicalizer.canonicalize(input).unwrap();
let bytes1 = result1.as_bytes();
let bytes2 = result2.as_bytes();
assert_eq!(
bytes1, bytes2,
"Canonicalization must be deterministic at byte level"
);
}
#[test]
fn test_text_content_preservation_regression() {
let canonicalizer = create_test_canonicalizer();
let test_cases = vec![
(
r#"<?xml version="1.0"?><root>Hello World</root>"#,
"Hello World",
),
(
r#"<?xml version="1.0"?><root> Multiple spaces </root>"#,
"Multiple spaces",
),
(
r#"<?xml version="1.0"?>
<root>
Line 1
Line 2
</root>"#,
"Line 1 Line 2",
),
];
for (input, expected_text) in test_cases {
let result = canonicalizer.canonicalize(input).unwrap();
assert!(
result.contains(expected_text),
"Text content '{}' not found in canonicalized output: {}",
expected_text,
result
);
}
let mixed_input = r#"<?xml version="1.0"?><root>Before<child>nested</child>After</root>"#;
let mixed_result = canonicalizer.canonicalize(mixed_input).unwrap();
assert!(
mixed_result.contains("Before"),
"Mixed content 'Before' text not preserved"
);
assert!(
mixed_result.contains("After"),
"Mixed content 'After' text not preserved"
);
assert!(
mixed_result.contains("<child>nested</child>"),
"Mixed content child element not preserved"
);
}
#[test]
fn test_mixed_content_preservation_regression() {
let canonicalizer = create_test_canonicalizer();
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
<!-- Comment before -->
<element1>Text 1</element1>
Some text between elements
<!-- Comment middle -->
<element2>Text 2</element2>
More text after
<!-- Comment after -->
</root>"#;
let result = canonicalizer.canonicalize(input).unwrap();
assert!(
result.contains("<!-- Comment before -->"),
"Comment before not preserved"
);
assert!(
result.contains("<!-- Comment middle -->"),
"Comment middle not preserved"
);
assert!(
result.contains("<!-- Comment after -->"),
"Comment after not preserved"
);
assert!(result.contains("Text 1"), "Element text 1 not preserved");
assert!(result.contains("Text 2"), "Element text 2 not preserved");
assert!(
result.contains("Some text between elements"),
"Interstitial text not preserved"
);
assert!(
result.contains("More text after"),
"Trailing text not preserved"
);
}
}