From f564c5876b53e3d16519ea4220e406f8a40ed206 Mon Sep 17 00:00:00 2001 From: swackhamer Date: Sat, 1 Aug 2026 22:45:19 -0500 Subject: [PATCH 1/3] test(wiring): compile the 15 test files Cargo never built Cargo compiles only `tests/*.rs` as test roots; files in subdirectories are reached solely through `mod` / `#[path]` declarations. 21 files under `tests/` were declared by nothing, so 114 `#[test]` functions had never once compiled. `tests/integration/mod.rs` was itself unreachable -- nothing declared `mod integration;` -- so every module it listed was dead. It also duplicated two entries already declared in `tests/integration.rs`, so it is removed and its 12 container/audio modules are declared in `tests/integration.rs` like every other integration module. Revived here (15 files, 24 tests): error_handling_tests.rs 9 tests -> 10 pass {mkv,webm,flv,avi,mts} 5 tests -> pass {mp3,flac,aac,wav,ogg,opus,ape} 7 tests -> pass unit/audio/mp3_tests.rs 2 tests -> pass Three defects the dead code was hiding: 1. API drift: all 12 container tests called `MetadataMap::from_file`, which no longer exists, and stringified via `TagValue::to_string()`, which never existed (`TagValue` has no `Display`). Rewritten to the current `BufferedReader` + `parse__metadata` entrypoints and the explicit variant match used by `infiray_tests.rs`. 2. The container tests ran `exiftool -json` but compared group-qualified keys ("FLAC:SampleRate"). Plain `-json` emits bare tag names, so every lookup missed. Eleven files then hit a `continue` and passed on nothing; the FLAC file lacks that guard and compared "44100" against the literal "null". Fixed with `-G0`, which emits exactly the keys asserted. FLAC now really does compare SampleRate/Channels/BitsPerSample against ExifTool, and matches. 3. `detect_format` returns `io::Result` and reports an unrecognized signature as `Ok(FileFormat::Unknown)`, not `None`. `test_error_unsupported_format` also had a wrong premise: its "invalid magic bytes" were the printable string "INVALID_FORMAT_MAGIC_BYTES_HERE", and ExifTool 13.59 reports that as `FileType: TXT, MIMEType: text/plain` -- oxidex agreeing with TXT was correct. The test now feeds genuinely unrecognized binary (ExifTool 13.59: "Unknown file type") and a new sibling test pins the TXT behaviour so a future change to it reads as the regression it would be. `test_error_truncated_tiff` asserted the error message contained one of eof/truncate/unexpected/invalid. The parser correctly reports "IFD offset 8 exceeds file size 8" -- a precise truncation diagnosis that matched no substring. Widened the accepted set rather than changing the parser. No test deleted. 554 integration + 17 unit tests pass under --all-features. Co-Authored-By: Claude Opus 5 --- tests/integration.rs | 42 +++++++++++ tests/integration/aac_integration_tests.rs | 43 ++++++++---- tests/integration/ape_integration_tests.rs | 43 +++++++----- tests/integration/avi_integration_tests.rs | 43 +++++++----- tests/integration/error_handling_tests.rs | 77 ++++++++++++++++----- tests/integration/flac_integration_tests.rs | 43 +++++++----- tests/integration/flv_integration_tests.rs | 42 +++++++---- tests/integration/mkv_integration_tests.rs | 42 +++++++---- tests/integration/mod.rs | 18 ----- tests/integration/mp3_integration_tests.rs | 43 +++++++----- tests/integration/mts_integration_tests.rs | 42 +++++++---- tests/integration/ogg_integration_tests.rs | 42 +++++++---- tests/integration/opus_integration_tests.rs | 42 +++++++---- tests/integration/wav_integration_tests.rs | 43 +++++++----- tests/integration/webm_integration_tests.rs | 42 +++++++---- tests/unit/audio/mp3_tests.rs | 2 +- tests/unit_tests.rs | 3 + 17 files changed, 430 insertions(+), 222 deletions(-) delete mode 100644 tests/integration/mod.rs diff --git a/tests/integration.rs b/tests/integration.rs index 789437033..0c13d75ef 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -130,5 +130,47 @@ 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; + #[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..df813d1e0 100644 --- a/tests/integration/ape_integration_tests.rs +++ b/tests/integration/ape_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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 +67,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..484f50435 100644 --- a/tests/integration/avi_integration_tests.rs +++ b/tests/integration/avi_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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 +67,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..10277deb9 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"); @@ -156,8 +195,9 @@ fn test_error_truncated_tiff() { let truncated_tiff = vec![ b'I', b'I', // Little-endian byte order 0x2A, 0x00, // Magic number - 0x08, 0x00, 0x00, 0x00, // IFD offset: 8 - // Truncated: missing IFD data + 0x08, 0x00, 0x00, + 0x00, // IFD offset: 8 + // Truncated: missing IFD data ]; fs::write(temp_path, truncated_tiff).expect("Failed to write temp file"); @@ -176,13 +216,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 +413,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 +422,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..7b9eb0d70 100644 --- a/tests/integration/flac_integration_tests.rs +++ b/tests/integration/flac_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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..49fe95e80 100644 --- a/tests/integration/flv_integration_tests.rs +++ b/tests/integration/flv_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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 +67,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/mkv_integration_tests.rs b/tests/integration/mkv_integration_tests.rs index 76538f2ee..683dbce80 100644 --- a/tests/integration/mkv_integration_tests.rs +++ b/tests/integration/mkv_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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 +67,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..4ea194bcd 100644 --- a/tests/integration/mp3_integration_tests.rs +++ b/tests/integration/mp3_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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 +67,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..447799180 100644 --- a/tests/integration/mts_integration_tests.rs +++ b/tests/integration/mts_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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 +67,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..17a7cf8da 100644 --- a/tests/integration/ogg_integration_tests.rs +++ b/tests/integration/ogg_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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 +67,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..8af2aed6b 100644 --- a/tests/integration/opus_integration_tests.rs +++ b/tests/integration/opus_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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 +67,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/wav_integration_tests.rs b/tests/integration/wav_integration_tests.rs index 0b01ef943..10cb82ea5 100644 --- a/tests/integration/wav_integration_tests.rs +++ b/tests/integration/wav_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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 +67,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..2a5c95f20 100644 --- a/tests/integration/webm_integration_tests.rs +++ b/tests/integration/webm_integration_tests.rs @@ -1,6 +1,21 @@ -use oxidex::core::MetadataMap; +use oxidex::core::TagValue; use oxidex::exiftool_oracle; +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 +29,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 +49,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 +67,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; From a7b4799ddfd785a0e8d8bb300cdef615616c84b1 Mon Sep 17 00:00:00 2001 From: swackhamer Date: Sat, 1 Aug 2026 22:54:10 -0500 Subject: [PATCH 2/3] test(makernotes): delete three suites that mirror fabricated tag tables (#407) These three files were never declared to Cargo, so they had never compiled. Compiling them was not the fix: every tag name they assert appears in **zero** ExifTool 13.59 source files. Declaring them would pin invented data as expected behaviour -- the exact mechanism that let the fabricated `registries/apple.rs` survive. Verified against ExifTool 13.59 (/tmp/oxidex-exiftool-cache/exiftool), using `grep -a -r "Name => ''"` over lib/, i.e. ExifTool's own tag tables. qualcomm_makernotes_tests.rs (17 tests) Tests `QualcommParser`, which production already deleted as a fabrication (see the note in makernotes::mod). Asserted ClearSight, ClearSightMode, ChromaFlash, OptiZoom, BokehMode, BokehLevel, ZoomLevel, HDRMode, NightMode, LowLightMode, SceneDetection, PhaseDetectAF, FrameMergeCount, MultiFrameNoiseReduction under "Qualcomm:" -- all absent from Qualcomm.pm. The file outlived the parser's deletion only because nothing compiled it. google_makernotes_tests.rs (17 tests) Asserted Astrophotography, ColorPop, FaceRetouching, HDRPlusMode, MergedFrameCount, NightSight, SceneDetection, SuperResZoom -- 0 hits each. ExifTool's *only* Google MakerNote table is Google::HDRPlusMakerNote, and it is string-id keyed protobuf (ID_FMT => 'str', ids like '1-1', '9-36-1', base64 + encrypted + gzipped), not a numeric TIFF IFD. Its real tags are ImageName, ImageData, TimeLogText, SummaryText, FrameCount, CreateDate. microsoft_makernotes_tests.rs (16 tests) Asserted AutoHDR, CreativeEffect, DynamicFlash, LensType, OpticalStabilization, PanoramaMode, PureViewMode, Refocus, RichCapture, RichCaptureMode, RichRecordingAudio, Video4K. Only LensType (10 other vendors' modules) and PanoramaMode (Kodak, Olympus) exist at all, and neither in Microsoft.pm. MakerNotes.pm has no MakerNoteMicrosoft dispatch; Microsoft.pm's only MakerNotes-group table is Microsoft::Stitch, binary data in EXIF tag 0x4748 with tags PanoramicStitchVersion / ...CameraMotion / ...MapType. google and microsoft were *passing*. That is the finding, not a reassurance: they pass because `makernotes/google.rs` and `makernotes/microsoft.rs` invent exactly the names the tests assert. A test that mirrors a fabricated table cannot detect the fabrication, and leaving it in place would make removing the invented production tags look like a regression. The production fabrication itself is NOT fixed here -- it is a behavioural change across three vendor parsers and needs its own review. It is reported separately. 50 tests deleted, 0 kept. No production code changed. 554 integration tests pass under --all-features. Co-authored-by: Claude Opus 5 --- tests/integration.rs | 6 + tests/integration/google_makernotes_tests.rs | 329 ------------------ .../integration/microsoft_makernotes_tests.rs | 184 ---------- .../integration/qualcomm_makernotes_tests.rs | 195 ----------- 4 files changed, 6 insertions(+), 708 deletions(-) delete mode 100644 tests/integration/google_makernotes_tests.rs delete mode 100644 tests/integration/microsoft_makernotes_tests.rs delete mode 100644 tests/integration/qualcomm_makernotes_tests.rs diff --git a/tests/integration.rs b/tests/integration.rs index 0c13d75ef..568a9b180 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -172,5 +172,11 @@ 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/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/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())); -} From f93a8cbe8cdda95ee28f147f1de6b780b0b04bbc Mon Sep 17 00:00:00 2001 From: swackhamer Date: Sat, 1 Aug 2026 22:55:00 -0500 Subject: [PATCH 3/3] style: cargo fmt the newly-reachable error_handling_tests cargo fmt --all only formats files reachable from the crate roots, so this file was never formatted while it was undeclared. Declaring it in #404 made it visible to the formatter for the first time, and CI's --check caught the drift. Co-Authored-By: Claude Opus 5 --- tests/integration/ape_integration_tests.rs | 1 + tests/integration/avi_integration_tests.rs | 1 + tests/integration/error_handling_tests.rs | 3 +-- tests/integration/flac_integration_tests.rs | 1 + tests/integration/flv_integration_tests.rs | 1 + tests/integration/mkv_integration_tests.rs | 1 + tests/integration/mp3_integration_tests.rs | 1 + tests/integration/mts_integration_tests.rs | 1 + tests/integration/ogg_integration_tests.rs | 1 + tests/integration/opus_integration_tests.rs | 1 + tests/integration/wav_integration_tests.rs | 1 + tests/integration/webm_integration_tests.rs | 1 + 12 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/integration/ape_integration_tests.rs b/tests/integration/ape_integration_tests.rs index df813d1e0..c3ee81bce 100644 --- a/tests/integration/ape_integration_tests.rs +++ b/tests/integration/ape_integration_tests.rs @@ -1,5 +1,6 @@ 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; diff --git a/tests/integration/avi_integration_tests.rs b/tests/integration/avi_integration_tests.rs index 484f50435..327980d18 100644 --- a/tests/integration/avi_integration_tests.rs +++ b/tests/integration/avi_integration_tests.rs @@ -1,5 +1,6 @@ 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; diff --git a/tests/integration/error_handling_tests.rs b/tests/integration/error_handling_tests.rs index 10277deb9..f63226ee4 100644 --- a/tests/integration/error_handling_tests.rs +++ b/tests/integration/error_handling_tests.rs @@ -195,8 +195,7 @@ fn test_error_truncated_tiff() { let truncated_tiff = vec![ b'I', b'I', // Little-endian byte order 0x2A, 0x00, // Magic number - 0x08, 0x00, 0x00, - 0x00, // IFD offset: 8 + 0x08, 0x00, 0x00, 0x00, // IFD offset: 8 // Truncated: missing IFD data ]; diff --git a/tests/integration/flac_integration_tests.rs b/tests/integration/flac_integration_tests.rs index 7b9eb0d70..ecf186daa 100644 --- a/tests/integration/flac_integration_tests.rs +++ b/tests/integration/flac_integration_tests.rs @@ -1,5 +1,6 @@ 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; diff --git a/tests/integration/flv_integration_tests.rs b/tests/integration/flv_integration_tests.rs index 49fe95e80..a0a0770f8 100644 --- a/tests/integration/flv_integration_tests.rs +++ b/tests/integration/flv_integration_tests.rs @@ -1,5 +1,6 @@ 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; diff --git a/tests/integration/mkv_integration_tests.rs b/tests/integration/mkv_integration_tests.rs index 683dbce80..4bc7258f8 100644 --- a/tests/integration/mkv_integration_tests.rs +++ b/tests/integration/mkv_integration_tests.rs @@ -1,5 +1,6 @@ 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; diff --git a/tests/integration/mp3_integration_tests.rs b/tests/integration/mp3_integration_tests.rs index 4ea194bcd..e21ebe5d2 100644 --- a/tests/integration/mp3_integration_tests.rs +++ b/tests/integration/mp3_integration_tests.rs @@ -1,5 +1,6 @@ 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; diff --git a/tests/integration/mts_integration_tests.rs b/tests/integration/mts_integration_tests.rs index 447799180..5e61a5c12 100644 --- a/tests/integration/mts_integration_tests.rs +++ b/tests/integration/mts_integration_tests.rs @@ -1,5 +1,6 @@ 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; diff --git a/tests/integration/ogg_integration_tests.rs b/tests/integration/ogg_integration_tests.rs index 17a7cf8da..5d295fb42 100644 --- a/tests/integration/ogg_integration_tests.rs +++ b/tests/integration/ogg_integration_tests.rs @@ -1,5 +1,6 @@ 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; diff --git a/tests/integration/opus_integration_tests.rs b/tests/integration/opus_integration_tests.rs index 8af2aed6b..5c7058965 100644 --- a/tests/integration/opus_integration_tests.rs +++ b/tests/integration/opus_integration_tests.rs @@ -1,5 +1,6 @@ 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; diff --git a/tests/integration/wav_integration_tests.rs b/tests/integration/wav_integration_tests.rs index 10cb82ea5..0c93e94ae 100644 --- a/tests/integration/wav_integration_tests.rs +++ b/tests/integration/wav_integration_tests.rs @@ -1,5 +1,6 @@ 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; diff --git a/tests/integration/webm_integration_tests.rs b/tests/integration/webm_integration_tests.rs index 2a5c95f20..4e55a6ce7 100644 --- a/tests/integration/webm_integration_tests.rs +++ b/tests/integration/webm_integration_tests.rs @@ -1,5 +1,6 @@ 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;