/ src / modules / powerrename / unittests / WICMetadataExtractorTests.cpp
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  }