diff --git a/mealie/services/scraper/scraper.py b/mealie/services/scraper/scraper.py index 9b446182a..cebce076f 100644 --- a/mealie/services/scraper/scraper.py +++ b/mealie/services/scraper/scraper.py @@ -4,13 +4,13 @@ from re import search as regex_search from uuid import uuid4 from fastapi import HTTPException, status -from slugify import slugify from mealie.core.root_logger import get_logger from mealie.lang.providers import Translator from mealie.pkgs import cache from mealie.repos.repository_factory import AllRepositories from mealie.schema.recipe import Recipe +from mealie.schema.recipe.recipe import create_recipe_slug from mealie.services.recipe.recipe_data_service import RecipeDataService from mealie.services.scraper.scraped_extras import ScrapedExtras @@ -72,7 +72,7 @@ async def create_from_html( if new_recipe.name is None: new_recipe.name = "Untitled" - new_recipe.slug = slugify(new_recipe.name) + new_recipe.slug = create_recipe_slug(new_recipe.name) new_recipe.image = cache.new_key(4) except Exception as e: recipe_data_service.logger.exception(f"Error Scraping Image: {e}") @@ -80,6 +80,6 @@ async def create_from_html( if new_recipe.name is None or new_recipe.name == "": new_recipe.name = f"No Recipe Name Found - {uuid4()!s}" - new_recipe.slug = slugify(new_recipe.name) + new_recipe.slug = create_recipe_slug(new_recipe.name) return new_recipe, extras diff --git a/tests/unit_tests/services_tests/scraper_tests/test_scraper.py b/tests/unit_tests/services_tests/scraper_tests/test_scraper.py new file mode 100644 index 000000000..ca2e27cd3 --- /dev/null +++ b/tests/unit_tests/services_tests/scraper_tests/test_scraper.py @@ -0,0 +1,40 @@ +import pytest + +from mealie.lang.providers import get_locale_provider +from mealie.schema.recipe.recipe import Recipe +from mealie.services.scraper import scraper +from mealie.services.scraper.recipe_scraper import RecipeScraper +from mealie.services.scraper.scraped_extras import ScrapedExtras + + +@pytest.mark.asyncio +async def test_create_from_html_truncates_long_slug(monkeypatch): + """A recipe scraped with an excessively long name must yield a slug short enough + to be used as a filesystem directory name. + + Regression test for GH #7662: scraping (e.g. an Instagram caption) dumped the entire + text into the recipe name. The scraper used a raw ``slugify`` instead of the shared + ``create_recipe_slug`` helper, so the persisted slug could exceed the OS 255-byte + filename limit. Editing/saving the recipe then raised an uncaught + ``OSError: [Errno 36] File name too long`` in ``RecipeService.check_assets``. + """ + # ~1300 characters, mirroring the caption-as-title data from the bug report + long_name = "High Protein Low Calorie Recipes Smash Or Pass " * 28 + + async def fake_scrape(self, url, html=None, on_progress=None): + return Recipe(name=long_name), ScrapedExtras() + + monkeypatch.setattr(RecipeScraper, "scrape", fake_scrape) + + translator = get_locale_provider() + recipe, _ = await scraper.create_from_html( + "https://example.com/recipe", + repos=None, # type: ignore[arg-type] + translator=translator, + html="", + ) + + # The full title is preserved (name column is unbounded)... + assert recipe.name == long_name + # ...but the slug is truncated to a filesystem-safe length. + assert 0 < len(recipe.slug) <= 250