mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-03 04:25:24 +02:00
fix: Recipe Zip Export Can't Be Imported (#2585)
* clean recipe data when importing via zip * added tests * simplified recursive logic
This commit is contained in:
parent
84b477edf6
commit
26ef351ae7
2 changed files with 336 additions and 3 deletions
|
@ -1,5 +1,12 @@
|
|||
import json
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
from uuid import uuid4
|
||||
from zipfile import ZipFile
|
||||
|
||||
import pytest
|
||||
from bs4 import BeautifulSoup
|
||||
|
@ -9,18 +16,37 @@ from recipe_scrapers._abstract import AbstractScraper
|
|||
from recipe_scrapers._schemaorg import SchemaOrg
|
||||
from slugify import slugify
|
||||
|
||||
from mealie.schema.recipe.recipe import RecipeCategory
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.recipe.recipe import RecipeCategory, RecipeSummary, RecipeTag
|
||||
from mealie.services.recipe.recipe_data_service import RecipeDataService
|
||||
from mealie.services.scraper.recipe_scraper import DEFAULT_SCRAPER_STRATEGIES
|
||||
from tests import data, utils
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.factories import random_string
|
||||
from tests.utils.factories import random_int, random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
from tests.utils.recipe_data import RecipeSiteTestCase, get_recipe_test_cases
|
||||
|
||||
recipe_test_data = get_recipe_test_cases()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def tempdir() -> Generator[str, None, None]:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
yield td
|
||||
|
||||
|
||||
def zip_recipe(tempdir: str, recipe: RecipeSummary) -> dict:
|
||||
data_file = tempfile.NamedTemporaryFile(mode="w+", dir=tempdir, suffix=".json", delete=False)
|
||||
json.dump(json.loads(recipe.json()), data_file)
|
||||
data_file.flush()
|
||||
|
||||
zip_file = shutil.make_archive(os.path.join(tempdir, "zipfile"), "zip")
|
||||
with ZipFile(zip_file, "w") as zf:
|
||||
zf.write(data_file.name)
|
||||
|
||||
return {"archive": Path(zip_file).read_bytes()}
|
||||
|
||||
|
||||
def get_init(html_path: Path):
|
||||
"""
|
||||
Override the init method of the abstract scraper to return a bootstrapped init function that
|
||||
|
@ -163,6 +189,257 @@ def test_create_by_url_with_tags(
|
|||
assert tag["name"] in expected_tags
|
||||
|
||||
|
||||
def test_create_recipe_from_zip(database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str):
|
||||
recipe_name = random_string()
|
||||
recipe = RecipeSummary(
|
||||
id=uuid4(),
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=recipe_name,
|
||||
slug=recipe_name,
|
||||
)
|
||||
|
||||
r = api_client.post(
|
||||
api_routes.recipes_create_from_zip, files=zip_recipe(tempdir, recipe), headers=unique_user.token
|
||||
)
|
||||
assert r.status_code == 201
|
||||
|
||||
fetched_recipe = database.recipes.get_by_slug(unique_user.group_id, recipe.slug)
|
||||
assert fetched_recipe
|
||||
|
||||
|
||||
def test_create_recipe_from_zip_invalid_group(
|
||||
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
|
||||
):
|
||||
recipe_name = random_string()
|
||||
recipe = RecipeSummary(
|
||||
id=uuid4(),
|
||||
user_id=unique_user.user_id,
|
||||
group_id=uuid4(),
|
||||
name=recipe_name,
|
||||
slug=recipe_name,
|
||||
)
|
||||
|
||||
r = api_client.post(
|
||||
api_routes.recipes_create_from_zip, files=zip_recipe(tempdir, recipe), headers=unique_user.token
|
||||
)
|
||||
assert r.status_code == 201
|
||||
|
||||
fetched_recipe = database.recipes.get_by_slug(unique_user.group_id, recipe.slug)
|
||||
assert fetched_recipe
|
||||
|
||||
# the group should always be set to the current user's group
|
||||
assert str(fetched_recipe.group_id) == str(unique_user.group_id)
|
||||
|
||||
|
||||
def test_create_recipe_from_zip_invalid_user(
|
||||
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
|
||||
):
|
||||
recipe_name = random_string()
|
||||
recipe = RecipeSummary(
|
||||
id=uuid4(),
|
||||
user_id=uuid4(),
|
||||
group_id=unique_user.group_id,
|
||||
name=recipe_name,
|
||||
slug=recipe_name,
|
||||
)
|
||||
|
||||
r = api_client.post(
|
||||
api_routes.recipes_create_from_zip, files=zip_recipe(tempdir, recipe), headers=unique_user.token
|
||||
)
|
||||
assert r.status_code == 201
|
||||
|
||||
fetched_recipe = database.recipes.get_by_slug(unique_user.group_id, recipe.slug)
|
||||
assert fetched_recipe
|
||||
|
||||
# invalid users should default to the current user
|
||||
assert str(fetched_recipe.user_id) == str(unique_user.user_id)
|
||||
|
||||
|
||||
def test_create_recipe_from_zip_existing_category(
|
||||
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
|
||||
):
|
||||
categories = database.categories.by_group(unique_user.group_id).create_many(
|
||||
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
|
||||
)
|
||||
category = random.choice(categories)
|
||||
|
||||
recipe_name = random_string()
|
||||
recipe = RecipeSummary(
|
||||
id=uuid4(),
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=recipe_name,
|
||||
slug=recipe_name,
|
||||
recipe_category=[category],
|
||||
)
|
||||
|
||||
r = api_client.post(
|
||||
api_routes.recipes_create_from_zip, files=zip_recipe(tempdir, recipe), headers=unique_user.token
|
||||
)
|
||||
assert r.status_code == 201
|
||||
|
||||
fetched_recipe = database.recipes.get_by_slug(unique_user.group_id, recipe.slug)
|
||||
assert fetched_recipe
|
||||
assert fetched_recipe.recipe_category
|
||||
assert len(fetched_recipe.recipe_category) == 1
|
||||
assert str(fetched_recipe.recipe_category[0].id) == str(category.id)
|
||||
|
||||
|
||||
def test_create_recipe_from_zip_existing_tag(
|
||||
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
|
||||
):
|
||||
tags = database.tags.by_group(unique_user.group_id).create_many(
|
||||
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
|
||||
)
|
||||
tag = random.choice(tags)
|
||||
|
||||
recipe_name = random_string()
|
||||
recipe = RecipeSummary(
|
||||
id=uuid4(),
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=recipe_name,
|
||||
slug=recipe_name,
|
||||
tags=[tag],
|
||||
)
|
||||
|
||||
r = api_client.post(
|
||||
api_routes.recipes_create_from_zip, files=zip_recipe(tempdir, recipe), headers=unique_user.token
|
||||
)
|
||||
assert r.status_code == 201
|
||||
|
||||
fetched_recipe = database.recipes.get_by_slug(unique_user.group_id, recipe.slug)
|
||||
assert fetched_recipe
|
||||
assert fetched_recipe.tags
|
||||
assert len(fetched_recipe.tags) == 1
|
||||
assert str(fetched_recipe.tags[0].id) == str(tag.id)
|
||||
|
||||
|
||||
def test_create_recipe_from_zip_existing_category_wrong_ids(
|
||||
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
|
||||
):
|
||||
categories = database.categories.by_group(unique_user.group_id).create_many(
|
||||
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
|
||||
)
|
||||
category = random.choice(categories)
|
||||
invalid_category = RecipeCategory(id=uuid4(), name=category.name, slug=category.slug)
|
||||
|
||||
recipe_name = random_string()
|
||||
recipe = RecipeSummary(
|
||||
id=uuid4(),
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=recipe_name,
|
||||
slug=recipe_name,
|
||||
recipe_category=[invalid_category],
|
||||
)
|
||||
|
||||
r = api_client.post(
|
||||
api_routes.recipes_create_from_zip, files=zip_recipe(tempdir, recipe), headers=unique_user.token
|
||||
)
|
||||
assert r.status_code == 201
|
||||
|
||||
fetched_recipe = database.recipes.get_by_slug(unique_user.group_id, recipe.slug)
|
||||
assert fetched_recipe
|
||||
assert fetched_recipe.recipe_category
|
||||
assert len(fetched_recipe.recipe_category) == 1
|
||||
assert str(fetched_recipe.recipe_category[0].id) == str(category.id)
|
||||
|
||||
|
||||
def test_create_recipe_from_zip_existing_tag_wrong_ids(
|
||||
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
|
||||
):
|
||||
tags = database.tags.by_group(unique_user.group_id).create_many(
|
||||
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
|
||||
)
|
||||
tag = random.choice(tags)
|
||||
invalid_tag = RecipeTag(id=uuid4(), name=tag.name, slug=tag.slug)
|
||||
|
||||
recipe_name = random_string()
|
||||
recipe = RecipeSummary(
|
||||
id=uuid4(),
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=recipe_name,
|
||||
slug=recipe_name,
|
||||
tags=[invalid_tag],
|
||||
)
|
||||
|
||||
r = api_client.post(
|
||||
api_routes.recipes_create_from_zip, files=zip_recipe(tempdir, recipe), headers=unique_user.token
|
||||
)
|
||||
assert r.status_code == 201
|
||||
|
||||
fetched_recipe = database.recipes.get_by_slug(unique_user.group_id, recipe.slug)
|
||||
assert fetched_recipe
|
||||
assert fetched_recipe.tags
|
||||
assert len(fetched_recipe.tags) == 1
|
||||
assert str(fetched_recipe.tags[0].id) == str(tag.id)
|
||||
|
||||
|
||||
def test_create_recipe_from_zip_invalid_category(
|
||||
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
|
||||
):
|
||||
invalid_name = random_string()
|
||||
invalid_category = RecipeCategory(id=uuid4(), name=invalid_name, slug=invalid_name)
|
||||
|
||||
recipe_name = random_string()
|
||||
recipe = RecipeSummary(
|
||||
id=uuid4(),
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=recipe_name,
|
||||
slug=recipe_name,
|
||||
recipe_category=[invalid_category],
|
||||
)
|
||||
|
||||
r = api_client.post(
|
||||
api_routes.recipes_create_from_zip, files=zip_recipe(tempdir, recipe), headers=unique_user.token
|
||||
)
|
||||
assert r.status_code == 201
|
||||
|
||||
fetched_recipe = database.recipes.get_by_slug(unique_user.group_id, recipe.slug)
|
||||
assert fetched_recipe
|
||||
assert fetched_recipe.recipe_category
|
||||
assert len(fetched_recipe.recipe_category) == 1
|
||||
|
||||
# a new category should be created
|
||||
assert fetched_recipe.recipe_category[0].name == invalid_name
|
||||
assert fetched_recipe.recipe_category[0].slug == invalid_name
|
||||
|
||||
|
||||
def test_create_recipe_from_zip_invalid_tag(
|
||||
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
|
||||
):
|
||||
invalid_name = random_string()
|
||||
invalid_tag = RecipeTag(id=uuid4(), name=invalid_name, slug=invalid_name)
|
||||
|
||||
recipe_name = random_string()
|
||||
recipe = RecipeSummary(
|
||||
id=uuid4(),
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=recipe_name,
|
||||
slug=recipe_name,
|
||||
tags=[invalid_tag],
|
||||
)
|
||||
|
||||
r = api_client.post(
|
||||
api_routes.recipes_create_from_zip, files=zip_recipe(tempdir, recipe), headers=unique_user.token
|
||||
)
|
||||
assert r.status_code == 201
|
||||
|
||||
fetched_recipe = database.recipes.get_by_slug(unique_user.group_id, recipe.slug)
|
||||
assert fetched_recipe
|
||||
assert fetched_recipe.tags
|
||||
assert len(fetched_recipe.tags) == 1
|
||||
|
||||
# a new tag should be created
|
||||
assert fetched_recipe.tags[0].name == invalid_name
|
||||
assert fetched_recipe.tags[0].slug == invalid_name
|
||||
|
||||
|
||||
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
||||
def test_read_update(
|
||||
api_client: TestClient,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue