Spaces:
Sleeping
Sleeping
| # tests/test_models.py | |
| """Tests for AquaGuard-RL data models (Action, Observation, State).""" | |
| import sys | |
| import os | |
| import pytest | |
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) | |
| from models import AquaGuardAction, AquaGuardState, ZoneObservation, CropObservation | |
| class TestAquaGuardAction: | |
| """Tests for AquaGuardAction validation.""" | |
| def test_default_action_is_valid(self): | |
| """Default action should be valid.""" | |
| action = AquaGuardAction() | |
| assert action is not None | |
| total = sum(action.crop_allocation.values()) | |
| assert total <= 1.001 | |
| def test_allocation_sum_valid(self): | |
| """Valid allocation summing to exactly 1.0.""" | |
| action = AquaGuardAction( | |
| crop_allocation={ | |
| "rice": 0.20, "wheat": 0.20, "millet": 0.20, | |
| "pulses": 0.15, "oilseeds": 0.15, "vegetables": 0.10, | |
| } | |
| ) | |
| assert abs(sum(action.crop_allocation.values()) - 1.0) < 0.01 | |
| def test_allocation_sum_too_high_raises(self): | |
| """Allocation sum > 1.0 should raise ValueError.""" | |
| with pytest.raises(Exception): # pydantic ValidationError | |
| AquaGuardAction( | |
| crop_allocation={ | |
| "rice": 0.40, "wheat": 0.40, "millet": 0.30, | |
| "pulses": 0.10, "oilseeds": 0.05, "vegetables": 0.03, | |
| } | |
| ) | |
| def test_negative_allocation_raises(self): | |
| """Negative allocation should raise ValueError.""" | |
| with pytest.raises(Exception): | |
| AquaGuardAction( | |
| crop_allocation={"rice": -0.1, "wheat": 0.5} | |
| ) | |
| def test_invalid_crop_raises(self): | |
| """Unknown crop in allocation should raise ValueError.""" | |
| with pytest.raises(Exception): | |
| AquaGuardAction(crop_allocation={"corn": 0.5, "rice": 0.3}) | |
| def test_water_quota_bounds(self): | |
| """Water quota must be in [0, 2000].""" | |
| # Valid | |
| action = AquaGuardAction(water_quotas={"zone_a": 0, "zone_b": 2000, "zone_c": 500}) | |
| assert action.water_quotas["zone_b"] == 2000 | |
| # Invalid | |
| with pytest.raises(Exception): | |
| AquaGuardAction(water_quotas={"zone_a": 2001}) | |
| def test_invalid_irrigation_method_raises(self): | |
| """Invalid irrigation method should raise ValueError.""" | |
| with pytest.raises(Exception): | |
| AquaGuardAction(irrigation_methods={"zone_a": "canal"}) | |
| def test_valid_irrigation_methods(self): | |
| """All valid irrigation methods should be accepted.""" | |
| for method in ["flood", "sprinkler", "drip"]: | |
| action = AquaGuardAction( | |
| irrigation_methods={"zone_a": method, "zone_b": method, "zone_c": method} | |
| ) | |
| assert action.irrigation_methods["zone_a"] == method | |
| def test_extraction_limit_bounds(self): | |
| """Extraction limit must be in [0, 60].""" | |
| with pytest.raises(Exception): | |
| AquaGuardAction(extraction_limits={"zone_a": 61.0}) | |
| def test_subsidy_adjustment_bounds(self): | |
| """Subsidy adjustment must be in [-1, 1].""" | |
| with pytest.raises(Exception): | |
| AquaGuardAction(subsidy_adjustments={"rice": 1.5}) | |
| with pytest.raises(Exception): | |
| AquaGuardAction(subsidy_adjustments={"rice": -1.5}) | |
| def test_justification_max_length(self): | |
| """Justification cannot exceed 2000 characters.""" | |
| # Valid: empty | |
| action = AquaGuardAction(justification="") | |
| assert action.justification == "" | |
| # Valid: long | |
| action = AquaGuardAction(justification="x" * 2000) | |
| assert len(action.justification) == 2000 | |
| def test_partial_allocation_valid(self): | |
| """Allocation summing to less than 1.0 (fallow land) is valid.""" | |
| action = AquaGuardAction( | |
| crop_allocation={"rice": 0.30, "wheat": 0.20} | |
| ) | |
| assert sum(action.crop_allocation.values()) < 1.0 | |
| class TestAquaGuardState: | |
| """Tests for AquaGuardState model.""" | |
| def test_default_state_valid(self): | |
| """Default state should be valid.""" | |
| state = AquaGuardState() | |
| assert state.task_name == "baseline" | |
| assert state.max_steps == 10 | |
| assert state.cumulative_reward == 0.0 | |
| def test_state_with_episode_id(self): | |
| """State with explicit episode_id.""" | |
| state = AquaGuardState(episode_id="test-123", step_count=5) | |
| assert state.episode_id == "test-123" | |
| assert state.step_count == 5 | |
| class TestZoneObservation: | |
| """Tests for ZoneObservation model.""" | |
| def test_valid_zone_obs(self): | |
| """Valid zone observation should be created.""" | |
| zone = ZoneObservation( | |
| zone_id="zone_a", | |
| groundwater_depth_m=25.0, | |
| groundwater_recharge_rate_mm_yr=180.0, | |
| soil_fertility=0.75, | |
| soil_salinity=0.12, | |
| arable_land_ha=80000.0, | |
| active_irrigation_method="flood", | |
| water_used_mm=900.0, | |
| is_in_danger_zone=False, | |
| is_collapsed=False, | |
| ) | |
| assert zone.zone_id == "zone_a" | |
| assert zone.groundwater_depth_m == 25.0 | |
| def test_fertility_bounds(self): | |
| """Soil fertility must be in [0, 1].""" | |
| with pytest.raises(Exception): | |
| ZoneObservation( | |
| zone_id="zone_a", | |
| groundwater_depth_m=25.0, | |
| groundwater_recharge_rate_mm_yr=180.0, | |
| soil_fertility=1.5, # Invalid | |
| soil_salinity=0.12, | |
| arable_land_ha=80000.0, | |
| active_irrigation_method="flood", | |
| water_used_mm=900.0, | |
| is_in_danger_zone=False, | |
| ) |