from __future__ import annotations from django.apps import apps from django.test import SimpleTestCase, TestCase, override_settings from location_data.models import SoilLocation from weather.adapters import MockWeatherAdapter, OpenMeteoWeatherAdapter from weather.models import WeatherForecast from weather.services import fetch_weather_from_api, update_weather_for_location class MockWeatherAdapterTests(SimpleTestCase): def setUp(self): self.adapter = MockWeatherAdapter(delay_seconds=0) def test_same_coordinate_returns_same_forecast(self): first = self.adapter.fetch_forecast(35.71, 51.4) second = self.adapter.fetch_forecast(35.71, 51.4) self.assertEqual(first, second) def test_nearby_coordinates_produce_nearby_forecast(self): first = self.adapter.fetch_forecast(35.71, 51.4) second = self.adapter.fetch_forecast(35.715, 51.405) first_daily = first["daily"] second_daily = second["daily"] self.assertLess( abs(first_daily["temperature_2m_mean"][0] - second_daily["temperature_2m_mean"][0]), 2.5, ) self.assertLess( abs(first_daily["relative_humidity_2m_mean"][0] - second_daily["relative_humidity_2m_mean"][0]), 8.0, ) self.assertLess( abs(first_daily["wind_speed_10m_max"][0] - second_daily["wind_speed_10m_max"][0]), 6.0, ) def test_shape_matches_open_meteo_daily_contract(self): forecast = self.adapter.fetch_forecast(35.71, 51.4) daily = forecast["daily"] self.assertEqual(len(daily["time"]), 7) self.assertEqual(len(daily["temperature_2m_max"]), 7) self.assertEqual(len(daily["weather_code"]), 7) class WeatherAdapterSelectionTests(SimpleTestCase): def tearDown(self): apps.get_app_config("weather").__dict__.pop("weather_data_adapter", None) @override_settings(WEATHER_DATA_PROVIDER="mock", WEATHER_MOCK_DELAY_SECONDS=0) def test_app_config_returns_mock_adapter(self): config = apps.get_app_config("weather") config.__dict__.pop("weather_data_adapter", None) adapter = config.get_weather_data_adapter() self.assertIsInstance(adapter, MockWeatherAdapter) @override_settings(WEATHER_DATA_PROVIDER="open-meteo", WEATHER_TIMEOUT_SECONDS=12) def test_app_config_returns_live_adapter(self): config = apps.get_app_config("weather") config.__dict__.pop("weather_data_adapter", None) adapter = config.get_weather_data_adapter() self.assertIsInstance(adapter, OpenMeteoWeatherAdapter) self.assertEqual(adapter.timeout, 12) @override_settings(WEATHER_DATA_PROVIDER="mock", WEATHER_MOCK_DELAY_SECONDS=0) class WeatherServiceTests(TestCase): def setUp(self): self.location = SoilLocation.objects.create( latitude="35.710000", longitude="51.400000", ) def test_fetch_weather_from_api_uses_mock_provider(self): payload = fetch_weather_from_api(35.71, 51.4) self.assertIn("daily", payload) self.assertEqual(len(payload["daily"]["time"]), 7) def test_update_weather_for_location_persists_seven_days(self): result = update_weather_for_location(self.location) self.assertEqual(result["status"], "success") self.assertEqual(result["days_updated"], 7) self.assertEqual( WeatherForecast.objects.filter(location=self.location).count(), 7, ) self.assertTrue( WeatherForecast.objects.filter( location=self.location, precipitation__isnull=False, weather_code__isnull=False, ).exists() )