from __future__ import annotations import pytest from cachelib import SimpleCache from email_campaigns.fonts.cache import GoogleFontsCache from email_campaigns.fonts.services.google_fonts import GoogleFont _FONTS = [ 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", ), 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", ), GoogleFont( name="Open Sans", fallback_font="Arial", generic_font_family="sans-serif", css_font_family="'Open Sans', Arial, sans-serif", url="https://fonts.googleapis.com/css2?family=Open+Sans", ), GoogleFont( name="Lato", fallback_font="Arial", generic_font_family="sans-serif", css_font_family="'Lato', Arial, sans-serif", url="https://fonts.googleapis.com/css2?family=Lato", ), GoogleFont( name="Inter", fallback_font="Arial", generic_font_family="sans-serif", css_font_family="'Inter', Arial, sans-serif", url="https://fonts.googleapis.com/css2?family=Inter", ), GoogleFont( name="Merriweather", fallback_font="Georgia", generic_font_family="serif", css_font_family="'Merriweather', Georgia, serif", url="https://fonts.googleapis.com/css2?family=Merriweather", ), GoogleFont( name="Playfair Display", fallback_font="Georgia", generic_font_family="serif", css_font_family="'Playfair Display', Georgia, serif", url="https://fonts.googleapis.com/css2?family=Playfair+Display", ), GoogleFont( name="Raleway", fallback_font="Arial", generic_font_family="sans-serif", css_font_family="'Raleway', Arial, sans-serif", url="https://fonts.googleapis.com/css2?family=Raleway", ), ] @pytest.fixture def cache() -> GoogleFontsCache: instance = GoogleFontsCache.__new__(GoogleFontsCache) instance.cache = SimpleCache() instance.prefix = "test:" instance.populate(_FONTS) return instance class TestGoogleFontsCacheGetFont: def test_returns_font_for_existing_name(self, cache: GoogleFontsCache) -> None: result = cache.get_font("Roboto") assert result is not None assert result.name == "Roboto" def test_returns_none_for_unknown_font(self, cache: GoogleFontsCache) -> None: assert cache.get_font("NotAFont") is None def test_case_sensitive(self, cache: GoogleFontsCache) -> None: assert cache.get_font("roboto") is None def test_returns_none_when_cache_empty(self) -> None: instance = GoogleFontsCache.__new__(GoogleFontsCache) instance.cache = SimpleCache() instance.prefix = "test:" assert instance.get_font("Roboto") is None def test_returned_font_has_correct_data(self, cache: GoogleFontsCache) -> None: result = cache.get_font("Roboto") assert result is not None assert result.css_font_family == "'Roboto', Arial, sans-serif" assert result.url == "https://fonts.googleapis.com/css2?family=Roboto" class TestGoogleFontsCacheSearch: def test_no_term_returns_all(self, cache: GoogleFontsCache) -> None: result = cache.search(None, 100) assert [f.name for f in result] == [f.name for f in _FONTS] def test_no_term_respects_limit(self, cache: GoogleFontsCache) -> None: result = cache.search(None, 3) assert len(result) == 3 def test_search_reduces_results(self, cache: GoogleFontsCache) -> None: all_results = cache.search(None, 100) filtered_results = cache.search("montser", 100) assert len(filtered_results) < len(all_results) def test_partial_match_finds_montserrat(self, cache: GoogleFontsCache) -> None: result = cache.search("montser", 10) assert any(f.name == "Montserrat" for f in result) def test_partial_match_top_result_is_montserrat( self, cache: GoogleFontsCache ) -> None: result = cache.search("montser", 10) assert result[0].name == "Montserrat" def test_typo_finds_roboto(self, cache: GoogleFontsCache) -> None: result = cache.search("Robto", 10) assert any(f.name == "Roboto" for f in result) def test_unrelated_term_returns_empty(self, cache: GoogleFontsCache) -> None: result = cache.search("xxxxnotafont", 10) assert result == [] def test_substring_matches_ranked_before_fuzzy(self) -> None: # "Robotto" appears before "Roboto" in the list — without prioritisation it would come first. # Searching "roboto": "roboto" is a substring of "Roboto" but not "Robotto" (scores 83 fuzzy). # Expected: Roboto (substring match) ranked before Robotto (fuzzy-only match). instance = GoogleFontsCache.__new__(GoogleFontsCache) instance.cache = SimpleCache() instance.prefix = "test:" instance.populate( [ GoogleFont( name="Robotto", fallback_font="Arial", generic_font_family="sans-serif", css_font_family="'Robotto'", url="", ), GoogleFont( name="Roboto", fallback_font="Arial", generic_font_family="sans-serif", css_font_family="'Roboto'", url="", ), ] ) result = instance.search("roboto", 10) names = [f.name for f in result] assert names.index("Roboto") < names.index("Robotto")