"""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