"""Tests pour le module json_parser.""" import json from pathlib import Path import pytest from json_to_metadata.json_parser import ( ValidationError, load_json, validate_frame, validate_roll, ) class TestLoadJson: """Tests pour la fonction load_json.""" def test_load_valid_json(self, tmp_path): """Charge un fichier JSON valide.""" data = {'id': 1, 'frames': []} json_file = tmp_path / 'test.json' json_file.write_text(json.dumps(data), encoding='utf-8') result = load_json(json_file) assert result == data def test_load_json_file_not_found(self): """Lève une exception si le fichier n'existe pas.""" with pytest.raises(FileNotFoundError): load_json(Path('/nonexistent/file.json')) def test_load_invalid_json(self, tmp_path): """Lève une exception pour un JSON malformé.""" json_file = tmp_path / 'invalid.json' json_file.write_text('{ invalid json }', encoding='utf-8') with pytest.raises(json.JSONDecodeError): load_json(json_file) def test_load_json_with_unicode(self, tmp_path): """Charge un JSON avec des caractères Unicode.""" data = {'note': 'Café à Paris 🇫🇷'} json_file = tmp_path / 'unicode.json' json_file.write_text(json.dumps(data, ensure_ascii=False), encoding='utf-8') result = load_json(json_file) assert result['note'] == 'Café à Paris 🇫🇷' class TestValidateRoll: """Tests pour la fonction validate_roll.""" def test_valid_roll_minimal(self): """Valide un roll avec les champs minimaux.""" roll = { 'id': 1, 'frames': [{'id': 1}] } assert validate_roll(roll) is True def test_valid_roll_complete(self): """Valide un roll complet avec tous les champs optionnels.""" roll = { 'id': 'roll-001', 'frames': [ {'id': 1, 'date': '2024-03-15T14:30:00'} ], 'camera': {'make': 'Nikon', 'model': 'FM2'}, 'filmStock': {'make': 'Kodak', 'model': 'Portra 400'}, 'iso': 400 } assert validate_roll(roll) is True def test_missing_id(self): """Lève une exception si l'id est manquant.""" roll = {'frames': [{'id': 1}]} with pytest.raises(ValidationError, match="'id'"): validate_roll(roll) def test_missing_frames(self): """Lève une exception si frames est manquant.""" roll = {'id': 1} with pytest.raises(ValidationError, match="'frames'"): validate_roll(roll) def test_empty_frames(self): """Lève une exception si frames est vide.""" roll = {'id': 1, 'frames': []} with pytest.raises(ValidationError, match="aucun frame"): validate_roll(roll) def test_frames_not_list(self): """Lève une exception si frames n'est pas une liste.""" roll = {'id': 1, 'frames': 'invalid'} with pytest.raises(ValidationError, match="liste"): validate_roll(roll) def test_invalid_camera_type(self): """Lève une exception si camera n'est pas un dict.""" roll = { 'id': 1, 'frames': [{'id': 1}], 'camera': 'Nikon FM2' } with pytest.raises(ValidationError, match="dictionnaire"): validate_roll(roll) def test_invalid_iso_type(self): """Lève une exception si iso n'est pas numérique.""" roll = { 'id': 1, 'frames': [{'id': 1}], 'iso': 'four hundred' } with pytest.raises(ValidationError, match="numérique"): validate_roll(roll) def test_frame_validation_error_propagation(self): """Les erreurs de validation des frames sont propagées.""" roll = { 'id': 1, 'frames': [ {'id': 1}, {'note': 'missing id'} # Frame invalide ] } with pytest.raises(ValidationError, match="frame 2"): validate_roll(roll) class TestValidateFrame: """Tests pour la fonction validate_frame.""" def test_valid_frame_minimal(self): """Valide un frame avec les champs minimaux.""" frame = {'id': 1} assert validate_frame(frame) is True def test_valid_frame_complete(self): """Valide un frame complet.""" frame = { 'id': 1, 'date': '2024-03-15T14:30:00', 'aperture': 'f/2.8', 'shutter': '1/125', 'focalLength': 50, 'location': {'latitude': 48.8584, 'longitude': 2.2945}, 'note': 'Tour Eiffel' } assert validate_frame(frame) is True def test_missing_id(self): """Lève une exception si l'id est manquant.""" frame = {'date': '2024-03-15'} with pytest.raises(ValidationError, match="'id'"): validate_frame(frame) def test_invalid_frame_type(self): """Lève une exception si le frame n'est pas un dict.""" with pytest.raises(ValidationError, match="dictionnaire"): validate_frame("not a frame") def test_valid_aperture_formats(self): """Accepte différents formats d'ouverture.""" frames = [ {'id': 1, 'aperture': 2.8}, {'id': 2, 'aperture': 'f/2.8'}, {'id': 3, 'aperture': 'f2.8'}, {'id': 4, 'aperture': '5.6'}, ] for frame in frames: assert validate_frame(frame) is True def test_invalid_aperture(self): """Lève une exception pour une ouverture invalide.""" frame = {'id': 1, 'aperture': 'wide open'} with pytest.raises(ValidationError, match="ouverture"): validate_frame(frame) def test_valid_shutter_formats(self): """Accepte différents formats de vitesse.""" frames = [ {'id': 1, 'shutter': '1/125'}, {'id': 2, 'shutter': '1/125s'}, {'id': 3, 'shutter': '2'}, {'id': 4, 'shutter': '2s'}, {'id': 5, 'shutter': 0.5}, {'id': 6, 'shutter': '2"'}, # 2 secondes avec guillemet {'id': 7, 'shutter': "4'"}, # 4 secondes avec apostrophe {'id': 8, 'shutter': 'B'}, # Mode Bulb {'id': 9, 'shutter': 'bulb'}, # Mode Bulb (minuscule) ] for frame in frames: assert validate_frame(frame) is True def test_invalid_shutter(self): """Lève une exception pour une vitesse invalide.""" frame = {'id': 1, 'shutter': 'fast'} with pytest.raises(ValidationError, match="vitesse"): validate_frame(frame) def test_valid_gps_coordinates(self): """Accepte des coordonnées GPS valides.""" frame = { 'id': 1, 'location': { 'latitude': 48.8584, 'longitude': 2.2945 } } assert validate_frame(frame) is True def test_invalid_latitude_range(self): """Lève une exception pour une latitude hors limites.""" frame = { 'id': 1, 'location': {'latitude': 91.0, 'longitude': 0} } with pytest.raises(ValidationError, match="latitude"): validate_frame(frame) def test_invalid_longitude_range(self): """Lève une exception pour une longitude hors limites.""" frame = { 'id': 1, 'location': {'latitude': 0, 'longitude': 181.0} } with pytest.raises(ValidationError, match="longitude"): validate_frame(frame) def test_null_optional_fields(self): """Accepte des champs optionnels à None.""" frame = { 'id': 1, 'date': None, 'aperture': None, 'shutter': None, 'location': None } assert validate_frame(frame) is True