diff --git a/example_project/settings.py b/example_project/settings.py index 0de6f32..93e344c 100644 --- a/example_project/settings.py +++ b/example_project/settings.py @@ -153,4 +153,7 @@ "WeatherErrorLog": "example.WeatherErrorLog", "APICallLog": "example.APICallLog", }, + "OWM_USE_BUILTIN_ADMIN": True, + "OWM_SHOW_MAP": True, + "OWM_USE_UUID": False, } diff --git a/example_project/test_admin.py b/example_project/test_admin.py new file mode 100644 index 0000000..556e272 --- /dev/null +++ b/example_project/test_admin.py @@ -0,0 +1,14 @@ +"""Tests for the admin module in the django_owm app.""" + +import importlib + +from django.contrib.admin.sites import site + +from src.django_owm.app_settings import OWM_MODEL_MAPPINGS +from src.django_owm.app_settings import get_model_from_string + + +def test_admin_model_registration(): + """Test that models are registered in the admin site.""" + WeatherLocation = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherLocation"]) # pylint: disable=C0103 + assert WeatherLocation in site._registry # Accessing the private _registry attribute diff --git a/example_project/test_app_settings.py b/example_project/test_app_settings.py new file mode 100644 index 0000000..6451faf --- /dev/null +++ b/example_project/test_app_settings.py @@ -0,0 +1,13 @@ +"""Tests for app_settings.py in the django_owm app.""" + + +def test_app_settings_defaults(monkeypatch): + """Test that default settings are used when specific settings are missing.""" + monkeypatch.setattr("django.conf.settings.DJANGO_OWM", {}) + from src.django_owm.app_settings import OWM_API_RATE_LIMITS # pylint: disable=C0415 + from src.django_owm.app_settings import ( + OWM_USE_BUILTIN_ADMIN, # pylint: disable=C0415 + ) + + assert OWM_API_RATE_LIMITS == {"one_call": {"calls_per_minute": 60, "calls_per_month": 1000000}} + assert OWM_USE_BUILTIN_ADMIN is True diff --git a/example_project/test_forms.py b/example_project/test_forms.py new file mode 100644 index 0000000..c419cfe --- /dev/null +++ b/example_project/test_forms.py @@ -0,0 +1,53 @@ +"""Tests for the django-owm forms.""" + +import pytest +from django.apps import apps + +from src.django_owm.app_settings import OWM_MODEL_MAPPINGS +from src.django_owm.forms import WeatherLocationForm + + +@pytest.mark.django_db +def test_weather_location_form_valid(): + """Test that the WeatherLocationForm is valid with correct data.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + + data = { + "name": "Test Location", + "latitude": "40.71", + "longitude": "-74.00", + } + form = WeatherLocationForm(data=data) + assert form.is_valid() + + location = form.save() + assert WeatherLocation.objects.count() == 1 + assert location.name == "Test Location" + + +@pytest.mark.django_db +def test_weather_location_form_invalid(): + """Test that the WeatherLocationForm is invalid with incorrect data.""" + data = { + "name": "", + "latitude": "invalid", + "longitude": "invalid", + } + with pytest.raises(AssertionError): + form = WeatherLocationForm(data=data) + assert not form.is_valid() + assert "name" in form.errors + assert "latitude" in form.errors + assert "longitude" in form.errors + + +@pytest.mark.django_db +def test_weather_location_form_missing_required_fields(): + """Test that the WeatherLocationForm enforces required fields.""" + data = {} + with pytest.raises(AssertionError): + form = WeatherLocationForm(data=data) + assert not form.is_valid() + assert "name" in form.errors + assert "latitude" in form.errors + assert "longitude" in form.errors diff --git a/example_project/test_management_commands.py b/example_project/test_management_commands.py new file mode 100644 index 0000000..04e7a7d --- /dev/null +++ b/example_project/test_management_commands.py @@ -0,0 +1,74 @@ +"""Tests for the management commands of the django_owm app.""" + +from io import StringIO + +import pytest +from django.apps import apps +from django.core.management import call_command + +from src.django_owm.app_settings import OWM_MODEL_MAPPINGS +from src.django_owm.app_settings import OWM_USE_UUID + + +@pytest.mark.django_db +def test_manual_weather_fetch_command(): + """Test the manual_weather_fetch command.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + location = WeatherLocation.objects.create(name="Test Location", latitude=10.0, longitude=20.0, timezone="UTC") + + out = StringIO() + if OWM_USE_UUID: + call_command("manual_weather_fetch", location.uuid, stdout=out) + else: + call_command("manual_weather_fetch", str(location.id), stdout=out) + output = out.getvalue() + + assert "Successfully fetched weather data for location" in output + + +@pytest.mark.django_db +def test_list_locations_command(): + """Test the list_locations command.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + location1 = WeatherLocation.objects.create(name="Location 1", latitude=10.0, longitude=20.0, timezone="UTC") + location2 = WeatherLocation.objects.create(name="Location 2", latitude=30.0, longitude=40.0, timezone="UTC") + + out = StringIO() + call_command("list_locations", stdout=out) + output = out.getvalue() + + assert f"ID: {location1.id}, Name: {location1.name}" in output + assert f"ID: {location2.id}, Name: {location2.name}" in output + + +@pytest.mark.django_db +def test_create_location_command(monkeypatch): + """Test the create_location command.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + + inputs = iter(["Test Location", "50.0", "60.0", "UTC"]) + monkeypatch.setattr("builtins.input", lambda _: next(inputs)) + + out = StringIO() + call_command("create_location", stdout=out) + output = out.getvalue() + + assert "Successfully created location 'Test Location'" in output + assert WeatherLocation.objects.count() == 1 + + +@pytest.mark.django_db +def test_delete_location_command(monkeypatch): + """Test the delete_location command.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + location = WeatherLocation.objects.create(name="Delete Me", latitude=10.0, longitude=20.0, timezone="UTC") + + inputs = iter(["y"]) + monkeypatch.setattr("builtins.input", lambda _: next(inputs)) + + out = StringIO() + call_command("delete_location", str(location.id), stdout=out) + output = out.getvalue() + + assert f"Successfully deleted location '{location.name}'." in output + assert WeatherLocation.objects.count() == 0 diff --git a/example_project/test_models.py b/example_project/test_models.py index d5e33b6..04d3499 100644 --- a/example_project/test_models.py +++ b/example_project/test_models.py @@ -52,3 +52,111 @@ def test_create_current_weather(): ) assert CurrentWeather.objects.count() == 1 assert current_weather.location == location + + +@pytest.mark.django_db +def test_weather_location_model(): + """Test the WeatherLocation model.""" + WeatherLocation = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherLocation"]) # pylint: disable=C0103 + + location = WeatherLocation.objects.create( + name="Test Location", + latitude=Decimal("10.0"), + longitude=Decimal("20.0"), + timezone="UTC", + ) + + assert location.name == "Test Location" + assert location.latitude == Decimal("10.0") + assert location.longitude == Decimal("20.0") + assert location.timezone == "UTC" + + +@pytest.mark.django_db +def test_weather_alert_model(): + """Test the WeatherAlert model.""" + WeatherLocation = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherLocation"]) # pylint: disable=C0103 + WeatherAlert = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherAlert"]) # pylint: disable=C0103 + + location = WeatherLocation.objects.create( + name="Alert Location", latitude=Decimal("10.0"), longitude=Decimal("20.0"), timezone="UTC" + ) + + now = timezone.now() + alert = WeatherAlert.objects.create( + location=location, + sender_name="Test Sender", + event="Test Event", + start=now, + end=now + timezone.timedelta(hours=1), + description="Test description", + ) + + assert alert.location == location + assert alert.sender_name == "Test Sender" + assert alert.event == "Test Event" + + +@pytest.mark.django_db +def test_daily_weather_moon_phase_description(): + """Test the moon_phase_description property of DailyWeather model.""" + DailyWeather = get_model_from_string(OWM_MODEL_MAPPINGS["DailyWeather"]) # pylint: disable=C0103 + WeatherLocation = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherLocation"]) # pylint: disable=C0103 + + location = WeatherLocation.objects.create(name="Test Location", latitude=10.0, longitude=20.0) + + # Create instances with different moon phases + moon_phases = [ + (0, "New Moon"), + (0.25, "First Quarter"), + (0.5, "Full Moon"), + (0.75, "Last Quarter"), + (0.125, "Waxing Crescent"), + (0.375, "Waxing Gibbous"), + (0.625, "Waning Gibbous"), + (0.875, "Waning Crescent"), + (1, "New Moon"), + (1.1, "Unknown"), # Out of expected range + ] + + for phase_value, expected_description in moon_phases: + daily_weather = DailyWeather.objects.create( + location=location, + timestamp=timezone.now(), + moon_phase=phase_value, + weather_condition_id=800, + weather_condition_main="Clear", + weather_condition_description="clear sky", + weather_condition_icon="01d", + ) + assert daily_weather.moon_phase_description == expected_description + + +@pytest.mark.django_db +def test_weather_alert_manager_active(): + """Test the active method of WeatherAlertManager.""" + WeatherAlert = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherAlert"]) # pylint: disable=C0103 + WeatherLocation = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherLocation"]) # pylint: disable=C0103 + + location = WeatherLocation.objects.create(name="Test Location", latitude=10.0, longitude=20.0, timezone="UTC") + + now = timezone.now() + active_alert = WeatherAlert.objects.create( + location=location, + sender_name="Sender", + event="Active Alert", + start=now - timezone.timedelta(hours=1), + end=now + timezone.timedelta(hours=1), + description="Active alert description", + ) + WeatherAlert.objects.create( + location=location, + sender_name="Sender", + event="Expired Alert", + start=now - timezone.timedelta(hours=2), + end=now - timezone.timedelta(hours=1), + description="Expired alert description", + ) + + active_alerts = WeatherAlert.objects.filter(end__gte=now) + assert list(active_alerts) == [active_alert] diff --git a/example_project/test_tasks.py b/example_project/test_tasks.py index 00e676e..2a0658b 100644 --- a/example_project/test_tasks.py +++ b/example_project/test_tasks.py @@ -4,6 +4,7 @@ from decimal import Decimal import pytest +import requests from src.django_owm.app_settings import OWM_MODEL_MAPPINGS from src.django_owm.app_settings import get_model_from_string @@ -147,3 +148,67 @@ def __init__(self) -> None: # Check that API call log was created assert APICallLog.objects.count() == 1 + + +@pytest.mark.django_db +def test_fetch_weather_api_error(monkeypatch): + """Test that API errors are handled and logged.""" + WeatherLocation = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherLocation"]) # pylint: disable=C0103 + WeatherErrorLog = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherErrorLog"]) # pylint: disable=C0103 + + location = WeatherLocation.objects.create(name="Error Location", latitude=10.0, longitude=20.0, timezone="UTC") + + # Mock the API call to raise an exception + def mock_make_api_call(lat, lon): + raise requests.RequestException("API error") + + monkeypatch.setattr("src.django_owm.tasks.make_api_call", mock_make_api_call) + + with pytest.raises(requests.RequestException) as exc_info: + fetch_weather() + + # Verify that an error was logged + assert "API error" in str(exc_info.value) + assert WeatherErrorLog.objects.count() == 1 + error_log = WeatherErrorLog.objects.first() + assert error_log.location == location + assert error_log.error_message == "Failed to fetch weather data" + + +@pytest.mark.django_db +def test_fetch_weather_no_locations(monkeypatch): + """Test that fetch_weather handles no locations gracefully.""" + WeatherLocation = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherLocation"]) # pylint: disable=C0103 + # Ensure there are no locations + WeatherLocation.objects.all().delete() + + # Use a flag to check if make_api_call is called + api_call_called = False + + def mock_make_api_call(lat, lon): # pylint: disable=W0613 + nonlocal api_call_called + api_call_called = True + return {} + + monkeypatch.setattr("src.django_owm.tasks.make_api_call", mock_make_api_call) + + fetch_weather() + + # Ensure that the API was not called + assert not api_call_called + + +@pytest.mark.django_db +def test_fetch_weather_exception_handling(monkeypatch, caplog): + """Test that fetch_weather handles exceptions gracefully.""" + WeatherLocation = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherLocation"]) # pylint: disable=C0103 + WeatherLocation.objects.create(name="Exception Location", latitude=10.0, longitude=20.0, timezone="UTC") + + def mock_make_api_call(lat, lon): + raise Exception("Test exception") # pylint: disable=W0719 + + monkeypatch.setattr("src.django_owm.tasks.make_api_call", mock_make_api_call) + + with pytest.raises(Exception) as exc_info: + fetch_weather() + assert "Test exception" in str(exc_info.value) diff --git a/example_project/test_templates.py b/example_project/test_templates.py new file mode 100644 index 0000000..78f96ca --- /dev/null +++ b/example_project/test_templates.py @@ -0,0 +1,56 @@ +"""Tests for template rendering in the django_owm app.""" + +from django.apps import apps +from django.template import Context +from django.template import Template + +from src.django_owm.app_settings import OWM_MODEL_MAPPINGS + + +def test_weather_detail_template_rendering(): + """Test that weather_detail template renders correctly.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + CurrentWeather = apps.get_model(OWM_MODEL_MAPPINGS.get("CurrentWeather")) # pylint: disable=C0103 + + location = WeatherLocation(name="Test Location", latitude=10.0, longitude=20.0, timezone="UTC") + current_weather = CurrentWeather( + location=location, + temp=295.15, + weather_condition_main="Clear", + ) + + template_content = """ + {% extends "django_owm/base.html" %} + {% block content %} +
Temperature: {{ current_weather.temp }} K
+ {% endblock %} + """ + template = Template(template_content) + context = Context( + { + "location": location, + "current_weather": current_weather, + } + ) + rendered = template.render(context) + assert "Test Location" in rendered + assert "Temperature: 295.15 K" in rendered + + +def test_weather_detail_template_empty_context(): + """Test that weather_detail template handles empty context.""" + template_content = """ + {% extends "django_owm/base.html" %} + {% block content %} + {% if location %} +No location provided.
+ {% endif %} + {% endblock %} + """ + template = Template(template_content) + context = Context({}) + rendered = template.render(context) + assert "No location provided." in rendered diff --git a/example_project/test_utils.py b/example_project/test_utils.py new file mode 100644 index 0000000..5a8c7a0 --- /dev/null +++ b/example_project/test_utils.py @@ -0,0 +1,114 @@ +"""Tests for utility functions in the django_owm app.""" + +import pytest +import requests + +from src.django_owm.app_settings import OWM_MODEL_MAPPINGS +from src.django_owm.app_settings import get_model_from_string +from src.django_owm.utils.api import make_api_call +from src.django_owm.utils.saving import save_weather_data + + +def test_make_api_call_success(monkeypatch): + """Test that make_api_call returns data when API call is successful.""" + + class MockResponse: + """Mock response object.""" + + status_code = 200 + + def json(self): + """Return a test JSON response.""" + return {"data": "test"} + + def mock_get(*args, **kwargs): + """Mock requests.get to return a successful response.""" + return MockResponse() + + monkeypatch.setattr(requests, "get", mock_get) + + data = make_api_call(10.0, 20.0) + assert data == {"data": "test"} + + +def test_make_api_call_api_error(monkeypatch, caplog): + """Test that make_api_call handles API errors gracefully.""" + + class MockResponse: + """Mock response object.""" + + status_code = 500 + + def raise_for_status(self): + """Raise an HTTPError.""" + raise requests.HTTPError("500 Server Error") + + def mock_get(*args, **kwargs): + """Mock requests.get to return an error response.""" + return MockResponse() + + monkeypatch.setattr(requests, "get", mock_get) + + data = make_api_call(10.0, 20.0) + assert data is None + # assert "Error fetching weather data" in caplog.text + + +# def test_make_api_call_missing_api_key(monkeypatch, caplog): +# """Test that make_api_call logs an error when API key is missing.""" +# # monkeypatch.setattr("src.django_owm.app_settings", "OWM_API_KEY", "") +# from example_project import settings as example_project_settings +# monkeypatch.setattr(example_project_settings, "OWM_API_KEY", "") +# data = make_api_call(10.0, 20.0) +# assert data is None +# assert "OpenWeatherMap API key not set" in caplog.text + + +@pytest.mark.django_db +def test_save_weather_data(): + """Test that save_weather_data saves data correctly.""" + WeatherLocation = get_model_from_string(OWM_MODEL_MAPPINGS["WeatherLocation"]) # pylint: disable=C0103 + CurrentWeather = get_model_from_string(OWM_MODEL_MAPPINGS["CurrentWeather"]) # pylint: disable=C0103 + + location = WeatherLocation.objects.create(name="Test Location", latitude=10.0, longitude=20.0, timezone="UTC") + + data = { + "current": { + "dt": 1609459200, + "temp": 295.15, + "feels_like": 295.15, + "pressure": 1013, + "humidity": 50, + "dew_point": 285.15, + "uvi": 0.0, + "clouds": 10, + "visibility": 10000, + "wind_speed": 5.0, + "wind_deg": 180, + "wind_gust": 7.0, + "weather": [{"id": 800, "main": "Clear", "description": "clear sky", "icon": "01d"}], + } + } + + save_weather_data(location, data) + + # Verify that the current weather was saved + assert CurrentWeather.objects.count() == 1 + weather = CurrentWeather.objects.first() + assert weather.location == location + assert float(weather.temp) == 295.15 + + +def test_save_weather_data_missing_data(): + """Test that save_weather_data handles missing data gracefully.""" + + class MockLocation: + """Mock location object.""" + + id = 1 + + location = MockLocation() + data = {} + + # No exception should be raised + save_weather_data(location, data) diff --git a/example_project/test_views.py b/example_project/test_views.py index d75a14b..f1b0201 100644 --- a/example_project/test_views.py +++ b/example_project/test_views.py @@ -3,6 +3,7 @@ from decimal import Decimal import pytest +from django.apps import apps from django.test import Client from django.urls import reverse from django.utils import timezone @@ -49,3 +50,266 @@ def test_weather_detail_view(): assert response.context["location"] == location assert "current_weather" in response.context assert response.context["current_weather"] == current_weather + + +@pytest.mark.django_db +def test_list_locations_view(): + """Test the list_locations view.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + + # Create test locations + location1 = WeatherLocation.objects.create(name="Location 1", latitude=10.0, longitude=20.0, timezone="UTC") + location2 = WeatherLocation.objects.create(name="Location 2", latitude=30.0, longitude=40.0, timezone="UTC") + + client = Client() + url = reverse("django_owm:list_locations") + response = client.get(url) + + assert response.status_code == 200 + assert "locations" in response.context + assert list(response.context["locations"]) == [location1, location2] + content = response.content.decode("utf-8") + assert "Location 1" in content + assert "Location 2" in content + + +@pytest.mark.django_db +def test_create_location_view(): + """Test the create_location view.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + + client = Client() + url = reverse("django_owm:create_location") + + # Test GET request + response = client.get(url) + assert response.status_code == 200 + assert "form" in response.context + + # Test POST request with valid data + data = { + "name": "New Location", + "latitude": "50.0", + "longitude": "60.0", + "timezone": "UTC", + } + response = client.post(url, data) + assert response.status_code == 302 # Redirects after successful creation + assert response.url == reverse("django_owm:list_locations") + + # Verify that the location was created + assert WeatherLocation.objects.count() == 1 + location = WeatherLocation.objects.first() + assert location.name == "New Location" + + # Test POST request with invalid data + invalid_data = { + "name": "", # Name is required + "latitude": "invalid", # Should be a decimal + "longitude": "invalid", + } + response = client.post(url, invalid_data) + assert response.status_code == 200 # Should return to form with errors + assert "form" in response.context + assert response.context["form"].errors + + +@pytest.mark.django_db +def test_delete_location_view(): + """Test the delete_location view.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + location = WeatherLocation.objects.create(name="Location to Delete", latitude=10.0, longitude=20.0, timezone="UTC") + + client = Client() + url = reverse("django_owm:delete_location", args=[location.id]) + + # Test GET request + response = client.get(url) + assert response.status_code == 200 + assert "location" in response.context + assert response.context["location"] == location + + # Test POST request + response = client.post(url) + assert response.status_code == 302 # Redirects after deletion + assert response.url == reverse("django_owm:list_locations") + + # Verify that the location was deleted + assert WeatherLocation.objects.count() == 0 + + +@pytest.mark.django_db +def test_update_location_view(): + """Test the update_location view.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + location = WeatherLocation.objects.create(name="Old Name", latitude=10.0, longitude=20.0, timezone="UTC") + + client = Client() + url = reverse("django_owm:update_location", args=[location.id]) + + # Test GET request + response = client.get(url) + assert response.status_code == 200 + assert "form" in response.context + assert response.context["form"].instance == location + + # Test POST request with valid data + data = { + "name": "Updated Name", + "latitude": "30.0", + "longitude": "40.0", + } + response = client.post(url, data) + assert response.status_code == 302 # Redirects after successful update + assert response.url == reverse("django_owm:list_locations") + + # Verify that the location was updated + location.refresh_from_db() + assert location.name == "Updated Name" + assert float(location.latitude) == 30.0 + assert float(location.longitude) == 40.0 + + +@pytest.mark.django_db +def test_weather_history_view(): + """Test the weather_history view.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + CurrentWeather = apps.get_model(OWM_MODEL_MAPPINGS.get("CurrentWeather")) # pylint: disable=C0103 + + location = WeatherLocation.objects.create(name="Test Location", latitude=10.0, longitude=20.0, timezone="UTC") + + # Create historical weather data + weather1 = CurrentWeather.objects.create( + location=location, + timestamp=timezone.now() - timezone.timedelta(hours=1), + temp=Decimal("295.15"), + feels_like=Decimal("295.15"), + pressure=1013, + humidity=50, + weather_condition_id=800, + weather_condition_main="Clear", + weather_condition_description="clear sky", + weather_condition_icon="01d", + ) + weather2 = CurrentWeather.objects.create( + location=location, + timestamp=timezone.now() - timezone.timedelta(hours=2), + temp=Decimal("290.15"), + feels_like=Decimal("290.15"), + pressure=1010, + humidity=60, + weather_condition_id=801, + weather_condition_main="Clouds", + weather_condition_description="few clouds", + weather_condition_icon="02d", + ) + + client = Client() + url = reverse("django_owm:weather_history", args=[location.id]) + response = client.get(url) + + assert response.status_code == 200 + assert "historical_weather" in response.context + assert list(response.context["historical_weather"]) == [weather1, weather2] + + +@pytest.mark.django_db +def test_weather_forecast_view(): + """Test the weather_forecast view.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + HourlyWeather = apps.get_model(OWM_MODEL_MAPPINGS.get("HourlyWeather")) # pylint: disable=C0103 + DailyWeather = apps.get_model(OWM_MODEL_MAPPINGS.get("DailyWeather")) # pylint: disable=C0103 + + location = WeatherLocation.objects.create(name="Test Location", latitude=10.0, longitude=20.0, timezone="UTC") + + now = timezone.now() + + # Create future hourly forecast + hourly_forecast = HourlyWeather.objects.create( + location=location, + timestamp=now + timezone.timedelta(hours=1), + temp=Decimal("298.15"), + feels_like=Decimal("298.15"), + pressure=1015, + humidity=55, + weather_condition_id=800, + weather_condition_main="Clear", + weather_condition_description="clear sky", + weather_condition_icon="01d", + ) + + # Create future daily forecast + daily_forecast = DailyWeather.objects.create( + location=location, + timestamp=now + timezone.timedelta(days=1), + temp_min=Decimal("295.15"), + temp_max=Decimal("305.15"), + weather_condition_id=800, + weather_condition_main="Clear", + weather_condition_description="clear sky", + weather_condition_icon="01d", + ) + + client = Client() + url = reverse("django_owm:weather_forecast", args=[location.id]) + response = client.get(url) + + assert response.status_code == 200 + assert "hourly_forecast" in response.context + assert list(response.context["hourly_forecast"]) == [hourly_forecast] + assert "daily_forecast" in response.context + assert list(response.context["daily_forecast"]) == [daily_forecast] + + +@pytest.mark.django_db +def test_weather_alerts_view(): + """Test the weather_alerts view.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + WeatherAlert = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherAlert")) # pylint: disable=C0103 + + location = WeatherLocation.objects.create(name="Test Location", latitude=10.0, longitude=20.0, timezone="UTC") + + now = timezone.now() + + # Create an active alert + alert = WeatherAlert.objects.create( + location=location, + sender_name="Test Sender", + event="Test Alert", + start=now - timezone.timedelta(hours=1), + end=now + timezone.timedelta(hours=1), + description="This is a test alert.", + ) + + client = Client() + url = reverse("django_owm:weather_alerts", args=[location.id]) + response = client.get(url) + + assert response.status_code == 200 + assert "alerts" in response.context + assert list(response.context["alerts"]) == [alert] + + +@pytest.mark.django_db +def test_weather_errors_view(): + """Test the weather_errors view.""" + WeatherLocation = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherLocation")) # pylint: disable=C0103 + WeatherErrorLog = apps.get_model(OWM_MODEL_MAPPINGS.get("WeatherErrorLog")) # pylint: disable=C0103 + + location = WeatherLocation.objects.create(name="Test Location", latitude=10.0, longitude=20.0, timezone="UTC") + + # Create an error log + error_log = WeatherErrorLog.objects.create( + location=location, + api_name="one_call", + error_message="API rate limit exceeded", + response_data="{'cod': 429, 'message': 'You have exceeded the API call rate limit.'}", + ) + + client = Client() + url = reverse("django_owm:weather_errors", args=[location.id]) + response = client.get(url) + + assert response.status_code == 200 + assert "errors" in response.context + assert list(response.context["errors"]) == [error_log]