From 2f77a5e8f83799855da1e9467b8779748c6fca64 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 12 Sep 2024 04:43:23 -0500 Subject: [PATCH] fix: Broken Social Preview Links (#4183) --- mealie/routes/spa/__init__.py | 14 +-- tests/integration_tests/test_spa.py | 131 ++++++++++++++++++++++++++-- 2 files changed, 133 insertions(+), 12 deletions(-) diff --git a/mealie/routes/spa/__init__.py b/mealie/routes/spa/__init__.py index a673969c7b8..77af4747dc3 100644 --- a/mealie/routes/spa/__init__.py +++ b/mealie/routes/spa/__init__.py @@ -165,13 +165,13 @@ def serve_recipe_with_meta_public( public_repos = AllRepositories(session) group = public_repos.groups.get_by_slug_or_id(group_slug) - if not group or group.preferences.private_group: # type: ignore + if not (group and group.preferences) or group.preferences.private_group: return response_404() - group_repos = AllRepositories(session, group_id=group.id) + group_repos = AllRepositories(session, group_id=group.id, household_id=None) recipe = group_repos.recipes.get_one(recipe_slug) - if not recipe or not recipe.settings.public: # type: ignore + if not (recipe and recipe.settings) or not recipe.settings.public: return response_404() # Inject meta tags @@ -190,9 +190,9 @@ async def serve_recipe_with_meta( return serve_recipe_with_meta_public(group_slug, recipe_slug, session) try: - repos = AllRepositories(session, group_id=user.group_id) + group_repos = AllRepositories(session, group_id=user.group_id, household_id=None) - recipe = repos.recipes.get_one(recipe_slug, "slug") + recipe = group_repos.recipes.get_one(recipe_slug, "slug") if recipe is None: return response_404() @@ -204,8 +204,8 @@ async def serve_recipe_with_meta( async def serve_shared_recipe_with_meta(group_slug: str, token_id: str, session: Session = Depends(generate_session)): try: - repos = AllRepositories(session) - token_summary = repos.recipe_share_tokens.get_one(token_id) + public_repos = AllRepositories(session, group_id=None) + token_summary = public_repos.recipe_share_tokens.get_one(token_id) if token_summary is None: raise Exception("Token Not Found") diff --git a/tests/integration_tests/test_spa.py b/tests/integration_tests/test_spa.py index f4c3dc190c8..68373f79ab8 100644 --- a/tests/integration_tests/test_spa.py +++ b/tests/integration_tests/test_spa.py @@ -1,8 +1,46 @@ +import pytest from bs4 import BeautifulSoup -from mealie.routes.spa import MetaTag, inject_meta, inject_recipe_json +from mealie.routes import spa +from mealie.schema.recipe.recipe import Recipe +from mealie.schema.recipe.recipe_share_token import RecipeShareTokenSave from tests import data as test_data from tests.utils.factories import random_string +from tests.utils.fixture_schemas import TestUser + + +@pytest.fixture(autouse=True) +def set_spa_contents(): + """Inject a simple HTML string into the SPA module to enable metadata injection""" + + spa.__contents = "" + + +def set_group_is_private(unique_user: TestUser, *, is_private: bool): + group = unique_user.repos.groups.get_by_slug_or_id(unique_user.group_id) + assert group and group.preferences + group.preferences.private_group = is_private + unique_user.repos.group_preferences.update(group.id, group.preferences) + + +def set_recipe_is_public(unique_user: TestUser, recipe: Recipe, *, is_public: bool): + assert recipe.settings + recipe.settings.public = is_public + unique_user.repos.recipes.update(recipe.slug, recipe) + + +def create_recipe(user: TestUser) -> Recipe: + recipe = user.repos.recipes.create( + Recipe( + user_id=user.user_id, + group_id=user.group_id, + name=random_string(), + ) + ) + set_group_is_private(user, is_private=False) + set_recipe_is_public(user, recipe, is_public=True) + + return recipe def test_spa_metadata_injection(): @@ -22,9 +60,9 @@ def test_spa_metadata_injection(): assert title_tag and title_tag["content"] - new_title_tag = MetaTag(hid="og:title", property_name="og:title", content=random_string()) - new_arbitrary_tag = MetaTag(hid=random_string(), property_name=random_string(), content=random_string()) - new_html = inject_meta(str(soup), [new_title_tag, new_arbitrary_tag]) + new_title_tag = spa.MetaTag(hid="og:title", property_name="og:title", content=random_string()) + new_arbitrary_tag = spa.MetaTag(hid=random_string(), property_name=random_string(), content=random_string()) + new_html = spa.inject_meta(str(soup), [new_title_tag, new_arbitrary_tag]) # verify changes were injected soup = BeautifulSoup(new_html, "lxml") @@ -63,8 +101,91 @@ def test_spa_recipe_json_injection(): soup = BeautifulSoup(f, "lxml") assert "https://schema.org" not in str(soup) - html = inject_recipe_json(str(soup), schema) + html = spa.inject_recipe_json(str(soup), schema) assert "@context" in html assert "https://schema.org" in html assert recipe_name in html + + +@pytest.mark.parametrize("use_public_user", [True, False]) +@pytest.mark.asyncio +async def test_spa_serve_recipe_with_meta(unique_user: TestUser, use_public_user: bool): + recipe = create_recipe(unique_user) + user = unique_user.repos.users.get_by_username(unique_user.username) + assert user + + response = await spa.serve_recipe_with_meta( + user.group_slug, recipe.slug, user=None if use_public_user else user, session=unique_user.repos.session + ) + assert response.status_code == 200 + assert "https://schema.org" in response.body.decode() + + +@pytest.mark.parametrize("use_public_user", [True, False]) +@pytest.mark.asyncio +async def test_spa_serve_recipe_with_meta_invalid_data(unique_user: TestUser, use_public_user: bool): + recipe = create_recipe(unique_user) + user = unique_user.repos.users.get_by_username(unique_user.username) + assert user + + response = await spa.serve_recipe_with_meta( + random_string(), recipe.slug, user=None if use_public_user else user, session=unique_user.repos.session + ) + assert response.status_code == 404 + + response = await spa.serve_recipe_with_meta( + user.group_slug, random_string(), user=None if use_public_user else user, session=unique_user.repos.session + ) + assert response.status_code == 404 + + set_recipe_is_public(unique_user, recipe, is_public=False) + response = await spa.serve_recipe_with_meta( + user.group_slug, recipe.slug, user=None if use_public_user else user, session=unique_user.repos.session + ) + if use_public_user: + assert response.status_code == 404 + else: + assert response.status_code == 200 + + set_group_is_private(unique_user, is_private=True) + set_recipe_is_public(unique_user, recipe, is_public=True) + response = await spa.serve_recipe_with_meta( + user.group_slug, recipe.slug, user=None if use_public_user else user, session=unique_user.repos.session + ) + if use_public_user: + assert response.status_code == 404 + else: + assert response.status_code == 200 + + +@pytest.mark.parametrize("use_private_group", [True, False]) +@pytest.mark.parametrize("use_public_recipe", [True, False]) +@pytest.mark.asyncio +async def test_spa_service_shared_recipe_with_meta( + unique_user: TestUser, use_private_group: bool, use_public_recipe: bool +): + group = unique_user.repos.groups.get_by_slug_or_id(unique_user.group_id) + assert group + recipe = create_recipe(unique_user) + + # visibility settings shouldn't matter for shared recipes + set_group_is_private(unique_user, is_private=use_private_group) + set_recipe_is_public(unique_user, recipe, is_public=use_public_recipe) + + token = unique_user.repos.recipe_share_tokens.create( + RecipeShareTokenSave(recipe_id=recipe.id, group_id=unique_user.group_id) + ) + + response = await spa.serve_shared_recipe_with_meta(group.slug, token.id, session=unique_user.repos.session) + assert response.status_code == 200 + assert "https://schema.org" in response.body.decode() + + +@pytest.mark.asyncio +async def test_spa_service_shared_recipe_with_meta_invalid_data(unique_user: TestUser): + group = unique_user.repos.groups.get_by_slug_or_id(unique_user.group_id) + assert group + + response = await spa.serve_shared_recipe_with_meta(group.slug, random_string(), session=unique_user.repos.session) + assert response.status_code == 404