Files
Logic/Modules/Backend/yield_harvest/tests.py
T

738 lines
31 KiB
Python
Raw Normal View History

2026-05-11 03:27:21 +03:30
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test import TestCase
from rest_framework.test import APIClient, APIRequestFactory, force_authenticate
from config.observability import METRICS
from external_api_adapter.adapter import AdapterResponse
from farm_hub.models import FarmHub, FarmType, Product
from fertilization.models import FertilizationPlan
from irrigation.models import IrrigationPlan
from .views import (
CurrentFarmChartView,
GrowthSimulationStatusView,
GrowthSimulationView,
HarvestPredictionView,
YieldHarvestSummaryView,
YieldPredictionView,
)
class CropSimulationViewTests(TestCase):
def setUp(self):
self.api_client = APIClient()
self.factory = APIRequestFactory()
self.user = get_user_model().objects.create_user(
username="farmer",
password="secret123",
email="farmer@example.com",
phone_number="09120000000",
)
self.other_user = get_user_model().objects.create_user(
username="other-farmer",
password="secret123",
email="other@example.com",
phone_number="09120000001",
)
self.farm_type = FarmType.objects.create(name="زراعی")
self.farm = FarmHub.objects.create(owner=self.user, farm_type=self.farm_type, name="Farm 1")
self.other_farm = FarmHub.objects.create(owner=self.other_user, farm_type=self.farm_type, name="Farm 2")
self.product = Product.objects.create(farm_type=self.farm_type, name="گوجه‌فرنگی")
self.farm.products.add(self.product)
self.api_client.force_authenticate(user=self.user)
def tearDown(self):
METRICS.clear()
@patch("yield_harvest.views.external_api_request")
def test_growth_queues_simulation_task(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=202,
data={
"data": {
"task_id": "growth-task-123",
"status_url": "/api/crop-simulation/growth/growth-task-123/status/",
"plant_name": "گوجه‌فرنگی",
}
},
)
request = self.factory.post(
"/api/yield-harvest/crop-simulation/growth/",
{"plant_name": "گوجه‌فرنگی", "dynamic_parameters": ["DVS", "LAI"], "farm_uuid": str(self.farm.farm_uuid)},
format="json",
)
force_authenticate(request, user=self.user)
response = GrowthSimulationView.as_view()(request)
self.assertEqual(response.status_code, 202)
self.assertEqual(response.data["code"], 202)
self.assertEqual(response.data["data"]["task_id"], "growth-task-123")
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/growth/",
method="POST",
payload={
"plant_name": "گوجه‌فرنگی",
"dynamic_parameters": ["DVS", "LAI"],
"farm_uuid": str(self.farm.farm_uuid),
},
)
@patch("yield_harvest.views.external_api_request")
def test_growth_top_level_route_queues_simulation_task(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=202,
data={
"data": {
"task_id": "growth-task-123",
"status_url": "/api/crop-simulation/growth/growth-task-123/status/",
"plant_name": "گوجه‌فرنگی",
}
},
)
response = self.api_client.post(
"/api/crop-simulation/growth/",
{
"plant_name": "گوجه‌فرنگی",
"dynamic_parameters": ["DVS", "LAI"],
"farm_uuid": str(self.farm.farm_uuid),
},
format="json",
)
self.assertEqual(response.status_code, 202)
self.assertEqual(response.json()["data"]["task_id"], "growth-task-123")
@patch("yield_harvest.views.external_api_request")
def test_growth_yield_harvest_route_queues_simulation_task(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=202,
data={
"data": {
"task_id": "growth-task-123",
"status_url": "/api/crop-simulation/growth/growth-task-123/status/",
"plant_name": "گوجه‌فرنگی",
}
},
)
response = self.api_client.post(
"/api/yield-harvest/growth/",
{
"plant_name": "wheat",
"dynamic_parameters": ["DVS", "LAI"],
"farm_uuid": str(self.farm.farm_uuid),
},
format="json",
)
self.assertEqual(response.status_code, 202)
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/growth/",
method="POST",
payload={
"plant_name": "گوجه‌فرنگی",
"dynamic_parameters": ["DVS", "LAI"],
"farm_uuid": str(self.farm.farm_uuid),
},
)
def test_growth_requires_farm_uuid_or_weather(self):
request = self.factory.post(
"/api/yield-harvest/crop-simulation/growth/",
{"plant_name": "گوجه‌فرنگی", "dynamic_parameters": ["DVS"]},
format="json",
)
force_authenticate(request, user=self.user)
response = GrowthSimulationView.as_view()(request)
self.assertEqual(response.status_code, 400)
@patch("yield_harvest.views.external_api_request")
def test_growth_status_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={
"data": {
"task_id": "growth-task-123",
"status": "SUCCESS",
"message": "done",
"progress": {},
"result": {
"plant_name": "گوجه‌فرنگی",
"dynamic_parameters": ["DVS", "LAI"],
"scenario_id": 1,
},
"error": "",
}
},
)
request = self.factory.get("/api/yield-harvest/crop-simulation/growth/growth-task-123/status/?page=1&page_size=10")
force_authenticate(request, user=self.user)
response = GrowthSimulationStatusView.as_view()(request, task_id="growth-task-123")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["code"], 200)
self.assertEqual(response.data["data"]["status"], "SUCCESS")
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/growth/growth-task-123/status/",
method="GET",
query={"page": "1", "page_size": "10"},
)
@patch("yield_harvest.views.external_api_request")
def test_growth_status_top_level_route_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={
"data": {
"task_id": "growth-task-123",
"status": "SUCCESS",
"message": "done",
"progress": {},
"result": {
"plant_name": "گوجه‌فرنگی",
"dynamic_parameters": ["DVS", "LAI"],
"scenario_id": 1,
},
"error": "",
}
},
)
response = self.api_client.get("/api/crop-simulation/growth/growth-task-123/status/?page=1&page_size=10")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["data"]["status"], "SUCCESS")
@patch("yield_harvest.views.external_api_request")
def test_growth_status_yield_harvest_route_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"task_id": "growth-task-123", "status": "SUCCESS"}},
)
response = self.api_client.get("/api/yield-harvest/growth/growth-task-123/status/?page=1&page_size=10")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["data"]["status"], "SUCCESS")
def test_legacy_plant_simulator_routes_are_unavailable(self):
legacy_paths = [
"/api/yield-harvest/plant-simulator/config/",
"/api/yield-harvest/plant-simulator/environment/",
"/api/yield-harvest/plant-simulator/reset/",
"/api/yield-harvest/plant-simulator/start/",
"/api/yield-harvest/plant-simulator/state/",
"/api/yield-harvest/plant-simulator/stop/",
]
for path in legacy_paths:
response = self.client.get(path)
self.assertEqual(response.status_code, 404, path)
@patch("yield_harvest.views.external_api_request")
def test_current_farm_chart_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={
"data": {
"result": {
"farm_uuid": str(self.farm.farm_uuid),
"plant_name": "گوجه‌فرنگی",
"scenario_id": 1,
"categories": ["day1"],
"series": {"biomass": [1.2]},
}
}
},
)
request = self.factory.post(
"/api/yield-harvest/crop-simulation/current-farm-chart/",
{"farm_uuid": str(self.farm.farm_uuid), "plant_name": "wheat"},
format="json",
)
force_authenticate(request, user=self.user)
response = CurrentFarmChartView.as_view()(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["code"], 200)
self.assertEqual(response.data["data"]["scenario_id"], 1)
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/current-farm-chart/",
method="POST",
payload={"farm_uuid": str(self.farm.farm_uuid), "plant_name": "گوجه‌فرنگی"},
)
@patch("yield_harvest.views.external_api_request")
def test_current_farm_chart_top_level_route_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"farm_uuid": str(self.farm.farm_uuid), "plant_name": "wheat"}}},
)
response = self.api_client.post(
"/api/crop-simulation/current-farm-chart/",
{"farm_uuid": str(self.farm.farm_uuid), "plant_name": "wheat"},
format="json",
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["data"]["farm_uuid"], str(self.farm.farm_uuid))
@patch("yield_harvest.views.external_api_request")
def test_current_farm_chart_yield_harvest_route_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"farm_uuid": str(self.farm.farm_uuid), "plant_name": "گوجه‌فرنگی"}}},
)
response = self.api_client.post(
"/api/yield-harvest/current-farm-chart/",
{"farm_uuid": str(self.farm.farm_uuid), "plant_name": "wheat"},
format="json",
)
self.assertEqual(response.status_code, 200)
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/current-farm-chart/",
method="POST",
payload={"farm_uuid": str(self.farm.farm_uuid), "plant_name": "گوجه‌فرنگی"},
)
@patch("yield_harvest.views.external_api_request")
def test_harvest_prediction_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={
"data": {
"result": {
"date": "2026-07-15",
"dateFormatted": "15 Jul 2026",
"daysUntil": 96,
"gddDetails": {"current": 800},
}
}
},
)
request = self.factory.post(
"/api/yield-harvest/crop-simulation/harvest-prediction/",
{"farm_uuid": str(self.farm.farm_uuid)},
format="json",
)
force_authenticate(request, user=self.user)
response = HarvestPredictionView.as_view()(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["data"]["daysUntil"], 96)
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/harvest-prediction/",
method="POST",
payload={"farm_uuid": str(self.farm.farm_uuid), "plant_name": "گوجه‌فرنگی"},
)
@patch("yield_harvest.views.external_api_request")
def test_harvest_prediction_top_level_route_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"date": "2026-07-15", "daysUntil": 96}}},
)
response = self.api_client.post(
"/api/crop-simulation/harvest-prediction/",
{"farm_uuid": str(self.farm.farm_uuid)},
format="json",
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["data"]["daysUntil"], 96)
@patch("yield_harvest.views.external_api_request")
def test_harvest_prediction_yield_harvest_route_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"date": "2026-07-15", "daysUntil": 96}}},
)
response = self.api_client.post(
"/api/yield-harvest/harvest-prediction/",
{"farm_uuid": str(self.farm.farm_uuid)},
format="json",
)
self.assertEqual(response.status_code, 200)
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/harvest-prediction/",
method="POST",
payload={"farm_uuid": str(self.farm.farm_uuid), "plant_name": "گوجه‌فرنگی"},
)
@patch("yield_harvest.views.external_api_request")
def test_current_farm_chart_includes_selected_plans(self, mock_external_api_request):
irrigation_plan = IrrigationPlan.objects.create(
farm=self.farm,
source=IrrigationPlan.SOURCE_FREE_TEXT,
title="برنامه آبیاری",
plan_payload={"plan": {"durationMinutes": 20}},
)
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"farm_uuid": str(self.farm.farm_uuid), "series": []}}},
)
response = self.api_client.post(
"/api/yield-harvest/current-farm-chart/",
{"farm_uuid": str(self.farm.farm_uuid), "irrigation_plan_uuid": str(irrigation_plan.uuid)},
format="json",
)
self.assertEqual(response.status_code, 200)
sent_payload = mock_external_api_request.call_args.kwargs["payload"]
self.assertEqual(sent_payload["irrigation_plan"]["id"], irrigation_plan.id)
self.assertEqual(sent_payload["irrigation_plan"]["uuid"], str(irrigation_plan.uuid))
@patch("yield_harvest.views.external_api_request")
def test_harvest_prediction_includes_selected_plans(self, mock_external_api_request):
fertilization_plan = FertilizationPlan.objects.create(
farm=self.farm,
source=FertilizationPlan.SOURCE_FREE_TEXT,
title="برنامه کودی",
plan_payload={"primary_recommendation": {"fertilizer_code": "npk-151515"}},
)
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"date": "2026-07-15", "daysUntil": 96}}},
)
response = self.api_client.post(
"/api/yield-harvest/harvest-prediction/",
{"farm_uuid": str(self.farm.farm_uuid), "fertilization_plan_uuid": str(fertilization_plan.uuid)},
format="json",
)
self.assertEqual(response.status_code, 200)
sent_payload = mock_external_api_request.call_args.kwargs["payload"]
self.assertEqual(sent_payload["fertilization_plan"]["id"], fertilization_plan.id)
self.assertEqual(sent_payload["fertilization_plan"]["uuid"], str(fertilization_plan.uuid))
@patch("yield_harvest.views.external_api_request")
def test_yield_prediction_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={
"data": {
"result": {
"farm_uuid": str(self.farm.farm_uuid),
"predictedYieldTons": 8.4,
"scenarioId": 1,
}
}
},
)
request = self.factory.post(
"/api/yield-harvest/crop-simulation/yield-prediction/",
{"farm_uuid": str(self.farm.farm_uuid)},
format="json",
)
force_authenticate(request, user=self.user)
response = YieldPredictionView.as_view()(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["data"]["predictedYieldTons"], 8.4)
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/yield-prediction/",
method="POST",
payload={"farm_uuid": str(self.farm.farm_uuid), "plant_name": "گوجه‌فرنگی"},
)
@patch("yield_harvest.views.external_api_request")
def test_yield_prediction_top_level_route_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"farm_uuid": str(self.farm.farm_uuid), "predictedYieldTons": 8.4}}},
)
response = self.api_client.post(
"/api/crop-simulation/yield-prediction/",
{"farm_uuid": str(self.farm.farm_uuid)},
format="json",
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["data"]["predictedYieldTons"], 8.4)
@patch("yield_harvest.views.external_api_request")
def test_yield_prediction_yield_harvest_route_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"farm_uuid": str(self.farm.farm_uuid), "predictedYieldTons": 8.4}}},
)
response = self.api_client.post(
"/api/yield-harvest/yield-prediction/",
{"farm_uuid": str(self.farm.farm_uuid)},
format="json",
)
self.assertEqual(response.status_code, 200)
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/yield-prediction/",
method="POST",
payload={"farm_uuid": str(self.farm.farm_uuid), "plant_name": "گوجه‌فرنگی"},
)
@patch("yield_harvest.views.external_api_request")
def test_yield_prediction_falls_back_to_farm_type_product_when_farm_products_are_empty(self, mock_external_api_request):
farm_without_products = FarmHub.objects.create(owner=self.user, farm_type=self.farm_type, name="Farm fallback")
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"farm_uuid": str(farm_without_products.farm_uuid), "predictedYieldTons": 8.4}}},
)
response = self.api_client.post(
"/api/yield-harvest/yield-prediction/",
{"farm_uuid": str(farm_without_products.farm_uuid)},
format="json",
)
self.assertEqual(response.status_code, 200)
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/yield-prediction/",
method="POST",
payload={"farm_uuid": str(farm_without_products.farm_uuid), "plant_name": "گوجه‌فرنگی"},
)
@patch("yield_harvest.views.external_api_request")
def test_yield_prediction_includes_selected_irrigation_and_fertilization_plans(self, mock_external_api_request):
irrigation_plan = IrrigationPlan.objects.create(
farm=self.farm,
source=IrrigationPlan.SOURCE_FREE_TEXT,
title="برنامه آبیاری",
crop_id="گوجه‌فرنگی",
growth_stage="flowering",
plan_payload={"plan": {"durationMinutes": 30}},
request_payload={"source": "manual"},
response_payload={"ok": True},
)
fertilization_plan = FertilizationPlan.objects.create(
farm=self.farm,
source=FertilizationPlan.SOURCE_FREE_TEXT,
title="برنامه کودی",
crop_id="گوجه‌فرنگی",
growth_stage="flowering",
plan_payload={"primary_recommendation": {"fertilizer_code": "npk-202020"}},
request_payload={"source": "manual"},
response_payload={"ok": True},
)
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"farm_uuid": str(self.farm.farm_uuid), "predictedYieldTons": 8.4}}},
)
response = self.api_client.post(
"/api/yield-harvest/yield-prediction/",
{
"farm_uuid": str(self.farm.farm_uuid),
"irrigation_plan_uuid": str(irrigation_plan.uuid),
"fertilization_plan_uuid": str(fertilization_plan.uuid),
},
format="json",
)
self.assertEqual(response.status_code, 200)
sent_payload = mock_external_api_request.call_args.kwargs["payload"]
self.assertEqual(sent_payload["irrigation_plan"]["id"], irrigation_plan.id)
self.assertEqual(sent_payload["irrigation_plan"]["plan_payload"]["plan"]["durationMinutes"], 30)
self.assertEqual(sent_payload["fertilization_plan"]["id"], fertilization_plan.id)
self.assertEqual(
sent_payload["fertilization_plan"]["plan_payload"]["primary_recommendation"]["fertilizer_code"],
"npk-202020",
)
def test_yield_prediction_rejects_foreign_plan_uuids(self):
other_irrigation_plan = IrrigationPlan.objects.create(
farm=self.other_farm,
source=IrrigationPlan.SOURCE_FREE_TEXT,
title="other irrigation",
)
response = self.api_client.post(
"/api/yield-harvest/yield-prediction/",
{
"farm_uuid": str(self.farm.farm_uuid),
"irrigation_plan_uuid": str(other_irrigation_plan.uuid),
},
format="json",
)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.json()["data"]["irrigation_plan_uuid"][0], "Irrigation plan not found.")
@patch("yield_harvest.views.external_api_request")
def test_yield_harvest_summary_top_level_route_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={
"data": {
"result": {
"farm_uuid": str(self.farm.farm_uuid),
"season_highlights_card": {"title": "Season highlights"},
"yield_prediction": {"predicted_yield_tons": 5.1},
"harvest_prediction_card": {"harvest_date": "2026-09-28", "days_until": 152},
"harvest_readiness_zones": {"zones": []},
"yield_quality_bands": {"primary_quality_grade": "B"},
"harvest_operations_card": {"steps": []},
"yield_prediction_chart": {"series": []},
}
}
},
)
response = self.api_client.get(
f"/api/crop-simulation/yield-harvest-summary/?farm_uuid={self.farm.farm_uuid}&season_year=1404&crop_name=wheat&include_narrative=true"
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["data"]["farm_uuid"], str(self.farm.farm_uuid))
mock_external_api_request.assert_called_once_with(
"ai",
"/api/crop-simulation/yield-harvest-summary/",
method="GET",
query={
"farm_uuid": str(self.farm.farm_uuid),
"season_year": "1404",
"crop_name": "wheat",
"include_narrative": "true",
},
)
@patch("yield_harvest.views.external_api_request")
def test_yield_harvest_summary_yield_harvest_route_proxies_to_ai_service(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"farm_uuid": str(self.farm.farm_uuid), "yield_prediction_chart": {"series": []}}}},
)
response = self.api_client.get(f"/api/yield-harvest/yield-harvest-summary/?farm_uuid={self.farm.farm_uuid}")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["data"]["farm_uuid"], str(self.farm.farm_uuid))
@patch("yield_harvest.views.external_api_request")
def test_yield_harvest_summary_includes_selected_plans_in_query(self, mock_external_api_request):
irrigation_plan = IrrigationPlan.objects.create(
farm=self.farm,
source=IrrigationPlan.SOURCE_FREE_TEXT,
title="برنامه آبیاری",
plan_payload={"plan": {"durationMinutes": 18}},
)
fertilization_plan = FertilizationPlan.objects.create(
farm=self.farm,
source=FertilizationPlan.SOURCE_FREE_TEXT,
title="برنامه کودی",
plan_payload={"primary_recommendation": {"fertilizer_code": "npk-111111"}},
)
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={"data": {"result": {"farm_uuid": str(self.farm.farm_uuid), "yield_prediction_chart": {"series": []}}}},
)
response = self.api_client.get(
f"/api/yield-harvest/yield-harvest-summary/?farm_uuid={self.farm.farm_uuid}&irrigation_plan_uuid={irrigation_plan.uuid}&fertilization_plan_uuid={fertilization_plan.uuid}"
)
self.assertEqual(response.status_code, 200)
sent_query = mock_external_api_request.call_args.kwargs["query"]
self.assertEqual(sent_query["irrigation_plan"]["id"], irrigation_plan.id)
self.assertEqual(sent_query["fertilization_plan"]["id"], fertilization_plan.id)
@patch("yield_harvest.views.external_api_request")
def test_yield_harvest_summary_records_empty_result_metric(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(status_code=200, data={"data": {"result": {}}})
response = self.api_client.get(f"/api/yield-harvest/yield-harvest-summary/?farm_uuid={self.farm.farm_uuid}")
self.assertEqual(response.status_code, 200)
self.assertEqual(METRICS["yield_harvest.ai.empty_result|operation=yield_harvest_summary"], 1)
@patch("yield_harvest.views.external_api_request")
def test_yield_harvest_summary_persists_seeded_log_from_realistic_ai_contract(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=200,
data={
"data": {
"result": {
"farm_uuid": str(self.farm.farm_uuid),
"yield_prediction": {"predicted_yield_tons": 5.1, "unit": "tons"},
"harvest_prediction_card": {
"harvest_date": "2026-09-28",
"days_until": 152,
"optimalWindowStart": "2026-09-25",
"optimalWindowEnd": "2026-10-01",
},
"yield_prediction_chart": {"series": [{"name": "yield", "data": []}]},
}
}
},
)
response = self.api_client.get(f"/api/yield-harvest/yield-harvest-summary/?farm_uuid={self.farm.farm_uuid}")
self.assertEqual(response.status_code, 200)
self.assertTrue(self.farm.yield_harvest_prediction_logs.exists())
log = self.farm.yield_harvest_prediction_logs.latest("id")
self.assertEqual(log.yield_stats, "5.1")
self.assertEqual(str(log.harvest_date), "2026-09-28")
@patch("yield_harvest.views.external_api_request")
def test_yield_prediction_provider_unavailable_returns_explicit_failure(self, mock_external_api_request):
mock_external_api_request.return_value = AdapterResponse(
status_code=503,
data={"message": "provider unavailable"},
)
response = self.api_client.post(
"/api/yield-harvest/yield-prediction/",
{"farm_uuid": str(self.farm.farm_uuid)},
format="json",
)
self.assertEqual(response.status_code, 503)
self.assertEqual(response.json()["data"]["message"], "provider unavailable")
def test_crop_simulation_rejects_foreign_farm_uuid(self):
request = self.factory.post(
"/api/yield-harvest/crop-simulation/yield-prediction/",
{"farm_uuid": str(self.other_farm.farm_uuid)},
format="json",
)
force_authenticate(request, user=self.user)
response = YieldPredictionView.as_view()(request)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.data["code"], 404)
self.assertEqual(response.data["data"]["farm_uuid"][0], "Farm not found.")