This commit is contained in:
2026-05-09 16:55:06 +03:30
parent 1679825ae2
commit cead7dafe2
51 changed files with 7514 additions and 1221 deletions
+59 -91
View File
@@ -5,7 +5,7 @@ import uuid
from django.test import TestCase
from rest_framework.test import APIClient
from location_data.models import SoilDepthData, SoilLocation
from location_data.models import BlockSubdivision, SoilLocation
from farm_data.models import PlantCatalogSnapshot, SensorData, SensorParameter
from farm_data.services import (
assign_farm_plants_from_backend_ids,
@@ -42,19 +42,6 @@ class FarmDetailApiTests(TestCase):
longitude="51.400000",
farm_boundary={"type": "Polygon", "coordinates": []},
)
SoilDepthData.objects.create(
soil_location=self.location,
depth_label="0-5cm",
clay=22.0,
nitrogen=10.0,
sand=40.0,
)
SoilDepthData.objects.create(
soil_location=self.location,
depth_label="5-15cm",
clay=18.0,
nitrogen=8.0,
)
self.weather = WeatherForecast.objects.create(
location=self.location,
forecast_date=date(2026, 4, 10),
@@ -123,9 +110,7 @@ class FarmDetailApiTests(TestCase):
self.assertEqual(resolved_metrics["nitrogen"], 99.0)
self.assertEqual(metric_sources["nitrogen"]["type"], "sensor")
self.assertEqual(metric_sources["nitrogen"]["strategy"], "single_value")
self.assertEqual(resolved_metrics["clay"], 22.0)
self.assertEqual(metric_sources["clay"], "soil")
self.assertEqual(len(payload["soil"]["depths"]), 2)
self.assertEqual(payload["soil"]["satellite_snapshots"], [])
self.assertCountEqual(payload["plant_ids"], [self.plant1.backend_plant_id, self.plant2.backend_plant_id])
self.assertEqual(len(payload["plants"]), 2)
returned_plants = {item["id"]: item for item in payload["plants"]}
@@ -204,21 +189,6 @@ class FarmDataUpsertApiTests(TestCase):
latitude="35.710000",
longitude="51.410000",
)
SoilDepthData.objects.create(
soil_location=self.location,
depth_label="0-5cm",
clay=20.0,
)
SoilDepthData.objects.create(
soil_location=self.location,
depth_label="5-15cm",
clay=18.0,
)
SoilDepthData.objects.create(
soil_location=self.location,
depth_label="15-30cm",
clay=16.0,
)
self.boundary = square_boundary_for_center(35.71, 51.41)
self.weather = WeatherForecast.objects.create(
location=self.location,
@@ -312,16 +282,7 @@ class FarmDataUpsertApiTests(TestCase):
self.assertEqual(response.status_code, 400)
self.assertIn("farm_uuid", response.json()["data"])
@patch("farm_data.services.update_weather_for_location", return_value={"status": "no_data"})
@patch(
"farm_data.services.fetch_soil_data_for_coordinates",
return_value={"status": "completed", "depths": []},
)
def test_post_creates_center_location_from_boundary_when_missing(
self,
_mock_fetch_soil_data_for_coordinates,
_mock_update_weather_for_location,
):
def test_post_creates_center_location_from_boundary_when_missing(self):
farm_uuid = uuid.uuid4()
response = self.client.post(
@@ -347,6 +308,60 @@ class FarmDataUpsertApiTests(TestCase):
self.assertEqual(str(farm.center_location.latitude), "50.010000")
self.assertEqual(str(farm.center_location.longitude), "50.010000")
self.assertIsNone(farm.weather_forecast_id)
self.assertEqual(farm.center_location.input_block_count, 1)
self.assertEqual(len(farm.center_location.block_layout["blocks"]), 1)
subdivision = BlockSubdivision.objects.get(soil_location=farm.center_location, block_code="block-1")
self.assertGreater(subdivision.grid_point_count, 0)
self.assertEqual(subdivision.grid_point_count, subdivision.centroid_count)
def test_post_persists_requested_block_count_on_center_location(self):
farm_uuid = uuid.uuid4()
response = self.client.post(
"/api/farm-data/",
data={
"farm_uuid": str(farm_uuid),
"farm_boundary": self.boundary,
"block_count": 3,
"sensor_payload": {"sensor-7-1": {"soil_moisture": 40.0}},
},
format="json",
)
self.assertEqual(response.status_code, 201)
farm = SensorData.objects.get(farm_uuid=farm_uuid)
self.assertEqual(farm.center_location.input_block_count, 3)
self.assertEqual(len(farm.center_location.block_layout["blocks"]), 3)
self.assertFalse(
BlockSubdivision.objects.filter(soil_location=farm.center_location).exists()
)
def test_resolve_center_location_runs_subdivision_only_on_creation(self):
boundary = square_boundary_for_center(35.75, 51.45)
first_location = resolve_center_location_from_boundary(boundary, block_count=1)
first_subdivision = BlockSubdivision.objects.get(
soil_location=first_location,
block_code="block-1",
)
second_location = resolve_center_location_from_boundary(boundary, block_count=1)
self.assertEqual(first_location.id, second_location.id)
self.assertEqual(
BlockSubdivision.objects.filter(
soil_location=second_location,
block_code="block-1",
).count(),
1,
)
self.assertEqual(
BlockSubdivision.objects.get(
soil_location=second_location,
block_code="block-1",
).id,
first_subdivision.id,
)
def test_resolve_center_location_uses_geometric_centroid_for_concave_polygon(self):
location = resolve_center_location_from_boundary(
@@ -368,53 +383,10 @@ class FarmDataUpsertApiTests(TestCase):
self.assertEqual(str(location.latitude), "2.078947")
self.assertEqual(str(location.longitude), "2.078947")
@patch("farm_data.services.update_weather_for_location")
@patch("farm_data.services.fetch_soil_data_for_coordinates")
def test_post_fetches_missing_location_and_weather_data(
self,
mock_fetch_soil_data_for_coordinates,
mock_update_weather_for_location,
):
def test_post_keeps_missing_location_without_external_sync(self):
missing_boundary = square_boundary_for_center(36.0, 52.0)
farm_uuid = uuid.uuid4()
def soil_side_effect(latitude, longitude, task_id="", progress_callback=None):
location = SoilLocation.objects.get(
latitude="36.000000",
longitude="52.000000",
)
SoilDepthData.objects.update_or_create(
soil_location=location,
depth_label="0-5cm",
defaults={"clay": 20.0},
)
SoilDepthData.objects.update_or_create(
soil_location=location,
depth_label="5-15cm",
defaults={"clay": 18.0},
)
SoilDepthData.objects.update_or_create(
soil_location=location,
depth_label="15-30cm",
defaults={"clay": 16.0},
)
return {"status": "completed", "location_id": location.id, "depths": ["0-5cm", "5-15cm", "15-30cm"]}
def weather_side_effect(location):
WeatherForecast.objects.update_or_create(
location=location,
forecast_date=date(2026, 4, 12),
defaults={
"temperature_min": 10.0,
"temperature_max": 20.0,
"temperature_mean": 15.0,
},
)
return {"status": "success", "location_id": location.id, "days_updated": 1}
mock_fetch_soil_data_for_coordinates.side_effect = soil_side_effect
mock_update_weather_for_location.side_effect = weather_side_effect
response = self.client.post(
"/api/farm-data/",
data={
@@ -426,9 +398,5 @@ class FarmDataUpsertApiTests(TestCase):
)
self.assertEqual(response.status_code, 201)
mock_fetch_soil_data_for_coordinates.assert_called_once()
mock_update_weather_for_location.assert_called_once()
farm = SensorData.objects.get(farm_uuid=farm_uuid)
self.assertEqual(farm.center_location.depths.count(), 3)
self.assertIsNotNone(farm.weather_forecast_id)
self.assertIsNone(farm.weather_forecast_id)