Skip to content

Commit

Permalink
test: add new unit tests for cached weather suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
linabutler committed Feb 16, 2023
1 parent 63f5c13 commit b3a31a8
Showing 1 changed file with 284 additions and 2 deletions.
286 changes: 284 additions & 2 deletions tests/unit/providers/weather/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

"""Unit tests for the weather provider module."""

from typing import Any
from typing import Any, cast

import pytest
from pytest import LogCaptureFixture
from pytest_mock import MockerFixture
from redis.asyncio import Redis
from redis.asyncio import Redis, RedisError

from merino.config import settings
from merino.exceptions import BackendError
Expand Down Expand Up @@ -173,3 +173,285 @@ async def test_query_error(
for record in filter_caplog(caplog.records, "merino.providers.weather.provider")
]
assert actual_log_messages == expected_log_messages


@pytest.mark.asyncio
async def test_query_cached_weather_report(
redis_mock: Any,
backend_mock: Any,
provider: Provider,
geolocation: Location,
) -> None:
"""Test that weather reports are cached in Redis after the first request to the backend."""
cache_keys: dict[str, bytes] = {}

async def mock_redis_get(key) -> Any:
return cache_keys.get(key, None)

redis_mock.get.side_effect = mock_redis_get

async def mock_redis_set(key, value, **kwargs):
cache_keys[key] = value

redis_mock.set.side_effect = mock_redis_set

report: WeatherReport = WeatherReport(
city_name="San Francisco",
current_conditions=CurrentConditions(
url=(
"http://www.accuweather.com/en/us/san-francisco-ca/"
"94103/current-weather/39376_pc?lang=en-us"
),
summary="Mostly cloudy",
icon_id=6,
temperature=Temperature(c=15.5, f=60.0),
),
forecast=Forecast(
url=(
"http://www.accuweather.com/en/us/san-francisco-ca/"
"94103/daily-weather-forecast/39376_pc?lang=en-us"
),
summary="Pleasant Saturday",
high=Temperature(c=21.1, f=70.0),
low=Temperature(c=13.9, f=57.0),
),
)
backend_mock.cache_inputs_for_weather_report.return_value = cast(
str, geolocation.city
).encode("utf-8") + cast(str, geolocation.postal_code).encode("utf-8")
backend_mock.get_weather_report.return_value = report

expected_suggestions: list[Suggestion] = [
Suggestion(
title="Weather for San Francisco",
url=(
"http://www.accuweather.com/en/us/san-francisco-ca/"
"94103/current-weather/39376_pc?lang=en-us"
),
provider="weather",
is_sponsored=False,
score=settings.providers.accuweather.score,
icon=None,
city_name=report.city_name,
current_conditions=report.current_conditions,
forecast=report.forecast,
)
]

uncached_suggestions: list[BaseSuggestion] = await provider.query(
SuggestionRequest(query="", geolocation=geolocation)
)
assert uncached_suggestions == expected_suggestions

cache_key = provider.cache_key_for_weather_report(geolocation)
assert cache_key is not None
redis_mock.get.assert_called_once_with(cache_key)
backend_mock.get_weather_report.assert_called_once()
redis_mock.set.assert_called_once()
assert cache_keys[cache_key] is not None

redis_mock.reset_mock()
backend_mock.reset_mock()

cached_suggestions: list[BaseSuggestion] = await provider.query(
SuggestionRequest(query="", geolocation=geolocation)
)
assert cached_suggestions == expected_suggestions

redis_mock.get.assert_called_once_with(cache_key)
backend_mock.get_weather_report.assert_not_called()
redis_mock.set.assert_not_called()


@pytest.mark.asyncio
async def test_query_cached_no_weather_report(
redis_mock: Any,
backend_mock: Any,
provider: Provider,
geolocation: Location,
) -> None:
"""Test that the absence of a weather report for a location is cached in Redis, avoiding
multiple requests to the backend.
"""
cache_keys: dict[str, bytes] = {}

async def mock_redis_get(key) -> Any:
return cache_keys.get(key, None)

redis_mock.get.side_effect = mock_redis_get

async def mock_redis_set(key, value, **kwargs):
cache_keys[key] = value

redis_mock.set.side_effect = mock_redis_set

backend_mock.cache_inputs_for_weather_report.return_value = cast(
str, geolocation.city
).encode("utf-8") + cast(str, geolocation.postal_code).encode("utf-8")
backend_mock.get_weather_report.return_value = None

uncached_suggestions: list[BaseSuggestion] = await provider.query(
SuggestionRequest(query="", geolocation=geolocation)
)
assert uncached_suggestions == []

cache_key = provider.cache_key_for_weather_report(geolocation)
assert cache_key is not None
redis_mock.get.assert_called_once_with(cache_key)
backend_mock.get_weather_report.assert_called_once()
redis_mock.set.assert_called_once()
assert cache_keys[cache_key] is not None

redis_mock.reset_mock()
backend_mock.reset_mock()

cached_suggestions: list[BaseSuggestion] = await provider.query(
SuggestionRequest(query="", geolocation=geolocation)
)
assert cached_suggestions == []

redis_mock.get.assert_called_once_with(cache_key)
backend_mock.get_weather_report.assert_not_called()
redis_mock.set.assert_not_called()


@pytest.mark.asyncio
async def test_query_with_bad_cache_entry(
redis_mock: Any,
backend_mock: Any,
provider: Provider,
geolocation: Location,
) -> None:
"""Test that a bad cache entry causes the provider to make a request to the backend."""
backend_mock.cache_inputs_for_weather_report.return_value = cast(
str, geolocation.city
).encode("utf-8") + cast(str, geolocation.postal_code).encode("utf-8")
cache_key = provider.cache_key_for_weather_report(geolocation)
assert cache_key is not None

cache_keys: dict[str, bytes] = {
cache_key: b"badjson!",
}

async def mock_redis_get(key) -> Any:
return cache_keys.get(key, None)

redis_mock.get.side_effect = mock_redis_get

async def mock_redis_set(key, value, **kwargs):
cache_keys[key] = value

redis_mock.set.side_effect = mock_redis_set

report: WeatherReport = WeatherReport(
city_name="San Francisco",
current_conditions=CurrentConditions(
url=(
"http://www.accuweather.com/en/us/san-francisco-ca/"
"94103/current-weather/39376_pc?lang=en-us"
),
summary="Mostly cloudy",
icon_id=6,
temperature=Temperature(c=15.5, f=60.0),
),
forecast=Forecast(
url=(
"http://www.accuweather.com/en/us/san-francisco-ca/"
"94103/daily-weather-forecast/39376_pc?lang=en-us"
),
summary="Pleasant Saturday",
high=Temperature(c=21.1, f=70.0),
low=Temperature(c=13.9, f=57.0),
),
)
backend_mock.get_weather_report.return_value = report

expected_suggestions: list[Suggestion] = [
Suggestion(
title="Weather for San Francisco",
url=(
"http://www.accuweather.com/en/us/san-francisco-ca/"
"94103/current-weather/39376_pc?lang=en-us"
),
provider="weather",
is_sponsored=False,
score=settings.providers.accuweather.score,
icon=None,
city_name=report.city_name,
current_conditions=report.current_conditions,
forecast=report.forecast,
)
]

suggestions: list[BaseSuggestion] = await provider.query(
SuggestionRequest(query="", geolocation=geolocation)
)
assert suggestions == expected_suggestions

redis_mock.get.assert_called_once_with(cache_key)
backend_mock.get_weather_report.assert_called_once()
redis_mock.set.assert_called_once()


@pytest.mark.asyncio
async def test_query_redis_unavailable(
redis_mock: Any,
backend_mock: Any,
provider: Provider,
geolocation: Location,
) -> None:
"""Test that Redis errors don't prevent requests to the backend."""
redis_mock.get.side_effect = RedisError("mercury in retrograde")
redis_mock.set.side_effect = RedisError("synergies not aligned")

report: WeatherReport = WeatherReport(
city_name="San Francisco",
current_conditions=CurrentConditions(
url=(
"http://www.accuweather.com/en/us/san-francisco-ca/"
"94103/current-weather/39376_pc?lang=en-us"
),
summary="Mostly cloudy",
icon_id=6,
temperature=Temperature(c=15.5, f=60.0),
),
forecast=Forecast(
url=(
"http://www.accuweather.com/en/us/san-francisco-ca/"
"94103/daily-weather-forecast/39376_pc?lang=en-us"
),
summary="Pleasant Saturday",
high=Temperature(c=21.1, f=70.0),
low=Temperature(c=13.9, f=57.0),
),
)
backend_mock.cache_inputs_for_weather_report.return_value = cast(
str, geolocation.city
).encode("utf-8") + cast(str, geolocation.postal_code).encode("utf-8")
backend_mock.get_weather_report.return_value = report

expected_suggestions: list[Suggestion] = [
Suggestion(
title="Weather for San Francisco",
url=(
"http://www.accuweather.com/en/us/san-francisco-ca/"
"94103/current-weather/39376_pc?lang=en-us"
),
provider="weather",
is_sponsored=False,
score=settings.providers.accuweather.score,
icon=None,
city_name=report.city_name,
current_conditions=report.current_conditions,
forecast=report.forecast,
)
]

suggestions: list[BaseSuggestion] = await provider.query(
SuggestionRequest(query="", geolocation=geolocation)
)
assert suggestions == expected_suggestions

redis_mock.get.assert_called_once()
backend_mock.get_weather_report.assert_called_once()
redis_mock.set.assert_called_once()

0 comments on commit b3a31a8

Please sign in to comment.