mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-05 13:35:23 +02:00
feat: Filter Recipes By Household (and a ton of bug fixes) (#4207)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
parent
2a6922a85c
commit
7c274de778
65 changed files with 896 additions and 590 deletions
|
@ -0,0 +1,81 @@
|
|||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.household.household import HouseholdCreate
|
||||
from mealie.schema.household.household_preferences import CreateHouseholdPreferences
|
||||
from mealie.services.household_services.household_service import HouseholdService
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.factories import random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_private_group", [True, False])
|
||||
def test_get_all_households(api_client: TestClient, unique_user: TestUser, is_private_group: bool):
|
||||
unique_user.repos.group_preferences.patch(UUID(unique_user.group_id), {"private_group": is_private_group})
|
||||
households = [
|
||||
HouseholdService.create_household(
|
||||
unique_user.repos,
|
||||
HouseholdCreate(name=random_string()),
|
||||
CreateHouseholdPreferences(private_household=False),
|
||||
)
|
||||
for _ in range(5)
|
||||
]
|
||||
|
||||
response = api_client.get(api_routes.explore_groups_group_slug_households(unique_user.group_id))
|
||||
if is_private_group:
|
||||
assert response.status_code == 404
|
||||
else:
|
||||
assert response.status_code == 200
|
||||
response_ids = [item["id"] for item in response.json()["items"]]
|
||||
for household in households:
|
||||
assert str(household.id) in response_ids
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_private_group", [True, False])
|
||||
def test_get_all_households_public_only(api_client: TestClient, unique_user: TestUser, is_private_group: bool):
|
||||
unique_user.repos.group_preferences.patch(UUID(unique_user.group_id), {"private_group": is_private_group})
|
||||
public_household = HouseholdService.create_household(
|
||||
unique_user.repos,
|
||||
HouseholdCreate(name=random_string()),
|
||||
CreateHouseholdPreferences(private_household=False),
|
||||
)
|
||||
private_household = HouseholdService.create_household(
|
||||
unique_user.repos,
|
||||
HouseholdCreate(name=random_string()),
|
||||
CreateHouseholdPreferences(private_household=True),
|
||||
)
|
||||
|
||||
response = api_client.get(api_routes.explore_groups_group_slug_households(unique_user.group_id))
|
||||
if is_private_group:
|
||||
assert response.status_code == 404
|
||||
else:
|
||||
assert response.status_code == 200
|
||||
response_ids = [item["id"] for item in response.json()["items"]]
|
||||
assert str(public_household.id) in response_ids
|
||||
assert str(private_household.id) not in response_ids
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_private_group", [True, False])
|
||||
@pytest.mark.parametrize("is_private_household", [True, False])
|
||||
def test_get_household(
|
||||
api_client: TestClient, unique_user: TestUser, is_private_group: bool, is_private_household: bool
|
||||
):
|
||||
unique_user.repos.group_preferences.patch(UUID(unique_user.group_id), {"private_group": is_private_group})
|
||||
household = household = HouseholdService.create_household(
|
||||
unique_user.repos,
|
||||
HouseholdCreate(name=random_string()),
|
||||
CreateHouseholdPreferences(private_household=is_private_household),
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.explore_groups_group_slug_households_household_slug(unique_user.group_id, household.slug),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
|
||||
if is_private_group or is_private_household:
|
||||
assert response.status_code == 404
|
||||
else:
|
||||
assert response.status_code == 200
|
||||
assert response.json()["id"] == str(household.id)
|
|
@ -1,3 +1,5 @@
|
|||
import random
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
|
@ -33,13 +35,10 @@ def test_get_group_members_filtered(api_client: TestClient, unique_user: TestUse
|
|||
assert str(h2_user.user_id) in all_ids
|
||||
|
||||
|
||||
def test_get_households(unfiltered_database: AllRepositories, api_client: TestClient, unique_user: TestUser):
|
||||
households = [
|
||||
unfiltered_database.households.create({"name": random_string(), "group_id": unique_user.group_id})
|
||||
for _ in range(5)
|
||||
]
|
||||
def test_get_households(api_client: TestClient, unique_user: TestUser):
|
||||
households = [unique_user.repos.households.create({"name": random_string()}) for _ in range(5)]
|
||||
response = api_client.get(api_routes.groups_households, headers=unique_user.token)
|
||||
response_ids = [item["id"] for item in response.json()]
|
||||
response_ids = [item["id"] for item in response.json()["items"]]
|
||||
for household in households:
|
||||
assert str(household.id) in response_ids
|
||||
|
||||
|
@ -58,23 +57,22 @@ def test_get_households_filtered(unfiltered_database: AllRepositories, api_clien
|
|||
]
|
||||
|
||||
response = api_client.get(api_routes.groups_households, headers=unique_user.token)
|
||||
response_ids = [item["id"] for item in response.json()]
|
||||
response_ids = [item["id"] for item in response.json()["items"]]
|
||||
for household in group_1_households:
|
||||
assert str(household.id) in response_ids
|
||||
for household in group_2_households:
|
||||
assert str(household.id) not in response_ids
|
||||
|
||||
|
||||
def test_get_household(unfiltered_database: AllRepositories, api_client: TestClient, unique_user: TestUser):
|
||||
group_1_id = unique_user.group_id
|
||||
group_2_id = str(unfiltered_database.groups.create({"name": random_string()}).id)
|
||||
def test_get_one_household(api_client: TestClient, unique_user: TestUser):
|
||||
households = [unique_user.repos.households.create({"name": random_string()}) for _ in range(5)]
|
||||
household = random.choice(households)
|
||||
|
||||
group_1_household = unfiltered_database.households.create({"name": random_string(), "group_id": group_1_id})
|
||||
group_2_household = unfiltered_database.households.create({"name": random_string(), "group_id": group_2_id})
|
||||
|
||||
response = api_client.get(api_routes.groups_households_slug(group_1_household.slug), headers=unique_user.token)
|
||||
response = api_client.get(api_routes.groups_households_household_slug(household.slug), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["id"] == str(group_1_household.id)
|
||||
assert response.json()["id"] == str(household.id)
|
||||
|
||||
response = api_client.get(api_routes.groups_households_slug(group_2_household.slug), headers=unique_user.token)
|
||||
|
||||
def test_get_one_household_not_found(api_client: TestClient, unique_user: TestUser):
|
||||
response = api_client.get(api_routes.groups_households_household_slug(random_string()), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import random
|
||||
from collections.abc import Generator
|
||||
from dataclasses import dataclass
|
||||
from uuid import UUID
|
||||
|
||||
|
@ -35,19 +36,20 @@ class TestCookbook:
|
|||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def cookbooks(unique_user: TestUser) -> list[TestCookbook]:
|
||||
def cookbooks(unique_user: TestUser) -> Generator[list[TestCookbook]]:
|
||||
database = unique_user.repos
|
||||
|
||||
data: list[ReadCookBook] = []
|
||||
yield_data: list[TestCookbook] = []
|
||||
for _ in range(3):
|
||||
cb = database.cookbooks.create(SaveCookBook(**get_page_data(unique_user.group_id, unique_user.household_id)))
|
||||
assert cb.slug
|
||||
data.append(cb)
|
||||
yield_data.append(TestCookbook(id=cb.id, slug=cb.slug, name=cb.name, data=cb.model_dump()))
|
||||
|
||||
yield yield_data
|
||||
|
||||
for cb in yield_data:
|
||||
for cb in data:
|
||||
try:
|
||||
database.cookbooks.delete(cb.id)
|
||||
except Exception:
|
||||
|
|
|
@ -3,6 +3,9 @@ from datetime import datetime, timezone
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.cookbook.cookbook import SaveCookBook
|
||||
from mealie.schema.recipe.recipe import Recipe
|
||||
from mealie.schema.recipe.recipe_category import TagSave
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.factories import random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
@ -65,6 +68,38 @@ def test_get_all_recipes_includes_all_households(
|
|||
assert str(h2_recipe_id) in response_ids
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_private_household", [True, False])
|
||||
def test_get_all_recipes_with_household_filter(
|
||||
api_client: TestClient, unique_user: TestUser, h2_user: TestUser, is_private_household: bool
|
||||
):
|
||||
household = unique_user.repos.households.get_one(h2_user.household_id)
|
||||
assert household and household.preferences
|
||||
household.preferences.private_household = is_private_household
|
||||
unique_user.repos.household_preferences.update(household.id, household.preferences)
|
||||
|
||||
response = api_client.post(api_routes.recipes, json={"name": random_string()}, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
recipe = unique_user.repos.recipes.get_one(response.json())
|
||||
assert recipe and recipe.id
|
||||
recipe_id = recipe.id
|
||||
|
||||
response = api_client.post(api_routes.recipes, json={"name": random_string()}, headers=h2_user.token)
|
||||
assert response.status_code == 201
|
||||
h2_recipe = h2_user.repos.recipes.get_one(response.json())
|
||||
assert h2_recipe and h2_recipe.id
|
||||
h2_recipe_id = h2_recipe.id
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.recipes,
|
||||
params={"households": [h2_recipe.household_id], "page": 1, "perPage": -1},
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
response_ids = {recipe["id"] for recipe in response.json()["items"]}
|
||||
assert str(recipe_id) not in response_ids
|
||||
assert str(h2_recipe_id) in response_ids
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_private_household", [True, False])
|
||||
def test_get_one_recipe_from_another_household(
|
||||
api_client: TestClient, unique_user: TestUser, h2_user: TestUser, is_private_household: bool
|
||||
|
@ -220,3 +255,49 @@ def test_user_can_update_last_made_on_other_household(
|
|||
assert recipe["id"] == str(h2_recipe_id)
|
||||
new_last_made = recipe["lastMade"]
|
||||
assert new_last_made == now != old_last_made
|
||||
|
||||
|
||||
def test_cookbook_recipes_only_includes_current_households(
|
||||
api_client: TestClient, unique_user: TestUser, h2_user: TestUser
|
||||
):
|
||||
tag = unique_user.repos.tags.create(TagSave(name=random_string(), group_id=unique_user.group_id))
|
||||
recipes = unique_user.repos.recipes.create_many(
|
||||
[
|
||||
Recipe(
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=random_string(),
|
||||
tags=[tag],
|
||||
)
|
||||
for _ in range(3)
|
||||
]
|
||||
)
|
||||
other_recipes = h2_user.repos.recipes.create_many(
|
||||
[
|
||||
Recipe(
|
||||
user_id=h2_user.user_id,
|
||||
group_id=h2_user.group_id,
|
||||
name=random_string(),
|
||||
)
|
||||
for _ in range(3)
|
||||
]
|
||||
)
|
||||
|
||||
cookbook = unique_user.repos.cookbooks.create(
|
||||
SaveCookBook(
|
||||
name=random_string(),
|
||||
group_id=unique_user.group_id,
|
||||
household_id=unique_user.household_id,
|
||||
tags=[tag],
|
||||
)
|
||||
)
|
||||
|
||||
response = api_client.get(api_routes.recipes, params={"cookbook": cookbook.slug}, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
recipes = [Recipe.model_validate(data) for data in response.json()["items"]]
|
||||
|
||||
fetched_recipe_ids = {recipe.id for recipe in recipes}
|
||||
for recipe in recipes:
|
||||
assert recipe.id in fetched_recipe_ids
|
||||
for recipe in other_recipes:
|
||||
assert recipe.id not in fetched_recipe_ids
|
||||
|
|
|
@ -20,6 +20,7 @@ from recipe_scrapers.plugins import SchemaOrgFillPlugin
|
|||
from slugify import slugify
|
||||
|
||||
from mealie.pkgs.safehttp.transport import AsyncSafeTransport
|
||||
from mealie.schema.cookbook.cookbook import SaveCookBook
|
||||
from mealie.schema.recipe.recipe import Recipe, RecipeCategory, RecipeSummary, RecipeTag
|
||||
from mealie.schema.recipe.recipe_category import CategorySave, TagSave
|
||||
from mealie.schema.recipe.recipe_notes import RecipeNote
|
||||
|
@ -791,3 +792,47 @@ def test_get_random_order(api_client: TestClient, unique_user: utils.TestUser):
|
|||
badparams: dict[str, int | str] = {"page": 1, "perPage": -1, "orderBy": "random"}
|
||||
response = api_client.get(api_routes.recipes, params=badparams, headers=unique_user.token)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
def test_get_cookbook_recipes(api_client: TestClient, unique_user: utils.TestUser):
|
||||
tag = unique_user.repos.tags.create(TagSave(name=random_string(), group_id=unique_user.group_id))
|
||||
cookbook_recipes = unique_user.repos.recipes.create_many(
|
||||
[
|
||||
Recipe(
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=random_string(),
|
||||
tags=[tag],
|
||||
)
|
||||
for _ in range(3)
|
||||
]
|
||||
)
|
||||
other_recipes = unique_user.repos.recipes.create_many(
|
||||
[
|
||||
Recipe(
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=random_string(),
|
||||
)
|
||||
for _ in range(3)
|
||||
]
|
||||
)
|
||||
|
||||
cookbook = unique_user.repos.cookbooks.create(
|
||||
SaveCookBook(
|
||||
name=random_string(),
|
||||
group_id=unique_user.group_id,
|
||||
household_id=unique_user.household_id,
|
||||
tags=[tag],
|
||||
)
|
||||
)
|
||||
|
||||
response = api_client.get(api_routes.recipes, params={"cookbook": cookbook.slug}, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
recipes = [Recipe.model_validate(data) for data in response.json()["items"]]
|
||||
|
||||
fetched_recipe_ids = {recipe.id for recipe in recipes}
|
||||
for recipe in cookbook_recipes:
|
||||
assert recipe.id in fetched_recipe_ids
|
||||
for recipe in other_recipes:
|
||||
assert recipe.id not in fetched_recipe_ids
|
||||
|
|
|
@ -247,6 +247,16 @@ def explore_groups_group_slug_foods_item_id(group_slug, item_id):
|
|||
return f"{prefix}/explore/groups/{group_slug}/foods/{item_id}"
|
||||
|
||||
|
||||
def explore_groups_group_slug_households(group_slug):
|
||||
"""`/api/explore/groups/{group_slug}/households`"""
|
||||
return f"{prefix}/explore/groups/{group_slug}/households"
|
||||
|
||||
|
||||
def explore_groups_group_slug_households_household_slug(group_slug, household_slug):
|
||||
"""`/api/explore/groups/{group_slug}/households/{household_slug}`"""
|
||||
return f"{prefix}/explore/groups/{group_slug}/households/{household_slug}"
|
||||
|
||||
|
||||
def explore_groups_group_slug_organizers_categories(group_slug):
|
||||
"""`/api/explore/groups/{group_slug}/organizers/categories`"""
|
||||
return f"{prefix}/explore/groups/{group_slug}/organizers/categories"
|
||||
|
@ -292,9 +302,9 @@ def foods_item_id(item_id):
|
|||
return f"{prefix}/foods/{item_id}"
|
||||
|
||||
|
||||
def groups_households_slug(slug):
|
||||
"""`/api/groups/households/{slug}`"""
|
||||
return f"{prefix}/groups/households/{slug}"
|
||||
def groups_households_household_slug(household_slug):
|
||||
"""`/api/groups/households/{household_slug}`"""
|
||||
return f"{prefix}/groups/households/{household_slug}"
|
||||
|
||||
|
||||
def groups_labels_item_id(item_id):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue