146 lines
5.1 KiB
Python
146 lines
5.1 KiB
Python
|
|
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 plant.models import Plant
|
||
|
|
|
||
|
|
from .growth_simulation import paginate_growth_stages, run_growth_simulation
|
||
|
|
|
||
|
|
|
||
|
|
@override_settings(ROOT_URLCONF="crop_simulation.urls")
|
||
|
|
class PlantGrowthSimulationApiTests(TestCase):
|
||
|
|
def setUp(self):
|
||
|
|
self.client = APIClient()
|
||
|
|
self.plant = Plant.objects.create(
|
||
|
|
name="گوجهفرنگی",
|
||
|
|
growth_profile={
|
||
|
|
"base_temperature": 10,
|
||
|
|
"required_gdd_for_maturity": 1200,
|
||
|
|
"current_cumulative_gdd": 50,
|
||
|
|
},
|
||
|
|
)
|
||
|
|
self.weather = [
|
||
|
|
{
|
||
|
|
"DAY": "2026-04-01",
|
||
|
|
"LAT": 35.7,
|
||
|
|
"LON": 51.4,
|
||
|
|
"TMIN": 12,
|
||
|
|
"TMAX": 24,
|
||
|
|
"RAIN": 0.0,
|
||
|
|
"ET0": 0.32,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"DAY": "2026-04-02",
|
||
|
|
"LAT": 35.7,
|
||
|
|
"LON": 51.4,
|
||
|
|
"TMIN": 13,
|
||
|
|
"TMAX": 25,
|
||
|
|
"RAIN": 0.0,
|
||
|
|
"ET0": 0.34,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"DAY": "2026-04-03",
|
||
|
|
"LAT": 35.7,
|
||
|
|
"LON": 51.4,
|
||
|
|
"TMIN": 14,
|
||
|
|
"TMAX": 27,
|
||
|
|
"RAIN": 1.0,
|
||
|
|
"ET0": 0.36,
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
def test_run_growth_simulation_returns_stage_timeline(self):
|
||
|
|
result = run_growth_simulation(
|
||
|
|
{
|
||
|
|
"plant_name": self.plant.name,
|
||
|
|
"dynamic_parameters": ["DVS", "LAI", "TAGP"],
|
||
|
|
"weather": self.weather,
|
||
|
|
"soil_parameters": {"SMFCF": 0.34, "SMW": 0.14, "RDMSOL": 120.0},
|
||
|
|
"site_parameters": {"WAV": 40.0},
|
||
|
|
"page_size": 2,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
self.assertEqual(result["plant_name"], self.plant.name)
|
||
|
|
self.assertGreaterEqual(result["daily_records_count"], 3)
|
||
|
|
self.assertTrue(result["stage_timeline"])
|
||
|
|
self.assertEqual(result["pagination"]["page_size"], 2)
|
||
|
|
|
||
|
|
@patch("crop_simulation.views.run_growth_simulation_task.delay")
|
||
|
|
def test_queue_api_returns_task_id(self, mock_delay):
|
||
|
|
mock_delay.return_value = SimpleNamespace(id="growth-task-1")
|
||
|
|
|
||
|
|
response = self.client.post(
|
||
|
|
"/growth/",
|
||
|
|
data={
|
||
|
|
"plant_name": self.plant.name,
|
||
|
|
"dynamic_parameters": ["DVS", "LAI"],
|
||
|
|
"weather": self.weather,
|
||
|
|
},
|
||
|
|
format="json",
|
||
|
|
)
|
||
|
|
|
||
|
|
self.assertEqual(response.status_code, 202)
|
||
|
|
self.assertEqual(response.json()["data"]["task_id"], "growth-task-1")
|
||
|
|
|
||
|
|
@patch("crop_simulation.views._get_async_result")
|
||
|
|
def test_status_api_returns_paginated_stages(self, mock_get_async_result):
|
||
|
|
stage_timeline = [
|
||
|
|
{
|
||
|
|
"order": 1,
|
||
|
|
"stage_code": "establishment",
|
||
|
|
"stage_name": "استقرار",
|
||
|
|
"start_date": "2026-04-01",
|
||
|
|
"end_date": "2026-04-02",
|
||
|
|
"days_count": 2,
|
||
|
|
"metrics": {"DVS": {"start": 0.1, "end": 0.2, "min": 0.1, "max": 0.2, "avg": 0.15}},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"order": 2,
|
||
|
|
"stage_code": "vegetative",
|
||
|
|
"stage_name": "رشد رویشی",
|
||
|
|
"start_date": "2026-04-03",
|
||
|
|
"end_date": "2026-04-05",
|
||
|
|
"days_count": 3,
|
||
|
|
"metrics": {"DVS": {"start": 0.3, "end": 0.8, "min": 0.3, "max": 0.8, "avg": 0.55}},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"order": 3,
|
||
|
|
"stage_code": "flowering",
|
||
|
|
"stage_name": "گلدهی",
|
||
|
|
"start_date": "2026-04-06",
|
||
|
|
"end_date": "2026-04-07",
|
||
|
|
"days_count": 2,
|
||
|
|
"metrics": {"DVS": {"start": 1.0, "end": 1.2, "min": 1.0, "max": 1.2, "avg": 1.1}},
|
||
|
|
},
|
||
|
|
]
|
||
|
|
mock_get_async_result.return_value = SimpleNamespace(
|
||
|
|
state="SUCCESS",
|
||
|
|
result={
|
||
|
|
"plant_name": self.plant.name,
|
||
|
|
"dynamic_parameters": ["DVS"],
|
||
|
|
"engine": "growth_projection",
|
||
|
|
"model_name": "growth_projection_v1",
|
||
|
|
"scenario_id": None,
|
||
|
|
"simulation_warning": None,
|
||
|
|
"summary_metrics": {},
|
||
|
|
"stage_timeline": stage_timeline,
|
||
|
|
"stages_page": stage_timeline[:1],
|
||
|
|
"pagination": paginate_growth_stages(stage_timeline, page=1, page_size=1)["pagination"],
|
||
|
|
"daily_records_count": 7,
|
||
|
|
"default_page_size": 1,
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
response = self.client.get("/growth/growth-task-1/status/?page=2&page_size=1")
|
||
|
|
|
||
|
|
self.assertEqual(response.status_code, 200)
|
||
|
|
payload = response.json()["data"]["result"]
|
||
|
|
self.assertEqual(payload["pagination"]["page"], 2)
|
||
|
|
self.assertEqual(len(payload["stages_page"]), 1)
|
||
|
|
self.assertEqual(payload["stages_page"][0]["stage_code"], "vegetative")
|