"""Tests pour le module xmp_writer.""" from json_to_metadata.xmp_writer import ( _convert_exif_to_xmp, _format_rational, _format_xmp_date, generate_xmp_content, write_xmp_sidecar, ) class TestGenerateXmpContent: """Tests pour la fonction generate_xmp_content.""" def test_basic_structure(self): """Génère une structure XMP valide.""" content = generate_xmp_content({}) assert 'Nikon' in content assert 'FM2' in content def test_maps_exposure(self): """Mappe les paramètres d'exposition.""" tags = {'ISO': 400, 'FNumber': 2.8} content = generate_xmp_content(tags) assert 'ISOSpeedRatings' in content assert '400' in content assert 'FNumber' in content def test_escapes_special_characters(self): """Échappe les caractères spéciaux XML.""" tags = {'ImageDescription': 'Test des & caractères "spéciaux"'} content = generate_xmp_content(tags) assert '<avec>' in content assert '&' in content def test_includes_modify_date(self): """Inclut la date de modification.""" content = generate_xmp_content({}) assert 'xmp:ModifyDate' in content class TestWriteXmpSidecar: """Tests pour la fonction write_xmp_sidecar.""" def test_creates_xmp_file(self, tmp_path): """Crée un fichier XMP à côté de l'image.""" image = tmp_path / 'photo.tif' image.write_bytes(b'fake image') tags = {'Make': 'Nikon'} xmp_path = write_xmp_sidecar(image, tags) assert xmp_path.exists() assert xmp_path.name == 'photo.xmp' assert xmp_path.parent == tmp_path def test_xmp_content_valid(self, tmp_path): """Le contenu du fichier XMP est valide.""" image = tmp_path / 'photo.jpg' image.write_bytes(b'fake image') tags = {'Make': 'Nikon', 'Model': 'FM2'} xmp_path = write_xmp_sidecar(image, tags) content = xmp_path.read_text(encoding='utf-8') assert 'Nikon' in content def test_dry_run_mode(self, tmp_path): """Le mode dry-run ne crée pas de fichier.""" image = tmp_path / 'photo.tif' image.write_bytes(b'fake image') xmp_path = write_xmp_sidecar(image, {'Make': 'Nikon'}, dry_run=True) assert not xmp_path.exists() assert xmp_path.name == 'photo.xmp' def test_overwrites_existing_xmp(self, tmp_path): """Écrase un fichier XMP existant.""" image = tmp_path / 'photo.tif' image.write_bytes(b'fake image') xmp_path = tmp_path / 'photo.xmp' xmp_path.write_text('old content') write_xmp_sidecar(image, {'Make': 'Canon'}) content = xmp_path.read_text() assert 'Canon' in content assert 'old content' not in content def test_handles_different_extensions(self, tmp_path): """Fonctionne avec différentes extensions d'images.""" for ext in ['.tif', '.avif', '.heic', '.webp', '.png']: image = tmp_path / f'photo{ext}' image.write_bytes(b'fake image') xmp_path = write_xmp_sidecar(image, {'Make': 'Test'}) assert xmp_path.suffix == '.xmp' assert xmp_path.stem == 'photo' xmp_path.unlink() # Nettoyer class TestConvertExifToXmp: """Tests pour la fonction _convert_exif_to_xmp.""" def test_make_mapping(self): """Mappe Make vers tiff:Make.""" result = _convert_exif_to_xmp('Make', 'Nikon') assert 'Nikon' in result def test_iso_mapping(self): """Mappe ISO avec structure rdf:Seq.""" result = _convert_exif_to_xmp('ISO', 400) assert 'ISOSpeedRatings' in result assert 'rdf:Seq' in result assert '400' in result def test_date_mapping(self): """Mappe DateTimeOriginal vers xmp:CreateDate.""" result = _convert_exif_to_xmp('DateTimeOriginal', '2024:03:15 14:30:00') assert 'xmp:CreateDate' in result assert '2024-03-15T14:30:00' in result def test_gps_mapping(self): """Mappe les coordonnées GPS.""" lat = _convert_exif_to_xmp('GPSLatitude', 48.8584) lat_ref = _convert_exif_to_xmp('GPSLatitudeRef', 'N') assert 'exif:GPSLatitude' in lat assert '48.8584' in lat assert 'exif:GPSLatitudeRef' in lat_ref assert 'N' in lat_ref def test_description_uses_alt(self): """ImageDescription utilise rdf:Alt pour le multilingue.""" result = _convert_exif_to_xmp('ImageDescription', 'Ma photo') assert 'dc:description' in result assert 'rdf:Alt' in result assert 'x-default' in result def test_unknown_tag_returns_none(self): """Les tags inconnus retournent None.""" result = _convert_exif_to_xmp('UnknownTag', 'value') assert result is None class TestFormatRational: """Tests pour la fonction _format_rational.""" def test_integer_value(self): """Formate un entier comme chaîne.""" assert _format_rational(50) == '50' assert _format_rational(100.0) == '100' def test_fraction_value(self): """Formate une fraction correctement.""" result = _format_rational(0.008) # 1/125 assert '1/125' in result def test_decimal_passthrough(self): """Retourne les décimaux non-fractions tels quels.""" result = _format_rational(2.8) assert '2.8' in result class TestFormatXmpDate: """Tests pour la fonction _format_xmp_date.""" def test_exif_to_xmp_format(self): """Convertit le format EXIF en ISO.""" result = _format_xmp_date('2024:03:15 14:30:00') assert result == '2024-03-15T14:30:00' def test_iso_passthrough(self): """Laisse passer les dates déjà au format ISO.""" result = _format_xmp_date('2024-03-15T14:30:00') assert result == '2024-03-15T14:30:00' def test_date_only(self): """Gère les dates sans heure.""" result = _format_xmp_date('2024-03-15') assert '2024-03-15' in result