init commit
This commit is contained in:
175
tests/test_weather_service.py
Normal file
175
tests/test_weather_service.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""Tests for the weather service."""
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
|
||||
from app.services.weather_service import WeatherService, WeatherServiceError
|
||||
from app.utils.http_client import HttpClient
|
||||
|
||||
|
||||
SAMPLE_API_RESPONSE = {
|
||||
"address": "viola,tn",
|
||||
"resolvedAddress": "Viola, TN, United States",
|
||||
"timezone": "America/Chicago",
|
||||
"days": [
|
||||
{
|
||||
"datetime": "2024-01-15",
|
||||
"hours": [
|
||||
{
|
||||
"datetime": "12:00:00",
|
||||
"datetimeEpoch": 2000000000, # Future timestamp
|
||||
"temp": 45.0,
|
||||
"feelslike": 42.0,
|
||||
"humidity": 65.0,
|
||||
"precip": 0.0,
|
||||
"precipprob": 20.0,
|
||||
"snow": 0.0,
|
||||
"snowdepth": 0.0,
|
||||
"windspeed": 10.0,
|
||||
"windgust": 15.0,
|
||||
"winddir": 180.0,
|
||||
"pressure": 30.1,
|
||||
"visibility": 10.0,
|
||||
"cloudcover": 50.0,
|
||||
"uvindex": 3,
|
||||
"conditions": "Partially cloudy",
|
||||
"icon": "partly-cloudy-day",
|
||||
},
|
||||
{
|
||||
"datetime": "13:00:00",
|
||||
"datetimeEpoch": 2000003600,
|
||||
"temp": 48.0,
|
||||
"feelslike": 45.0,
|
||||
"humidity": 60.0,
|
||||
"precip": 0.0,
|
||||
"precipprob": 15.0,
|
||||
"snow": 0.0,
|
||||
"snowdepth": 0.0,
|
||||
"windspeed": 12.0,
|
||||
"windgust": 18.0,
|
||||
"winddir": 185.0,
|
||||
"pressure": 30.0,
|
||||
"visibility": 10.0,
|
||||
"cloudcover": 45.0,
|
||||
"uvindex": 4,
|
||||
"conditions": "Partially cloudy",
|
||||
"icon": "partly-cloudy-day",
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
"alerts": [
|
||||
{
|
||||
"event": "Wind Advisory",
|
||||
"headline": "Wind Advisory in effect until 6 PM",
|
||||
"description": "Gusty winds expected throughout the day.",
|
||||
"onset": "2024-01-15T08:00:00",
|
||||
"ends": "2024-01-15T18:00:00",
|
||||
"id": "NWS-456",
|
||||
"language": "en",
|
||||
"link": "https://weather.gov/alerts/456",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class TestWeatherService:
|
||||
"""Test weather service functionality."""
|
||||
|
||||
def test_requires_api_key(self) -> None:
|
||||
"""Test that service requires an API key."""
|
||||
with pytest.raises(WeatherServiceError, match="API key is required"):
|
||||
WeatherService(api_key="")
|
||||
|
||||
@responses.activate
|
||||
def test_get_forecast_success(self) -> None:
|
||||
"""Test successful forecast retrieval."""
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/viola%2Ctn",
|
||||
json=SAMPLE_API_RESPONSE,
|
||||
status=200,
|
||||
)
|
||||
|
||||
service = WeatherService(api_key="test-key")
|
||||
forecast = service.get_forecast("viola,tn", hours_ahead=24)
|
||||
|
||||
assert forecast.location == "viola,tn"
|
||||
assert forecast.resolved_address == "Viola, TN, United States"
|
||||
assert len(forecast.hourly_forecasts) == 2
|
||||
assert len(forecast.alerts) == 1
|
||||
assert forecast.alerts[0].event == "Wind Advisory"
|
||||
|
||||
@responses.activate
|
||||
def test_get_forecast_parses_hourly_data(self) -> None:
|
||||
"""Test that hourly forecast data is correctly parsed."""
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/viola%2Ctn",
|
||||
json=SAMPLE_API_RESPONSE,
|
||||
status=200,
|
||||
)
|
||||
|
||||
service = WeatherService(api_key="test-key")
|
||||
forecast = service.get_forecast("viola,tn")
|
||||
|
||||
hour = forecast.hourly_forecasts[0]
|
||||
assert hour.temp == 45.0
|
||||
assert hour.feelslike == 42.0
|
||||
assert hour.humidity == 65.0
|
||||
assert hour.precip_prob == 20.0
|
||||
assert hour.wind_speed == 10.0
|
||||
assert hour.wind_gust == 15.0
|
||||
assert hour.conditions == "Partially cloudy"
|
||||
|
||||
@responses.activate
|
||||
def test_get_forecast_handles_api_error(self) -> None:
|
||||
"""Test handling of API errors."""
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/viola%2Ctn",
|
||||
json={"error": "Invalid API key"},
|
||||
status=401,
|
||||
)
|
||||
|
||||
service = WeatherService(api_key="bad-key")
|
||||
|
||||
with pytest.raises(WeatherServiceError, match="Failed to fetch forecast"):
|
||||
service.get_forecast("viola,tn")
|
||||
|
||||
@responses.activate
|
||||
def test_get_forecast_handles_invalid_json(self) -> None:
|
||||
"""Test handling of invalid JSON response."""
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/viola%2Ctn",
|
||||
body="not json",
|
||||
status=200,
|
||||
content_type="text/plain",
|
||||
)
|
||||
|
||||
service = WeatherService(api_key="test-key")
|
||||
|
||||
with pytest.raises(WeatherServiceError, match="Invalid JSON"):
|
||||
service.get_forecast("viola,tn")
|
||||
|
||||
@responses.activate
|
||||
def test_location_encoding(self) -> None:
|
||||
"""Test that location is properly URL encoded."""
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/Nashville%2C%20TN",
|
||||
json={
|
||||
"address": "Nashville, TN",
|
||||
"resolvedAddress": "Nashville, TN, United States",
|
||||
"timezone": "America/Chicago",
|
||||
"days": [],
|
||||
"alerts": [],
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
service = WeatherService(api_key="test-key")
|
||||
forecast = service.get_forecast("Nashville, TN")
|
||||
|
||||
assert forecast.resolved_address == "Nashville, TN, United States"
|
||||
Reference in New Issue
Block a user