from unittest import mock import pytest from email_campaigns.fonts.cache import GoogleFontsCache from email_campaigns.fonts.handlers import GetGoogleFontsHandler, GetGoogleFontsRequest from email_campaigns.fonts.services.google_fonts import GoogleFont, GoogleFontsService _FONT = GoogleFont( name="Roboto", fallback_font="Arial", generic_font_family="sans-serif", css_font_family="'Roboto', Arial, sans-serif", url="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;1,400;1,700&display=swap", ) _MONTSERRAT = GoogleFont( name="Montserrat", fallback_font="Arial", generic_font_family="sans-serif", css_font_family="'Montserrat', Arial, sans-serif", url="https://fonts.googleapis.com/css2?family=Montserrat", ) class TestGetGoogleFontsHandler: @pytest.fixture def cache_mock(self) -> mock.MagicMock: m = mock.MagicMock(spec=GoogleFontsCache) m.is_populated.return_value = True m.search.return_value = [] return m @pytest.fixture def service_mock(self) -> mock.MagicMock: m = mock.MagicMock(spec=GoogleFontsService) m.get_fonts.return_value = [] return m @pytest.fixture def handler( self, service_mock: mock.MagicMock, cache_mock: mock.MagicMock ) -> GetGoogleFontsHandler: return GetGoogleFontsHandler(service=service_mock, cache=cache_mock) def test_cache_hit_skips_api_fetch( self, handler: GetGoogleFontsHandler, service_mock: mock.MagicMock, cache_mock: mock.MagicMock, ) -> None: cache_mock.is_populated.return_value = True handler.handle(GetGoogleFontsRequest(identity_id="user-1")) service_mock.get_fonts.assert_not_called() cache_mock.populate.assert_not_called() def test_cache_miss_fetches_and_populates( self, handler: GetGoogleFontsHandler, service_mock: mock.MagicMock, cache_mock: mock.MagicMock, ) -> None: cache_mock.is_populated.return_value = False service_mock.get_fonts.return_value = [_FONT] handler.handle(GetGoogleFontsRequest(identity_id="user-1")) service_mock.get_fonts.assert_called_once() cache_mock.populate.assert_called_once_with([_FONT]) def test_search_and_limit_forwarded_to_cache( self, handler: GetGoogleFontsHandler, cache_mock: mock.MagicMock, ) -> None: handler.handle( GetGoogleFontsRequest(identity_id="user-1", search="robo", limit=5) ) cache_mock.search.assert_called_once_with("robo", 5) def test_returns_cache_search_results( self, handler: GetGoogleFontsHandler, cache_mock: mock.MagicMock, ) -> None: cache_mock.search.return_value = [_FONT] result = handler.handle(GetGoogleFontsRequest(identity_id="user-1")) assert result == [_FONT] def test_default_limit_applied( self, handler: GetGoogleFontsHandler, cache_mock: mock.MagicMock, ) -> None: handler.handle(GetGoogleFontsRequest(identity_id="user-1")) _, limit = cache_mock.search.call_args.args assert limit == 100 def test_search_reduces_results( self, handler: GetGoogleFontsHandler, cache_mock: mock.MagicMock, ) -> None: cache_mock.search.side_effect = lambda term, _limit: ( [_MONTSERRAT] if term else [_FONT, _MONTSERRAT] ) all_results = handler.handle(GetGoogleFontsRequest(identity_id="user-1")) filtered_results = handler.handle( GetGoogleFontsRequest(identity_id="user-1", search="montser") ) assert len(filtered_results) < len(all_results) assert filtered_results == [_MONTSERRAT]