diff --git a/tests/integration.rs b/tests/integration.rs index 789437033..568a9b180 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -130,5 +130,53 @@ mod cli_typed_value_tests; #[path = "integration/exif_tag_id_collision_tests.rs"] mod exif_tag_id_collision_tests; +#[path = "integration/error_handling_tests.rs"] +mod error_handling_tests; + +// Container / audio format integration tests. These were previously declared +// only in tests/integration/mod.rs, which no Cargo test root ever included, so +// they never compiled. Declare them here like every other integration module. +#[path = "integration/mkv_integration_tests.rs"] +mod mkv_integration_tests; + +#[path = "integration/webm_integration_tests.rs"] +mod webm_integration_tests; + +#[path = "integration/flv_integration_tests.rs"] +mod flv_integration_tests; + +#[path = "integration/avi_integration_tests.rs"] +mod avi_integration_tests; + +#[path = "integration/mts_integration_tests.rs"] +mod mts_integration_tests; + +#[path = "integration/mp3_integration_tests.rs"] +mod mp3_integration_tests; + +#[path = "integration/flac_integration_tests.rs"] +mod flac_integration_tests; + +#[path = "integration/aac_integration_tests.rs"] +mod aac_integration_tests; + +#[path = "integration/wav_integration_tests.rs"] +mod wav_integration_tests; + +#[path = "integration/ogg_integration_tests.rs"] +mod ogg_integration_tests; + +#[path = "integration/opus_integration_tests.rs"] +mod opus_integration_tests; + +#[path = "integration/ape_integration_tests.rs"] +mod ape_integration_tests; + +// No qualcomm/google/microsoft MakerNote test modules: those three suites were +// deleted rather than declared. Every tag name they asserted appears in zero +// ExifTool 13.59 source files, so declaring them would have pinned invented +// data as expected behaviour. See the commit that removed them for the +// name-by-name evidence. + #[path = "forensic/mod.rs"] mod forensic; diff --git a/tests/integration/aac_integration_tests.rs b/tests/integration/aac_integration_tests.rs index 5fab8f48f..304ae0bb9 100644 --- a/tests/integration/aac_integration_tests.rs +++ b/tests/integration/aac_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::audio::aac::parse_aac_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_aac_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("AAC:SampleRate"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,18 +50,15 @@ fn test_aac_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse AAC file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open AAC file"); + let oxidex_metadata = parse_aac_metadata(&reader).expect("Failed to parse AAC file"); // Compare key tags - let tags_to_compare = [ - "AAC:AudioChannels", - "AAC:SampleRate", - ]; + let tags_to_compare = ["AAC:AudioChannels", "AAC:SampleRate"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -51,15 +68,11 @@ fn test_aac_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/ape_integration_tests.rs b/tests/integration/ape_integration_tests.rs index 62ce0147f..c3ee81bce 100644 --- a/tests/integration/ape_integration_tests.rs +++ b/tests/integration/ape_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::audio::ape::parse_ape_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_ape_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("APE:CompressionLevel"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,19 +50,15 @@ fn test_ape_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse APE file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open APE file"); + let oxidex_metadata = parse_ape_metadata(&reader).expect("Failed to parse APE file"); // Compare key tags - let tags_to_compare = [ - "APE:CompressionLevel", - "APE:SampleRate", - "APE:Channels", - ]; + let tags_to_compare = ["APE:CompressionLevel", "APE:SampleRate", "APE:Channels"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -52,15 +68,11 @@ fn test_ape_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/avi_integration_tests.rs b/tests/integration/avi_integration_tests.rs index 8a8224a3e..327980d18 100644 --- a/tests/integration/avi_integration_tests.rs +++ b/tests/integration/avi_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::video::parse_avi_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_avi_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("RIFF:FrameRate"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,19 +50,15 @@ fn test_avi_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse AVI file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open AVI file"); + let oxidex_metadata = parse_avi_metadata(&reader).expect("Failed to parse AVI file"); // Compare key tags - let tags_to_compare = [ - "RIFF:FrameRate", - "RIFF:ImageWidth", - "RIFF:ImageHeight", - ]; + let tags_to_compare = ["RIFF:FrameRate", "RIFF:ImageWidth", "RIFF:ImageHeight"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -52,15 +68,11 @@ fn test_avi_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/error_handling_tests.rs b/tests/integration/error_handling_tests.rs index 07b2eb329..f63226ee4 100644 --- a/tests/integration/error_handling_tests.rs +++ b/tests/integration/error_handling_tests.rs @@ -22,6 +22,7 @@ //! - No memory leaks (Rust ownership system) //! - No panics (all errors are `Result`) +use oxidex::core::FileFormat; use oxidex::io::buffered_reader::BufferedReader; use oxidex::parsers::detection::detect_format; use oxidex::parsers::jpeg::segment_parser::parse_segments; @@ -84,17 +85,54 @@ fn test_error_unsupported_format() { let temp_file = NamedTempFile::new().expect("Failed to create temp file"); let temp_path = temp_file.path(); - // Write invalid magic bytes (not a known format) - fs::write(temp_path, b"INVALID_FORMAT_MAGIC_BYTES_HERE").expect("Failed to write temp file"); + // Binary bytes matching no known signature. ExifTool 13.59 reports + // "Error: Unknown file type" for exactly these bytes, so Unknown is the + // agreed-upon answer rather than an oxidex-only convention. + let unrecognized: &[u8] = &[ + 0x00, 0xde, 0xad, 0xbe, 0xef, 0x13, 0x37, 0x00, 0x01, 0x02, 0xfe, 0xdc, 0xba, 0x98, 0x76, + 0x54, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, + 0x22, 0x11, + ]; + fs::write(temp_path, unrecognized).expect("Failed to write temp file"); let reader = BufferedReader::new(temp_path).expect("Failed to open temp file"); - // Try to detect format - should return None for unsupported format - let format = detect_format(&reader); + // `detect_format` reports an unrecognized signature as `Ok(FileFormat::Unknown)` + // rather than an error, so assert on the variant: treating a plain `Ok` as + // success here would pass for every format. + let format = detect_format(&reader).expect("detection must not error on readable data"); - assert!( - format.is_none(), - "Expected None for unsupported format, got: {:?}", + assert_eq!( + format, + FileFormat::Unknown, + "Expected FileFormat::Unknown for unrecognized binary signature, got: {:?}", + format + ); +} + +#[test] +fn test_printable_ascii_is_txt_not_unknown() { + // This guards the fixture used by `test_error_unsupported_format`. That test + // originally fed in the printable string "INVALID_FORMAT_MAGIC_BYTES_HERE" + // and asserted "not a known format" -- but printable ASCII *is* a known + // format. ExifTool 13.59 reports `FileType: TXT, MIMEType: text/plain` for + // that exact byte string, so a future change that makes plain text detect as + // Unknown would be a regression against ExifTool, not a fix. + use std::fs; + use tempfile::NamedTempFile; + + let temp_file = NamedTempFile::new().expect("Failed to create temp file"); + let temp_path = temp_file.path(); + + fs::write(temp_path, b"INVALID_FORMAT_MAGIC_BYTES_HERE").expect("Failed to write temp file"); + + let reader = BufferedReader::new(temp_path).expect("Failed to open temp file"); + let format = detect_format(&reader).expect("detection must not error on readable data"); + + assert_eq!( + format, + FileFormat::TXT, + "Printable ASCII must detect as TXT (ExifTool 13.59 agrees), got: {:?}", format ); } @@ -116,8 +154,9 @@ fn test_error_truncated_jpeg() { 0xFF, 0xD8, // SOI marker 0xFF, 0xE1, // APP1 marker 0x00, 0x10, // Length: 16 bytes (but we won't provide all of them) - b'E', b'x', b'i', b'f', 0x00, 0x00, // EXIF identifier - // Truncated: missing TIFF header and IFD data + b'E', b'x', b'i', b'f', 0x00, + 0x00, // EXIF identifier + // Truncated: missing TIFF header and IFD data ]; fs::write(temp_path, truncated_jpeg).expect("Failed to write temp file"); @@ -157,7 +196,7 @@ fn test_error_truncated_tiff() { b'I', b'I', // Little-endian byte order 0x2A, 0x00, // Magic number 0x08, 0x00, 0x00, 0x00, // IFD offset: 8 - // Truncated: missing IFD data + // Truncated: missing IFD data ]; fs::write(temp_path, truncated_tiff).expect("Failed to write temp file"); @@ -176,13 +215,16 @@ fn test_error_truncated_tiff() { if let Err(e) = result { println!("Parser correctly returned error for truncated TIFF: {}", e); - // Error should indicate unexpected EOF or similar + // Error should name the truncation, not some unrelated failure. + // "IFD offset N exceeds file size N" is the parser's current phrasing + // and is a precise truncation diagnosis, so it counts. let error_msg = e.to_string().to_lowercase(); assert!( error_msg.contains("eof") || error_msg.contains("truncate") || error_msg.contains("unexpected") - || error_msg.contains("invalid"), + || error_msg.contains("invalid") + || error_msg.contains("exceeds file size"), "Error message should indicate truncation/EOF, got: {}", e ); @@ -370,9 +412,7 @@ fn test_no_panic_on_random_data() { let temp_path = temp_file.path(); // Generate random-looking data (deterministic for reproducibility) - let random_data: Vec = (0..1000) - .map(|i| ((i * 37 + 91) % 256) as u8) - .collect(); + let random_data: Vec = (0..1000).map(|i| ((i * 37 + 91) % 256) as u8).collect(); fs::write(temp_path, random_data).expect("Failed to write temp file"); @@ -381,8 +421,8 @@ fn test_no_panic_on_random_data() { // Try various parsers - none should panic let _ = detect_format(&reader); - // If detected as a format, try parsing (should not panic) - if detect_format(&reader).is_some() { + // If detected as a real format, try parsing (should not panic). + if detect_format(&reader).is_ok_and(|f| f != FileFormat::Unknown) { let _ = parse_tiff_file(&reader); let _ = parse_segments(&reader); } diff --git a/tests/integration/flac_integration_tests.rs b/tests/integration/flac_integration_tests.rs index a47c1148b..ecf186daa 100644 --- a/tests/integration/flac_integration_tests.rs +++ b/tests/integration/flac_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::audio::flac::parse_flac_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_flac_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("FLAC:SampleRate"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,33 +50,25 @@ fn test_flac_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse FLAC file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open FLAC file"); + let oxidex_metadata = parse_flac_metadata(&reader).expect("Failed to parse FLAC file"); // Compare key tags - let tags_to_compare = [ - "FLAC:SampleRate", - "FLAC:Channels", - "FLAC:BitsPerSample", - ]; + let tags_to_compare = ["FLAC:SampleRate", "FLAC:Channels", "FLAC:BitsPerSample"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/flv_integration_tests.rs b/tests/integration/flv_integration_tests.rs index ed387ad05..a0a0770f8 100644 --- a/tests/integration/flv_integration_tests.rs +++ b/tests/integration/flv_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::video::parse_flv_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_flv_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("FLV:HasVideo"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,18 +50,15 @@ fn test_flv_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse FLV file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open FLV file"); + let oxidex_metadata = parse_flv_metadata(&reader).expect("Failed to parse FLV file"); // Compare key tags - let tags_to_compare = [ - "FLV:HasVideo", - "FLV:HasAudio", - ]; + let tags_to_compare = ["FLV:HasVideo", "FLV:HasAudio"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -51,15 +68,11 @@ fn test_flv_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/google_makernotes_tests.rs b/tests/integration/google_makernotes_tests.rs deleted file mode 100644 index d17a0595b..000000000 --- a/tests/integration/google_makernotes_tests.rs +++ /dev/null @@ -1,329 +0,0 @@ -//! Integration tests for Google (Pixel) MakerNotes parser -//! -//! Tests the Google Pixel MakerNotes parsing functionality including: -//! - MakerNoteParser trait implementation -//! - Header validation -//! - HDR+ mode detection -//! - Night Sight status -//! - Super Res Zoom -//! - Motion Photos -//! - Computational photography settings - -use oxidex::parsers::tiff::ifd_parser::ByteOrder; -use oxidex::parsers::tiff::makernotes::google::GoogleParser; -use oxidex::parsers::tiff::makernotes::shared::MakerNoteParser; -use std::collections::HashMap; - -#[test] -fn test_google_parser_trait() { - let parser = GoogleParser::new(); - assert_eq!(parser.manufacturer_name(), "Google"); - assert_eq!(parser.tag_prefix(), "Google:"); -} - -#[test] -fn test_google_validate_header_with_signature() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - data.extend_from_slice(b"Google"); - data.extend_from_slice(&[0x00, 0x00]); // Padding - data.extend_from_slice(&[0x05, 0x00]); // 5 entries - - assert!(parser.validate_header(&data)); -} - -#[test] -fn test_google_hdr_plus_off() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x01, 0x00]); // Tag: HDR+ Mode - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); // Value: 0 (Off) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!(tags.get("Google:HDRPlusMode"), Some(&"Off".to_string())); -} - -#[test] -fn test_google_hdr_plus_enhanced() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x01, 0x00]); // Tag - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x02, 0x00, 0x00, 0x00]); // Value: 2 (Enhanced) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!( - tags.get("Google:HDRPlusMode"), - Some(&"HDR+ Enhanced".to_string()) - ); -} - -#[test] -fn test_google_night_sight_off() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x03, 0x00]); // Tag: Night Sight - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); // Value: 0 (Off) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!(tags.get("Google:NightSight"), Some(&"Off".to_string())); -} - -#[test] -fn test_google_night_sight_on() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x03, 0x00]); // Tag - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x02, 0x00, 0x00, 0x00]); // Value: 2 (On) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!(tags.get("Google:NightSight"), Some(&"On".to_string())); -} - -#[test] -fn test_google_night_sight_astrophotography() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x03, 0x00]); // Tag - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x03, 0x00, 0x00, 0x00]); // Value: 3 (Astrophotography) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!( - tags.get("Google:NightSight"), - Some(&"Astrophotography".to_string()) - ); -} - -#[test] -fn test_google_super_res_zoom_off() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x05, 0x00]); // Tag: Super Res Zoom - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); // Value: 0 (Off) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!(tags.get("Google:SuperResZoom"), Some(&"Off".to_string())); -} - -#[test] -fn test_google_super_res_zoom_2x() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x05, 0x00]); // Tag - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x14, 0x00, 0x00, 0x00]); // Value: 20 (2.0x) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!(tags.get("Google:SuperResZoom"), Some(&"2.0x".to_string())); -} - -#[test] -fn test_google_super_res_zoom_7_5x() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x05, 0x00]); // Tag - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x4B, 0x00, 0x00, 0x00]); // Value: 75 (7.5x) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!(tags.get("Google:SuperResZoom"), Some(&"7.5x".to_string())); -} - -#[test] -fn test_google_scene_detection_food() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x0B, 0x00]); // Tag: Scene Detection - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x07, 0x00, 0x00, 0x00]); // Value: 7 (Food) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!(tags.get("Google:SceneDetection"), Some(&"Food".to_string())); -} - -#[test] -fn test_google_face_retouching() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x09, 0x00]); // Tag: Face Retouching - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x32, 0x00, 0x00, 0x00]); // Value: 50 - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!(tags.get("Google:FaceRetouching"), Some(&"50".to_string())); -} - -#[test] -fn test_google_color_pop_on() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x0F, 0x00]); // Tag: Color Pop - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Value: 1 (On) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!(tags.get("Google:ColorPop"), Some(&"On".to_string())); -} - -#[test] -fn test_google_astrophotography_on() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x11, 0x00]); // Tag: Astrophotography - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Value: 1 (On) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!( - tags.get("Google:Astrophotography"), - Some(&"On".to_string()) - ); -} - -#[test] -fn test_google_frame_merge_count() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - data.extend_from_slice(&[0x01, 0x00]); // 1 entry - data.extend_from_slice(&[0x19, 0x00]); // Tag: Frame Count - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x0F, 0x00, 0x00, 0x00]); // Value: 15 frames - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!( - tags.get("Google:MergedFrameCount"), - Some(&"15".to_string()) - ); -} - -#[test] -fn test_google_multiple_tags() { - let parser = GoogleParser::new(); - let mut data = Vec::new(); - - // Create IFD with multiple entries - data.extend_from_slice(&[0x03, 0x00]); // 3 entries - - // HDR+ Mode - data.extend_from_slice(&[0x01, 0x00]); // Tag - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x02, 0x00, 0x00, 0x00]); // Value: 2 (Enhanced) - - // Night Sight - data.extend_from_slice(&[0x03, 0x00]); // Tag - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x02, 0x00, 0x00, 0x00]); // Value: 2 (On) - - // Super Res Zoom - data.extend_from_slice(&[0x05, 0x00]); // Tag - data.extend_from_slice(&[0x03, 0x00]); // Type: SHORT - data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); // Count: 1 - data.extend_from_slice(&[0x20, 0x00, 0x00, 0x00]); // Value: 32 (3.2x) - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_ok()); - assert_eq!(tags.len(), 3); - assert_eq!( - tags.get("Google:HDRPlusMode"), - Some(&"HDR+ Enhanced".to_string()) - ); - assert_eq!(tags.get("Google:NightSight"), Some(&"On".to_string())); - assert_eq!(tags.get("Google:SuperResZoom"), Some(&"3.2x".to_string())); -} - -#[test] -fn test_google_invalid_data() { - let parser = GoogleParser::new(); - let data = vec![0x01]; // Too short - - let mut tags = HashMap::new(); - let result = parser.parse(&data, ByteOrder::LittleEndian, &mut tags); - - assert!(result.is_err()); -} diff --git a/tests/integration/microsoft_makernotes_tests.rs b/tests/integration/microsoft_makernotes_tests.rs deleted file mode 100644 index a11599931..000000000 --- a/tests/integration/microsoft_makernotes_tests.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! Integration tests for Microsoft (Lumia) MakerNotes parser - -use oxidex::parsers::tiff::ifd_parser::ByteOrder; -use oxidex::parsers::tiff::makernotes::microsoft::MicrosoftParser; -use oxidex::parsers::tiff::makernotes::shared::MakerNoteParser; -use std::collections::HashMap; - -#[test] -fn test_microsoft_parser_trait() { - let parser = MicrosoftParser::new(); - assert_eq!(parser.manufacturer_name(), "Microsoft"); - assert_eq!(parser.tag_prefix(), "Microsoft:"); -} - -#[test] -fn test_microsoft_rich_capture_on() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:RichCapture"), Some(&"On".to_string())); -} - -#[test] -fn test_microsoft_rich_capture_mode_hdr() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:RichCaptureMode"), Some(&"HDR".to_string())); -} - -#[test] -fn test_microsoft_rich_capture_mode_hdr_flash() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:RichCaptureMode"), Some(&"HDR + Flash".to_string())); -} - -#[test] -fn test_microsoft_dynamic_flash() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x06, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:DynamicFlash"), Some(&"Flash + No Flash Blend".to_string())); -} - -#[test] -fn test_microsoft_refocus_available() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x08, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:Refocus"), Some(&"Available".to_string())); -} - -#[test] -fn test_microsoft_pureview_mode_5mp() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x0B, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:PureViewMode"), Some(&"5MP Oversampled".to_string())); -} - -#[test] -fn test_microsoft_pureview_mode_lossless_zoom() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x0B, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:PureViewMode"), Some(&"Lossless Zoom".to_string())); -} - -#[test] -fn test_microsoft_creative_effect_vintage() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x0E, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:CreativeEffect"), Some(&"Vintage".to_string())); -} - -#[test] -fn test_microsoft_video_4k_on() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x10, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:Video4K"), Some(&"On".to_string())); -} - -#[test] -fn test_microsoft_rich_recording_on() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x12, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:RichRecordingAudio"), Some(&"On".to_string())); -} - -#[test] -fn test_microsoft_ois_on() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x14, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:OpticalStabilization"), Some(&"On (OIS)".to_string())); -} - -#[test] -fn test_microsoft_auto_hdr_on() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x16, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:AutoHDR"), Some(&"On".to_string())); -} - -#[test] -fn test_microsoft_panorama_on() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x18, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:PanoramaMode"), Some(&"On".to_string())); -} - -#[test] -fn test_microsoft_lens_type_wide_angle() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x1A, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Microsoft:LensType"), Some(&"Wide Angle Attachment".to_string())); -} - -#[test] -fn test_microsoft_multiple_tags() { - let parser = MicrosoftParser::new(); - let mut data = vec![0x02, 0x00]; // 2 entries - - // Rich Capture - data.extend_from_slice(&[0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - // PureView Mode - data.extend_from_slice(&[0x0B, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.len(), 2); - assert_eq!(tags.get("Microsoft:RichCapture"), Some(&"On".to_string())); - assert_eq!(tags.get("Microsoft:PureViewMode"), Some(&"5MP Oversampled".to_string())); -} diff --git a/tests/integration/mkv_integration_tests.rs b/tests/integration/mkv_integration_tests.rs index 76538f2ee..4bc7258f8 100644 --- a/tests/integration/mkv_integration_tests.rs +++ b/tests/integration/mkv_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::video::parse_mkv_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_mkv_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("Matroska:DocType"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,18 +50,15 @@ fn test_mkv_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse MKV file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open MKV file"); + let oxidex_metadata = parse_mkv_metadata(&reader).expect("Failed to parse MKV file"); // Compare key tags - let tags_to_compare = [ - "Matroska:DocType", - "Matroska:DocTypeVersion", - ]; + let tags_to_compare = ["Matroska:DocType", "Matroska:DocTypeVersion"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -51,15 +68,11 @@ fn test_mkv_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs deleted file mode 100644 index 2beb62123..000000000 --- a/tests/integration/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Integration tests module - -pub mod format_detection; -pub mod makernote_integration; - -// Phase 1: Video/Audio integration tests -pub mod mkv_integration_tests; -pub mod webm_integration_tests; -pub mod flv_integration_tests; -pub mod avi_integration_tests; -pub mod mts_integration_tests; -pub mod mp3_integration_tests; -pub mod flac_integration_tests; -pub mod aac_integration_tests; -pub mod wav_integration_tests; -pub mod ogg_integration_tests; -pub mod opus_integration_tests; -pub mod ape_integration_tests; diff --git a/tests/integration/mp3_integration_tests.rs b/tests/integration/mp3_integration_tests.rs index 13c303886..e21ebe5d2 100644 --- a/tests/integration/mp3_integration_tests.rs +++ b/tests/integration/mp3_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::audio::mp3::parse_mp3_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_mp3_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("ID3:Title"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,19 +50,15 @@ fn test_mp3_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse MP3 file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open MP3 file"); + let oxidex_metadata = parse_mp3_metadata(&reader).expect("Failed to parse MP3 file"); // Compare key tags - let tags_to_compare = [ - "ID3:Title", - "ID3:Artist", - "ID3:Album", - ]; + let tags_to_compare = ["ID3:Title", "ID3:Artist", "ID3:Album"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -52,15 +68,11 @@ fn test_mp3_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/mts_integration_tests.rs b/tests/integration/mts_integration_tests.rs index 25532b640..5e61a5c12 100644 --- a/tests/integration/mts_integration_tests.rs +++ b/tests/integration/mts_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::video::parse_mts_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_mts_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("H264:ImageWidth"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,18 +50,15 @@ fn test_mts_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse MTS file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open MTS file"); + let oxidex_metadata = parse_mts_metadata(&reader).expect("Failed to parse MTS file"); // Compare key tags - let tags_to_compare = [ - "H264:ImageWidth", - "H264:ImageHeight", - ]; + let tags_to_compare = ["H264:ImageWidth", "H264:ImageHeight"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -51,15 +68,11 @@ fn test_mts_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/ogg_integration_tests.rs b/tests/integration/ogg_integration_tests.rs index 6b3dcf730..5d295fb42 100644 --- a/tests/integration/ogg_integration_tests.rs +++ b/tests/integration/ogg_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::audio::ogg::parse_ogg_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_ogg_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("Vorbis:SampleRate"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,18 +50,15 @@ fn test_ogg_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse OGG file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open OGG file"); + let oxidex_metadata = parse_ogg_metadata(&reader).expect("Failed to parse OGG file"); // Compare key tags - let tags_to_compare = [ - "Vorbis:SampleRate", - "Vorbis:Channels", - ]; + let tags_to_compare = ["Vorbis:SampleRate", "Vorbis:Channels"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -51,15 +68,11 @@ fn test_ogg_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/opus_integration_tests.rs b/tests/integration/opus_integration_tests.rs index 7a9ec9d5f..5c7058965 100644 --- a/tests/integration/opus_integration_tests.rs +++ b/tests/integration/opus_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::audio::opus::parse_opus_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_opus_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("Opus:OpusVersion"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,18 +50,15 @@ fn test_opus_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse Opus file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open Opus file"); + let oxidex_metadata = parse_opus_metadata(&reader).expect("Failed to parse Opus file"); // Compare key tags - let tags_to_compare = [ - "Opus:OpusVersion", - "Opus:Channels", - ]; + let tags_to_compare = ["Opus:OpusVersion", "Opus:Channels"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -51,15 +68,11 @@ fn test_opus_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/qualcomm_makernotes_tests.rs b/tests/integration/qualcomm_makernotes_tests.rs deleted file mode 100644 index d8bc1d7c2..000000000 --- a/tests/integration/qualcomm_makernotes_tests.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! Integration tests for Qualcomm MakerNotes parser - -use oxidex::parsers::tiff::ifd_parser::ByteOrder; -use oxidex::parsers::tiff::makernotes::qualcomm::QualcommParser; -use oxidex::parsers::tiff::makernotes::shared::MakerNoteParser; -use std::collections::HashMap; - -#[test] -fn test_qualcomm_parser_trait() { - let parser = QualcommParser::new(); - assert_eq!(parser.manufacturer_name(), "Qualcomm"); - assert_eq!(parser.tag_prefix(), "Qualcomm:"); -} - -#[test] -fn test_qualcomm_clear_sight_on() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:ClearSight"), Some(&"On".to_string())); -} - -#[test] -fn test_qualcomm_clear_sight_mode_fusion() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:ClearSightMode"), Some(&"Monochrome + RGB Fusion".to_string())); -} - -#[test] -fn test_qualcomm_chroma_flash() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:ChromaFlash"), Some(&"Flash + No Flash Blend".to_string())); -} - -#[test] -fn test_qualcomm_optizoom_medium() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x07, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:OptiZoom"), Some(&"Medium".to_string())); -} - -#[test] -fn test_qualcomm_zoom_level_5x() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x08, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:ZoomLevel"), Some(&"5.0x".to_string())); -} - -#[test] -fn test_qualcomm_hdr_mode() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x0A, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:HDRMode"), Some(&"HDR".to_string())); -} - -#[test] -fn test_qualcomm_hdr_mode_staggered() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x0A, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:HDRMode"), Some(&"Staggered HDR".to_string())); -} - -#[test] -fn test_qualcomm_scene_detection_portrait() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x0E, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:SceneDetection"), Some(&"Portrait".to_string())); -} - -#[test] -fn test_qualcomm_bokeh_mode_on() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x10, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:BokehMode"), Some(&"On".to_string())); -} - -#[test] -fn test_qualcomm_bokeh_level() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x11, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:BokehLevel"), Some(&"75".to_string())); -} - -#[test] -fn test_qualcomm_low_light_mode_on() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x13, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:LowLightMode"), Some(&"On".to_string())); -} - -#[test] -fn test_qualcomm_night_mode_on() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x15, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:NightMode"), Some(&"On".to_string())); -} - -#[test] -fn test_qualcomm_phase_detect_af() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x17, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:PhaseDetectAF"), Some(&"Active".to_string())); -} - -#[test] -fn test_qualcomm_frame_merge_count() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x1B, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:FrameMergeCount"), Some(&"10".to_string())); -} - -#[test] -fn test_qualcomm_multi_frame_nr_on() { - let parser = QualcommParser::new(); - let mut data = vec![0x01, 0x00]; - data.extend_from_slice(&[0x0C, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.get("Qualcomm:MultiFrameNoiseReduction"), Some(&"On".to_string())); -} - -#[test] -fn test_qualcomm_multiple_tags() { - let parser = QualcommParser::new(); - let mut data = vec![0x02, 0x00]; // 2 entries - - // Clear Sight - data.extend_from_slice(&[0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - // HDR Mode - data.extend_from_slice(&[0x0A, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); - - let mut tags = HashMap::new(); - assert!(parser.parse(&data, ByteOrder::LittleEndian, &mut tags).is_ok()); - assert_eq!(tags.len(), 2); - assert_eq!(tags.get("Qualcomm:ClearSight"), Some(&"On".to_string())); - assert_eq!(tags.get("Qualcomm:HDRMode"), Some(&"HDR".to_string())); -} diff --git a/tests/integration/wav_integration_tests.rs b/tests/integration/wav_integration_tests.rs index 0b01ef943..0c93e94ae 100644 --- a/tests/integration/wav_integration_tests.rs +++ b/tests/integration/wav_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::audio::wav::parse_wav_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_wav_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("RIFF:NumChannels"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,19 +50,15 @@ fn test_wav_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse WAV file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open WAV file"); + let oxidex_metadata = parse_wav_metadata(&reader).expect("Failed to parse WAV file"); // Compare key tags - let tags_to_compare = [ - "RIFF:NumChannels", - "RIFF:SampleRate", - "RIFF:BitsPerSample", - ]; + let tags_to_compare = ["RIFF:NumChannels", "RIFF:SampleRate", "RIFF:BitsPerSample"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -52,15 +68,11 @@ fn test_wav_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/integration/webm_integration_tests.rs b/tests/integration/webm_integration_tests.rs index fd5f22a6c..4e55a6ce7 100644 --- a/tests/integration/webm_integration_tests.rs +++ b/tests/integration/webm_integration_tests.rs @@ -1,6 +1,22 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +use oxidex::io::buffered_reader::BufferedReader; +use oxidex::parsers::video::parse_webm_metadata; use serde_json::Value; +use std::path::Path; + +/// The value as it reaches output, for the variants these parsers emit. +/// +/// Anything else is reported verbatim so a value stored in an unexpected shape +/// fails the comparison instead of quietly reading as equal. +fn printed(value: &TagValue) -> String { + match value { + TagValue::String(s) => s.clone(), + TagValue::Integer(n) => n.to_string(), + TagValue::Float(f) => f.to_string(), + other => format!("", other), + } +} #[test] #[ignore] // Requires ExifTool to be installed @@ -14,10 +30,14 @@ fn test_webm_metadata_parity_with_exiftool() { } // Run ExifTool + // -G0 is required: the tags compared below are group-qualified + // ("Matroska:DocType"), and a plain `-json` emits bare tag names, so every + // lookup would miss and the comparison would silently pass on nothing. let oracle = exiftool_oracle::shared().unwrap_or_else(|e| panic!("No usable ExifTool oracle: {e}")); let exiftool_output = oracle .command() + .arg("-G0") .arg("-json") .arg(test_file) .output() @@ -30,18 +50,15 @@ fn test_webm_metadata_parity_with_exiftool() { assert!(exiftool_output.status.success(), "ExifTool failed"); - let exiftool_json: Vec = serde_json::from_slice(&exiftool_output.stdout) - .expect("Failed to parse ExifTool JSON"); + let exiftool_json: Vec = + serde_json::from_slice(&exiftool_output.stdout).expect("Failed to parse ExifTool JSON"); // Run OxiDex - let oxidex_metadata = MetadataMap::from_file(test_file) - .expect("Failed to parse WebM file"); + let reader = BufferedReader::new(Path::new(test_file)).expect("Failed to open WebM file"); + let oxidex_metadata = parse_webm_metadata(&reader).expect("Failed to parse WebM file"); // Compare key tags - let tags_to_compare = [ - "Matroska:DocType", - "Matroska:DocTypeVersion", - ]; + let tags_to_compare = ["Matroska:DocType", "Matroska:DocTypeVersion"]; for tag in &tags_to_compare { let exiftool_value = &exiftool_json[0][tag]; @@ -51,15 +68,11 @@ fn test_webm_metadata_parity_with_exiftool() { let oxidex_value = oxidex_metadata.get(tag); - assert!( - oxidex_value.is_some(), - "OxiDex missing tag: {}", - tag - ); + assert!(oxidex_value.is_some(), "OxiDex missing tag: {}", tag); // Compare values (convert to strings for comparison) let exiftool_str = exiftool_value.to_string().trim_matches('"').to_string(); - let oxidex_str = oxidex_value.unwrap().to_string(); + let oxidex_str = printed(oxidex_value.unwrap()); assert_eq!( exiftool_str, oxidex_str, diff --git a/tests/unit/audio/mp3_tests.rs b/tests/unit/audio/mp3_tests.rs index 82f680e00..9838bc848 100644 --- a/tests/unit/audio/mp3_tests.rs +++ b/tests/unit/audio/mp3_tests.rs @@ -1,6 +1,6 @@ -use oxidex::parsers::audio::mp3::Mp3Parser; use oxidex::core::FormatParser; use oxidex::io::BufferedReader; +use oxidex::parsers::audio::mp3::Mp3Parser; #[test] fn test_mp3_id3v2_magic() { diff --git a/tests/unit_tests.rs b/tests/unit_tests.rs index 7b754be94..68fb82d34 100644 --- a/tests/unit_tests.rs +++ b/tests/unit_tests.rs @@ -3,6 +3,9 @@ #[path = "unit/audio/flac_tests.rs"] mod flac_tests; +#[path = "unit/audio/mp3_tests.rs"] +mod mp3_tests; + #[path = "unit/format_detection/phase1_tests.rs"] mod phase1_tests;