From afe4e1ed6adbe29cf67f4f1c58dcb7721b24353e Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:43:57 +0000 Subject: [PATCH 1/4] happy linter happy life --- mealie/routes/spa/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mealie/routes/spa/__init__.py b/mealie/routes/spa/__init__.py index a673969c7b8..9715006d382 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) 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 From cecd6266e2199b5ae4ae21309f146638feb1f298 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:30:08 +0000 Subject: [PATCH 2/4] add spa tests --- tests/integration_tests/test_spa.py | 131 ++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 5 deletions(-) diff --git a/tests/integration_tests/test_spa.py b/tests/integration_tests/test_spa.py index f4c3dc190c8..0bb87e508a2 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", [True, False]) +@pytest.mark.asyncio +async def test_spa_serve_recipe_with_meta(unique_user: TestUser, use_public: 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 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", [True, False]) +@pytest.mark.asyncio +async def test_spa_serve_recipe_with_meta_invalid_data(unique_user: TestUser, use_public: 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 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 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 else user, session=unique_user.repos.session + ) + if use_public: + 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 else user, session=unique_user.repos.session + ) + if use_public: + 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 From e53e5b6d5107f9c52f952c685556df85f0869f43 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Mon, 9 Sep 2024 18:11:21 +0000 Subject: [PATCH 3/4] fix repo access --- mealie/routes/spa/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mealie/routes/spa/__init__.py b/mealie/routes/spa/__init__.py index 9715006d382..77af4747dc3 100644 --- a/mealie/routes/spa/__init__.py +++ b/mealie/routes/spa/__init__.py @@ -168,7 +168,7 @@ def serve_recipe_with_meta_public( 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 and recipe.settings) or not recipe.settings.public: @@ -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") From 926af859a890176ed9f648127df67171d28735bc Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:51:44 +0000 Subject: [PATCH 4/4] made test vars clearer --- tests/integration_tests/test_spa.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/integration_tests/test_spa.py b/tests/integration_tests/test_spa.py index 0bb87e508a2..68373f79ab8 100644 --- a/tests/integration_tests/test_spa.py +++ b/tests/integration_tests/test_spa.py @@ -108,42 +108,42 @@ def test_spa_recipe_json_injection(): assert recipe_name in html -@pytest.mark.parametrize("use_public", [True, False]) +@pytest.mark.parametrize("use_public_user", [True, False]) @pytest.mark.asyncio -async def test_spa_serve_recipe_with_meta(unique_user: TestUser, use_public: bool): +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 else user, session=unique_user.repos.session + 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", [True, False]) +@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: bool): +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 else user, session=unique_user.repos.session + 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 else user, session=unique_user.repos.session + 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 else user, session=unique_user.repos.session + user.group_slug, recipe.slug, user=None if use_public_user else user, session=unique_user.repos.session ) - if use_public: + if use_public_user: assert response.status_code == 404 else: assert response.status_code == 200 @@ -151,9 +151,9 @@ async def test_spa_serve_recipe_with_meta_invalid_data(unique_user: TestUser, us 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 else user, session=unique_user.repos.session + user.group_slug, recipe.slug, user=None if use_public_user else user, session=unique_user.repos.session ) - if use_public: + if use_public_user: assert response.status_code == 404 else: assert response.status_code == 200