import pytest import os import yaml import tempfile from unittest.mock import Mock from f02_1_flight_lifecycle_manager import CameraParameters from f17_configuration_manager import ( ConfigurationManager, SystemConfig, OperationalArea, ModelPaths, FlightConfig ) @pytest.fixture def valid_yaml_content(): return """ camera: focal_length_mm: 35.0 sensor_width_mm: 36.0 resolution: width: 3840 height: 2160 operational_area: name: "Test Area" min_lat: 40.0 max_lat: 45.0 min_lon: -10.0 max_lon: 10.0 """ @pytest.fixture def temp_config_file(valid_yaml_content): fd, path = tempfile.mkstemp(suffix=".yaml") with os.fdopen(fd, 'w') as f: f.write(valid_yaml_content) yield path os.remove(path) @pytest.fixture def mock_db(): db = Mock() flight_mock = Mock() flight_mock.camera_params = CameraParameters(focal_length_mm=25, sensor_width_mm=36, resolution={"width": 1920, "height": 1080}) flight_mock.altitude_m = 400.0 db.get_flight_by_id.return_value = flight_mock return db @pytest.fixture def cm(mock_db): return ConfigurationManager(f03_database=mock_db) class TestConfigurationManager: # --- 17.01 Feature: System Configuration --- def test_load_config_valid_yaml(self, cm, temp_config_file): config = cm.load_config(temp_config_file) assert isinstance(config, SystemConfig) assert config.camera.focal_length_mm == 35.0 assert config.operational_area.name == "Test Area" def test_load_config_missing_file_uses_defaults(self, cm): config = cm.load_config("nonexistent_file.yaml") assert config.camera.focal_length_mm == 25.0 # Default value assert config.operational_area.name == "Eastern Ukraine" def test_load_config_invalid_yaml_raises_error(self, cm): fd, path = tempfile.mkstemp(suffix=".yaml") with os.fdopen(fd, 'w') as f: f.write("invalid: yaml: : syntax") with pytest.raises(ValueError, match="Malformed YAML"): cm.load_config(path) os.remove(path) def test_load_config_partial_uses_defaults(self, cm): fd, path = tempfile.mkstemp(suffix=".yaml") with os.fdopen(fd, 'w') as f: f.write("camera:\n focal_length_mm: 50.0\n") config = cm.load_config(path) assert config.camera.focal_length_mm == 50.0 assert config.camera.sensor_width_mm == 36.0 # Kept default assert config.database.url == "sqlite:///flights.db" # Default database os.remove(path) def test_validate_config_valid(self, cm): config = cm._apply_defaults({}) val = cm.validate_config(config) assert val.is_valid is True def test_validate_config_invalid_focal_length(self, cm): config = cm._apply_defaults({}) config.camera.focal_length_mm = -10.0 val = cm.validate_config(config) assert val.is_valid is False assert any("Focal length" in e for e in val.errors) def test_validate_config_invalid_operational_area(self, cm): config = cm._apply_defaults({}) config.operational_area.min_lat = 100.0 # Invalid lat val = cm.validate_config(config) assert val.is_valid is False assert any("latitude bounds" in e for e in val.errors) def test_get_camera_params_default(self, cm, temp_config_file): cm.load_config(temp_config_file) params = cm.get_camera_params() assert params.focal_length_mm == 35.0 assert params.resolution["width"] == 3840 def test_update_config_valid_key(self, cm, temp_config_file): cm.load_config(temp_config_file) assert cm.update_config("operational_area", "name", "Western Ukraine") is True assert cm._system_config.operational_area.name == "Western Ukraine" def test_update_config_invalid_section(self, cm, temp_config_file): cm.load_config(temp_config_file) assert cm.update_config("nonexistent", "key", "value") is False # --- 17.02 Feature: Flight Configuration --- def test_get_flight_config_existing_via_db(self, cm): # Should use mock_db to fetch the config config = cm.get_flight_config("flight_123") assert isinstance(config, FlightConfig) assert config.altitude == 400.0 assert config.camera_params.focal_length_mm == 25.0 def test_get_flight_config_nonexistent(self, cm): cm.db.get_flight_by_id.return_value = None with pytest.raises(ValueError, match="not found"): cm.get_flight_config("missing_flight") def test_save_flight_config_valid(self, cm): config = FlightConfig( camera_params=CameraParameters(focal_length_mm=50, sensor_width_mm=36, resolution={"width": 100, "height": 100}), altitude=300.0 ) assert cm.save_flight_config("flight_saved", config) is True # Verify retrieval via cache instead of DB retrieved = cm.get_flight_config("flight_saved") assert retrieved.altitude == 300.0 def test_save_flight_config_invalid_flight_id(self, cm): assert cm.save_flight_config("", None) is False def test_get_operational_altitude_existing(self, cm): assert cm.get_operational_altitude("flight_123") == 400.0 def test_get_frame_spacing_existing(self, cm): assert cm.get_frame_spacing("flight_123") == 100.0 def test_get_frame_spacing_default(self, cm): cm.db.get_flight_by_id.return_value = None # Should fallback to default 100.0 on error assert cm.get_frame_spacing("missing_flight") == 100.0