WICMetadataExtractorTests.cpp
1 #include "pch.h" 2 #include "WICMetadataExtractor.h" 3 #include <filesystem> 4 #include <sstream> 5 6 using namespace Microsoft::VisualStudio::CppUnitTestFramework; 7 using namespace PowerRenameLib; 8 9 namespace WICMetadataExtractorTests 10 { 11 // Helper function to get the test data directory path 12 std::wstring GetTestDataPath() 13 { 14 // Get the directory where the test DLL is located 15 // When running with vstest, we need to get the DLL module handle 16 HMODULE hModule = nullptr; 17 GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 18 reinterpret_cast<LPCWSTR>(&GetTestDataPath), 19 &hModule); 20 21 wchar_t modulePath[MAX_PATH]; 22 GetModuleFileNameW(hModule, modulePath, MAX_PATH); 23 std::filesystem::path dllPath(modulePath); 24 25 // Navigate to the test data directory 26 // The test data is in the output directory alongside the DLL 27 std::filesystem::path testDataPath = dllPath.parent_path() / L"testdata"; 28 29 return testDataPath.wstring(); 30 } 31 32 TEST_CLASS(ExtractEXIFMetadataTests) 33 { 34 public: 35 TEST_METHOD(ExtractEXIF_InvalidFile_ReturnsFalse) 36 { 37 // Test that EXIF extraction fails for nonexistent file 38 WICMetadataExtractor extractor; 39 EXIFMetadata metadata; 40 41 std::wstring testFile = GetTestDataPath() + L"\\nonexistent.jpg"; 42 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 43 44 Assert::IsFalse(result, L"EXIF extraction should fail for nonexistent file"); 45 } 46 47 TEST_METHOD(ExtractEXIF_ExifTest_AllFields) 48 { 49 // Test exif_test.jpg which contains comprehensive EXIF data 50 WICMetadataExtractor extractor; 51 EXIFMetadata metadata; 52 53 std::wstring testFile = GetTestDataPath() + L"\\exif_test.jpg"; 54 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 55 56 Assert::IsTrue(result, L"EXIF extraction should succeed"); 57 58 // Verify all the fields that are in exif_test.jpg 59 Assert::IsTrue(metadata.cameraMake.has_value(), L"Camera make should be present"); 60 Assert::AreEqual(L"samsung", metadata.cameraMake.value().c_str(), L"Camera make should be samsung"); 61 62 Assert::IsTrue(metadata.cameraModel.has_value(), L"Camera model should be present"); 63 Assert::AreEqual(L"SM-G930P", metadata.cameraModel.value().c_str(), L"Camera model should be SM-G930P"); 64 65 Assert::IsTrue(metadata.lensModel.has_value(), L"Lens model should be present"); 66 Assert::AreEqual(L"Samsung Galaxy S7 Rear Camera", metadata.lensModel.value().c_str(), L"Lens model should match"); 67 68 Assert::IsTrue(metadata.iso.has_value(), L"ISO should be present"); 69 Assert::AreEqual(40, static_cast<int>(metadata.iso.value()), L"ISO should be 40"); 70 71 Assert::IsTrue(metadata.aperture.has_value(), L"Aperture should be present"); 72 Assert::AreEqual(1.7, metadata.aperture.value(), 0.01, L"Aperture should be f/1.7"); 73 74 Assert::IsTrue(metadata.shutterSpeed.has_value(), L"Shutter speed should be present"); 75 Assert::AreEqual(0.000625, metadata.shutterSpeed.value(), 0.000001, L"Shutter speed should be 0.000625s"); 76 77 Assert::IsTrue(metadata.focalLength.has_value(), L"Focal length should be present"); 78 Assert::AreEqual(4.2, metadata.focalLength.value(), 0.1, L"Focal length should be 4.2mm"); 79 80 Assert::IsTrue(metadata.flash.has_value(), L"Flash should be present"); 81 Assert::AreEqual(0u, static_cast<unsigned int>(metadata.flash.value()), L"Flash should be 0x0"); 82 83 Assert::IsTrue(metadata.exposureBias.has_value(), L"Exposure bias should be present"); 84 Assert::AreEqual(0.0, metadata.exposureBias.value(), 0.01, L"Exposure bias should be 0 EV"); 85 86 Assert::IsTrue(metadata.author.has_value(), L"Author should be present"); 87 Assert::AreEqual(L"Carl Seibert (Exif)", metadata.author.value().c_str(), L"Author should match"); 88 89 Assert::IsTrue(metadata.copyright.has_value(), L"Copyright should be present"); 90 Assert::IsTrue(metadata.copyright.value().find(L"Carl Seibert") != std::wstring::npos, L"Copyright should contain Carl Seibert"); 91 } 92 93 TEST_METHOD(ExtractEXIF_ExifTest2_WidthHeight) 94 { 95 // Test exif_test_2.jpg which only contains width and height 96 WICMetadataExtractor extractor; 97 EXIFMetadata metadata; 98 99 std::wstring testFile = GetTestDataPath() + L"\\exif_test_2.jpg"; 100 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 101 102 Assert::IsTrue(result, L"EXIF extraction should succeed"); 103 104 // exif_test_2.jpg only has width and height 105 Assert::IsTrue(metadata.width.has_value(), L"Width should be present"); 106 Assert::AreEqual(1080u, static_cast<unsigned int>(metadata.width.value()), L"Width should be 1080px"); 107 108 Assert::IsTrue(metadata.height.has_value(), L"Height should be present"); 109 Assert::AreEqual(810u, static_cast<unsigned int>(metadata.height.value()), L"Height should be 810px"); 110 111 // Other fields should not be present 112 Assert::IsFalse(metadata.cameraMake.has_value(), L"Camera make should not be present in exif_test_2.jpg"); 113 Assert::IsFalse(metadata.cameraModel.has_value(), L"Camera model should not be present in exif_test_2.jpg"); 114 Assert::IsFalse(metadata.iso.has_value(), L"ISO should not be present in exif_test_2.jpg"); 115 } 116 117 TEST_METHOD(ExtractEXIF_ClearCache) 118 { 119 // Test cache clearing works 120 WICMetadataExtractor extractor; 121 EXIFMetadata metadata; 122 123 std::wstring testFile = GetTestDataPath() + L"\\exif_test.jpg"; 124 125 bool result1 = extractor.ExtractEXIFMetadata(testFile, metadata); 126 Assert::IsTrue(result1); 127 128 extractor.ClearCache(); 129 130 EXIFMetadata metadata2; 131 bool result2 = extractor.ExtractEXIFMetadata(testFile, metadata2); 132 Assert::IsTrue(result2); 133 134 // Both calls should succeed 135 Assert::AreEqual(metadata.cameraMake.value().c_str(), metadata2.cameraMake.value().c_str()); 136 } 137 }; 138 139 TEST_CLASS(ExtractXMPMetadataTests) 140 { 141 public: 142 TEST_METHOD(ExtractXMP_InvalidFile_ReturnsFalse) 143 { 144 // Test that XMP extraction fails for nonexistent file 145 WICMetadataExtractor extractor; 146 XMPMetadata metadata; 147 148 std::wstring testFile = GetTestDataPath() + L"\\nonexistent.jpg"; 149 bool result = extractor.ExtractXMPMetadata(testFile, metadata); 150 151 Assert::IsFalse(result, L"XMP extraction should fail for nonexistent file"); 152 } 153 154 TEST_METHOD(ExtractXMP_XmpTest_AllFields) 155 { 156 // Test xmp_test.jpg which contains comprehensive XMP data 157 WICMetadataExtractor extractor; 158 XMPMetadata metadata; 159 160 std::wstring testFile = GetTestDataPath() + L"\\xmp_test.jpg"; 161 bool result = extractor.ExtractXMPMetadata(testFile, metadata); 162 163 Assert::IsTrue(result, L"XMP extraction should succeed"); 164 165 // Verify all the fields that are in xmp_test.jpg 166 Assert::IsTrue(metadata.title.has_value(), L"Title should be present"); 167 Assert::AreEqual(L"object name here", metadata.title.value().c_str(), L"Title should match"); 168 169 Assert::IsTrue(metadata.description.has_value(), L"Description should be present"); 170 Assert::IsTrue(metadata.description.value().find(L"This is a metadata test file") != std::wstring::npos, 171 L"Description should contain expected text"); 172 173 Assert::IsTrue(metadata.rights.has_value(), L"Rights should be present"); 174 Assert::AreEqual(L"metadatamatters.blog", metadata.rights.value().c_str(), L"Rights should match"); 175 176 Assert::IsTrue(metadata.creatorTool.has_value(), L"Creator tool should be present"); 177 Assert::IsTrue(metadata.creatorTool.value().find(L"Adobe Photoshop Lightroom") != std::wstring::npos, 178 L"Creator tool should contain Lightroom"); 179 180 Assert::IsTrue(metadata.documentID.has_value(), L"Document ID should be present"); 181 Assert::IsTrue(metadata.documentID.value().find(L"xmp.did:") != std::wstring::npos, 182 L"Document ID should start with xmp.did:"); 183 184 Assert::IsTrue(metadata.instanceID.has_value(), L"Instance ID should be present"); 185 Assert::IsTrue(metadata.instanceID.value().find(L"xmp.iid:") != std::wstring::npos, 186 L"Instance ID should start with xmp.iid:"); 187 188 Assert::IsTrue(metadata.subject.has_value(), L"Subject keywords should be present"); 189 Assert::IsTrue(metadata.subject.value().size() > 0, L"Should have at least one keyword"); 190 } 191 192 TEST_METHOD(ExtractXMP_XmpTest2_BasicFields) 193 { 194 // Test xmp_test_2.jpg which only contains basic XMP fields 195 WICMetadataExtractor extractor; 196 XMPMetadata metadata; 197 198 std::wstring testFile = GetTestDataPath() + L"\\xmp_test_2.jpg"; 199 bool result = extractor.ExtractXMPMetadata(testFile, metadata); 200 201 Assert::IsTrue(result, L"XMP extraction should succeed"); 202 203 // xmp_test_2.jpg only has CreatorTool, DocumentID, and InstanceID 204 Assert::IsTrue(metadata.creatorTool.has_value(), L"Creator tool should be present"); 205 Assert::IsTrue(metadata.creatorTool.value().find(L"Adobe Photoshop CS6") != std::wstring::npos, 206 L"Creator tool should be Photoshop CS6"); 207 208 Assert::IsTrue(metadata.documentID.has_value(), L"Document ID should be present"); 209 Assert::IsTrue(metadata.documentID.value().find(L"xmp.did:") != std::wstring::npos, 210 L"Document ID should start with xmp.did:"); 211 212 Assert::IsTrue(metadata.instanceID.has_value(), L"Instance ID should be present"); 213 Assert::IsTrue(metadata.instanceID.value().find(L"xmp.iid:") != std::wstring::npos, 214 L"Instance ID should start with xmp.iid:"); 215 216 // Other fields should not be present 217 Assert::IsFalse(metadata.title.has_value(), L"Title should not be present in xmp_test_2.jpg"); 218 Assert::IsFalse(metadata.description.has_value(), L"Description should not be present in xmp_test_2.jpg"); 219 Assert::IsFalse(metadata.rights.has_value(), L"Rights should not be present in xmp_test_2.jpg"); 220 Assert::IsFalse(metadata.creator.has_value(), L"Creator should not be present in xmp_test_2.jpg"); 221 } 222 223 TEST_METHOD(ExtractXMP_ClearCache) 224 { 225 // Test cache clearing works 226 WICMetadataExtractor extractor; 227 XMPMetadata metadata; 228 229 std::wstring testFile = GetTestDataPath() + L"\\xmp_test.jpg"; 230 231 bool result1 = extractor.ExtractXMPMetadata(testFile, metadata); 232 Assert::IsTrue(result1); 233 234 extractor.ClearCache(); 235 236 XMPMetadata metadata2; 237 bool result2 = extractor.ExtractXMPMetadata(testFile, metadata2); 238 Assert::IsTrue(result2); 239 240 // Both calls should succeed 241 Assert::AreEqual(metadata.title.value().c_str(), metadata2.title.value().c_str()); 242 } 243 }; 244 245 TEST_CLASS(ExtractHEIFMetadataTests) 246 { 247 public: 248 TEST_METHOD(ExtractHEIF_EXIF_CameraInfo) 249 { 250 // Test HEIF EXIF extraction - camera information 251 // This test requires HEIF Image Extensions to be installed from Microsoft Store 252 WICMetadataExtractor extractor; 253 EXIFMetadata metadata; 254 255 std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic"; 256 257 // Check if file exists first 258 if (!std::filesystem::exists(testFile)) 259 { 260 Logger::WriteMessage(L"HEIF test file not found, skipping test"); 261 return; 262 } 263 264 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 265 266 // If HEIF extension is not installed, extraction may fail 267 if (!result) 268 { 269 Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed"); 270 return; 271 } 272 273 Assert::IsTrue(result, L"HEIF EXIF extraction should succeed"); 274 275 // Verify camera information from iPhone 276 Assert::IsTrue(metadata.cameraMake.has_value(), L"Camera make should be present"); 277 Assert::AreEqual(L"Apple", metadata.cameraMake.value().c_str(), L"Camera make should be Apple"); 278 279 Assert::IsTrue(metadata.cameraModel.has_value(), L"Camera model should be present"); 280 // Model should contain "iPhone" 281 Assert::IsTrue(metadata.cameraModel.value().find(L"iPhone") != std::wstring::npos, 282 L"Camera model should contain iPhone"); 283 } 284 285 TEST_METHOD(ExtractHEIF_EXIF_DateTaken) 286 { 287 // Test HEIF EXIF extraction - date taken 288 WICMetadataExtractor extractor; 289 EXIFMetadata metadata; 290 291 std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic"; 292 293 if (!std::filesystem::exists(testFile)) 294 { 295 Logger::WriteMessage(L"HEIF test file not found, skipping test"); 296 return; 297 } 298 299 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 300 301 if (!result) 302 { 303 Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed"); 304 return; 305 } 306 307 Assert::IsTrue(result, L"HEIF EXIF extraction should succeed"); 308 309 // Verify date taken is present 310 Assert::IsTrue(metadata.dateTaken.has_value(), L"Date taken should be present"); 311 312 // Verify the date is a reasonable year (2020-2030 range) 313 SYSTEMTIME dt = metadata.dateTaken.value(); 314 Assert::IsTrue(dt.wYear >= 2020 && dt.wYear <= 2030, L"Date taken year should be reasonable"); 315 } 316 317 TEST_METHOD(ExtractHEIF_EXIF_ShootingParameters) 318 { 319 // Test HEIF EXIF extraction - shooting parameters 320 WICMetadataExtractor extractor; 321 EXIFMetadata metadata; 322 323 std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic"; 324 325 if (!std::filesystem::exists(testFile)) 326 { 327 Logger::WriteMessage(L"HEIF test file not found, skipping test"); 328 return; 329 } 330 331 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 332 333 if (!result) 334 { 335 Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed"); 336 return; 337 } 338 339 Assert::IsTrue(result, L"HEIF EXIF extraction should succeed"); 340 341 // Verify shooting parameters are present 342 Assert::IsTrue(metadata.iso.has_value(), L"ISO should be present"); 343 Assert::IsTrue(metadata.iso.value() > 0, L"ISO should be positive"); 344 345 Assert::IsTrue(metadata.aperture.has_value(), L"Aperture should be present"); 346 Assert::IsTrue(metadata.aperture.value() > 0, L"Aperture should be positive"); 347 348 Assert::IsTrue(metadata.shutterSpeed.has_value(), L"Shutter speed should be present"); 349 Assert::IsTrue(metadata.shutterSpeed.value() > 0, L"Shutter speed should be positive"); 350 351 Assert::IsTrue(metadata.focalLength.has_value(), L"Focal length should be present"); 352 Assert::IsTrue(metadata.focalLength.value() > 0, L"Focal length should be positive"); 353 } 354 355 TEST_METHOD(ExtractHEIF_EXIF_GPS) 356 { 357 // Test HEIF EXIF extraction - GPS coordinates 358 WICMetadataExtractor extractor; 359 EXIFMetadata metadata; 360 361 std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic"; 362 363 if (!std::filesystem::exists(testFile)) 364 { 365 Logger::WriteMessage(L"HEIF test file not found, skipping test"); 366 return; 367 } 368 369 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 370 371 if (!result) 372 { 373 Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed"); 374 return; 375 } 376 377 Assert::IsTrue(result, L"HEIF EXIF extraction should succeed"); 378 379 // Verify GPS coordinates are present (if the test file has GPS data) 380 if (metadata.latitude.has_value() && metadata.longitude.has_value()) 381 { 382 // Latitude should be between -90 and 90 383 Assert::IsTrue(metadata.latitude.value() >= -90.0 && metadata.latitude.value() <= 90.0, 384 L"Latitude should be valid"); 385 386 // Longitude should be between -180 and 180 387 Assert::IsTrue(metadata.longitude.value() >= -180.0 && metadata.longitude.value() <= 180.0, 388 L"Longitude should be valid"); 389 } 390 else 391 { 392 Logger::WriteMessage(L"GPS data not present in test file"); 393 } 394 } 395 396 TEST_METHOD(ExtractHEIF_EXIF_ImageDimensions) 397 { 398 // Test HEIF EXIF extraction - image dimensions 399 WICMetadataExtractor extractor; 400 EXIFMetadata metadata; 401 402 std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic"; 403 404 if (!std::filesystem::exists(testFile)) 405 { 406 Logger::WriteMessage(L"HEIF test file not found, skipping test"); 407 return; 408 } 409 410 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 411 412 if (!result) 413 { 414 Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed"); 415 return; 416 } 417 418 Assert::IsTrue(result, L"HEIF EXIF extraction should succeed"); 419 420 // Verify image dimensions are present 421 Assert::IsTrue(metadata.width.has_value(), L"Width should be present"); 422 Assert::IsTrue(metadata.width.value() > 0, L"Width should be positive"); 423 424 Assert::IsTrue(metadata.height.has_value(), L"Height should be present"); 425 Assert::IsTrue(metadata.height.value() > 0, L"Height should be positive"); 426 } 427 428 TEST_METHOD(ExtractHEIF_EXIF_LensModel) 429 { 430 // Test HEIF EXIF extraction - lens model 431 WICMetadataExtractor extractor; 432 EXIFMetadata metadata; 433 434 std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic"; 435 436 if (!std::filesystem::exists(testFile)) 437 { 438 Logger::WriteMessage(L"HEIF test file not found, skipping test"); 439 return; 440 } 441 442 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 443 444 if (!result) 445 { 446 Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed"); 447 return; 448 } 449 450 Assert::IsTrue(result, L"HEIF EXIF extraction should succeed"); 451 452 // Verify lens model is present (iPhone photos typically have this) 453 if (metadata.lensModel.has_value()) 454 { 455 Assert::IsFalse(metadata.lensModel.value().empty(), L"Lens model should not be empty"); 456 } 457 else 458 { 459 Logger::WriteMessage(L"Lens model not present in test file"); 460 } 461 } 462 }; 463 464 TEST_CLASS(ExtractAVIFMetadataTests) 465 { 466 public: 467 TEST_METHOD(ExtractAVIF_EXIF_CameraInfo) 468 { 469 // Test AVIF EXIF extraction - camera information 470 // This test requires AV1 Video Extension to be installed from Microsoft Store 471 WICMetadataExtractor extractor; 472 EXIFMetadata metadata; 473 474 std::wstring testFile = GetTestDataPath() + L"\\avif_test.avif"; 475 476 if (!std::filesystem::exists(testFile)) 477 { 478 Logger::WriteMessage(L"AVIF test file not found, skipping test"); 479 return; 480 } 481 482 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 483 484 if (!result) 485 { 486 Logger::WriteMessage(L"AVIF extraction failed - AV1 Video Extension may not be installed"); 487 return; 488 } 489 490 Assert::IsTrue(result, L"AVIF EXIF extraction should succeed"); 491 492 // Verify camera information 493 if (metadata.cameraMake.has_value()) 494 { 495 Assert::IsFalse(metadata.cameraMake.value().empty(), L"Camera make should not be empty"); 496 } 497 498 if (metadata.cameraModel.has_value()) 499 { 500 Assert::IsFalse(metadata.cameraModel.value().empty(), L"Camera model should not be empty"); 501 } 502 } 503 504 TEST_METHOD(ExtractAVIF_EXIF_DateTaken) 505 { 506 // Test AVIF EXIF extraction - date taken 507 WICMetadataExtractor extractor; 508 EXIFMetadata metadata; 509 510 std::wstring testFile = GetTestDataPath() + L"\\avif_test.avif"; 511 512 if (!std::filesystem::exists(testFile)) 513 { 514 Logger::WriteMessage(L"AVIF test file not found, skipping test"); 515 return; 516 } 517 518 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 519 520 if (!result) 521 { 522 Logger::WriteMessage(L"AVIF extraction failed - AV1 Video Extension may not be installed"); 523 return; 524 } 525 526 Assert::IsTrue(result, L"AVIF EXIF extraction should succeed"); 527 528 // Verify date taken is present 529 if (metadata.dateTaken.has_value()) 530 { 531 SYSTEMTIME dt = metadata.dateTaken.value(); 532 Assert::IsTrue(dt.wYear >= 2000 && dt.wYear <= 2100, L"Date taken year should be reasonable"); 533 } 534 else 535 { 536 Logger::WriteMessage(L"Date taken not present in AVIF test file"); 537 } 538 } 539 540 TEST_METHOD(ExtractAVIF_EXIF_ImageDimensions) 541 { 542 // Test AVIF EXIF extraction - image dimensions 543 WICMetadataExtractor extractor; 544 EXIFMetadata metadata; 545 546 std::wstring testFile = GetTestDataPath() + L"\\avif_test.avif"; 547 548 if (!std::filesystem::exists(testFile)) 549 { 550 Logger::WriteMessage(L"AVIF test file not found, skipping test"); 551 return; 552 } 553 554 bool result = extractor.ExtractEXIFMetadata(testFile, metadata); 555 556 if (!result) 557 { 558 Logger::WriteMessage(L"AVIF extraction failed - AV1 Video Extension may not be installed"); 559 return; 560 } 561 562 Assert::IsTrue(result, L"AVIF EXIF extraction should succeed"); 563 564 // Verify image dimensions are present 565 if (metadata.width.has_value()) 566 { 567 Assert::IsTrue(metadata.width.value() > 0, L"Width should be positive"); 568 } 569 570 if (metadata.height.has_value()) 571 { 572 Assert::IsTrue(metadata.height.value() > 0, L"Height should be positive"); 573 } 574 } 575 }; 576 }