Skip to content

Commit

Permalink
Implement several more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jacklinke committed Oct 11, 2024
1 parent 62e4946 commit 69f360e
Show file tree
Hide file tree
Showing 10 changed files with 764 additions and 0 deletions.
3 changes: 3 additions & 0 deletions example_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,7 @@
"WeatherErrorLog": "example.WeatherErrorLog",
"APICallLog": "example.APICallLog",
},
"OWM_USE_BUILTIN_ADMIN": True,
"OWM_SHOW_MAP": True,
"OWM_USE_UUID": False,
}
14 changes: 14 additions & 0 deletions example_project/test_admin.py
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions example_project/test_app_settings.py
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions example_project/test_forms.py
Original file line number Diff line number Diff line change
@@ -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
74 changes: 74 additions & 0 deletions example_project/test_management_commands.py
Original file line number Diff line number Diff line change
@@ -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
108 changes: 108 additions & 0 deletions example_project/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
65 changes: 65 additions & 0 deletions example_project/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Loading

0 comments on commit 69f360e

Please sign in to comment.