2026-04-25 17:22:41 +03:30
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from types import SimpleNamespace
|
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
|
|
|
|
from django.test import TestCase, override_settings
|
|
|
|
|
from rest_framework.test import APIClient
|
|
|
|
|
|
|
|
|
|
from irrigation.indicators import WaterStressService
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override_settings(ROOT_URLCONF="irrigation.urls")
|
|
|
|
|
class WaterStressApiTests(TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
self.client = APIClient()
|
|
|
|
|
|
|
|
|
|
@patch("irrigation.views.apps.get_app_config")
|
|
|
|
|
def test_water_stress_api_returns_payload(self, mock_get_app_config):
|
|
|
|
|
mock_service = SimpleNamespace(
|
|
|
|
|
get_water_stress=lambda **_kwargs: {
|
|
|
|
|
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
|
|
|
|
"waterStressIndex": 12,
|
|
|
|
|
"level": "پایین",
|
|
|
|
|
"sourceMetric": {"soilMoisture": 46.0},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
mock_get_app_config.return_value = SimpleNamespace(
|
|
|
|
|
get_water_stress_service=lambda: mock_service
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = self.client.post(
|
|
|
|
|
"/water-stress/",
|
2026-05-02 14:03:48 +03:30
|
|
|
data={
|
|
|
|
|
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
|
|
|
|
"irrigation_recommendation": {"events": [{"date": "2026-04-25", "amount": 2.5}]},
|
|
|
|
|
"fertilization_recommendation": {
|
|
|
|
|
"events": [{"date": "2026-04-20", "N_amount": 45, "N_recovery": 0.7}]
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-04-25 17:22:41 +03:30
|
|
|
format="json",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
self.assertEqual(response.json()["data"]["waterStressIndex"], 12)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WaterStressServiceTests(TestCase):
|
|
|
|
|
@patch("irrigation.indicators.apps.get_app_config")
|
|
|
|
|
@patch("irrigation.indicators.SensorData.objects.filter")
|
|
|
|
|
def test_service_uses_crop_simulation_water_stress(self, mock_filter, mock_get_app_config):
|
|
|
|
|
mock_filter.return_value.first.return_value = SimpleNamespace(
|
|
|
|
|
farm_uuid="550e8400-e29b-41d4-a716-446655440000",
|
|
|
|
|
soil_moisture=46.0,
|
|
|
|
|
)
|
|
|
|
|
mock_service = SimpleNamespace(
|
|
|
|
|
get_water_stress=lambda **_kwargs: {
|
|
|
|
|
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
|
|
|
|
"plant_name": "Tomato",
|
|
|
|
|
"waterStressIndex": 37,
|
|
|
|
|
"level": "متوسط",
|
|
|
|
|
"sourceMetric": {"engine": "crop_simulation"},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
mock_get_app_config.return_value = SimpleNamespace(
|
|
|
|
|
get_water_stress_service=lambda: mock_service
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
payload = WaterStressService().get_water_stress(
|
|
|
|
|
farm_uuid="550e8400-e29b-41d4-a716-446655440000"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(payload["waterStressIndex"], 37)
|
|
|
|
|
self.assertEqual(payload["sourceMetric"]["engine"], "crop_simulation")
|
|
|
|
|
|
|
|
|
|
@patch("irrigation.indicators.apps.get_app_config")
|
|
|
|
|
@patch("irrigation.indicators.SensorData.objects.filter")
|
2026-04-27 18:02:26 +03:30
|
|
|
def test_service_raises_when_crop_simulation_fails(self, mock_filter, mock_get_app_config):
|
2026-04-25 17:22:41 +03:30
|
|
|
mock_filter.return_value.first.return_value = SimpleNamespace(
|
|
|
|
|
farm_uuid="550e8400-e29b-41d4-a716-446655440000",
|
|
|
|
|
soil_moisture=46.0,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class BrokenService:
|
|
|
|
|
def get_water_stress(self, **_kwargs):
|
|
|
|
|
raise RuntimeError("simulation offline")
|
|
|
|
|
|
|
|
|
|
mock_get_app_config.return_value = SimpleNamespace(
|
|
|
|
|
get_water_stress_service=lambda: BrokenService()
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-27 18:02:26 +03:30
|
|
|
with self.assertRaises(RuntimeError):
|
|
|
|
|
WaterStressService().get_water_stress(
|
|
|
|
|
farm_uuid="550e8400-e29b-41d4-a716-446655440000"
|
|
|
|
|
)
|