1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-05 13:35:23 +02:00

feat: Add Households to Mealie (#3970)

This commit is contained in:
Michael Genson 2024-08-22 10:14:32 -05:00 committed by GitHub
parent 0c29cef17d
commit eb170cc7e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
315 changed files with 6975 additions and 3577 deletions

View file

@ -9,13 +9,15 @@ from tests.utils.fixture_schemas import TestUser
@pytest.mark.parametrize("is_private_group", [True, False], ids=["private group", "public group"])
def test_public_about_get_app_info(api_client: TestClient, is_private_group: bool, database: AllRepositories):
def test_public_about_get_app_info(
api_client: TestClient, is_private_group: bool, unfiltered_database: AllRepositories
):
settings = get_app_settings()
group = database.groups.get_by_name(settings.DEFAULT_GROUP)
group = unfiltered_database.groups.get_by_name(settings.DEFAULT_GROUP)
assert group and group.preferences
group.preferences.private_group = is_private_group
database.group_preferences.update(group.id, group.preferences)
unfiltered_database.group_preferences.update(group.id, group.preferences)
response = api_client.get(api_routes.app_about)
as_dict = response.json()

View file

@ -1,5 +1,8 @@
from fastapi.testclient import TestClient
from mealie.core.config import get_app_settings
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.user.user import GroupInDB
from tests.utils import api_routes
from tests.utils.assertion_helpers import assert_ignore_keys
from tests.utils.factories import random_bool, random_string
@ -29,21 +32,34 @@ def test_admin_create_group(api_client: TestClient, admin_user: TestUser):
response = api_client.post(api_routes.admin_groups, json={"name": random_string()}, headers=admin_user.token)
assert response.status_code == 201
# verify preferences are set and the default household is created
group = GroupInDB.model_validate(response.json())
assert group.preferences and len(group.households) == 1
created_household = group.households[0]
assert created_household.name == get_app_settings().DEFAULT_HOUSEHOLD
response = api_client.get(api_routes.admin_households_item_id(created_household.id), headers=admin_user.token)
assert response.status_code == 200
assert response.json()["id"] == str(created_household.id)
# verify no extra households are created
response = api_client.get(api_routes.admin_households, headers=admin_user.token, params={"page": 1, "perPage": -1})
assert response.status_code == 200
items = response.json()["items"]
filtered_item_ids: list[str] = []
for item in items:
if item["groupId"] == str(group.id):
filtered_item_ids.append(item["id"])
assert len(filtered_item_ids) == 1
assert filtered_item_ids[0] == str(created_household.id)
def test_admin_update_group(api_client: TestClient, admin_user: TestUser, unique_user: TestUser):
update_payload = {
"id": unique_user.group_id,
"name": "New Name",
"preferences": {
"privateGroup": random_bool(),
"firstDayOfWeek": 2,
"recipePublic": random_bool(),
"recipeShowNutrition": random_bool(),
"recipeShowAssets": random_bool(),
"recipeLandscapeView": random_bool(),
"recipeDisableComments": random_bool(),
"recipeDisableAmount": random_bool(),
},
"preferences": {"privateGroup": random_bool()},
}
response = api_client.put(
@ -57,18 +73,13 @@ def test_admin_update_group(api_client: TestClient, admin_user: TestUser, unique
as_json = response.json()
assert as_json["name"] == update_payload["name"]
assert_ignore_keys(as_json["preferences"], update_payload["preferences"])
assert_ignore_keys(as_json["preferences"], update_payload["preferences"]) # type: ignore
def test_admin_delete_group(api_client: TestClient, admin_user: TestUser, unique_user: TestUser):
# Delete User
response = api_client.delete(api_routes.admin_users_item_id(unique_user.user_id), headers=admin_user.token)
def test_admin_delete_group(unfiltered_database: AllRepositories, api_client: TestClient, admin_user: TestUser):
group = unfiltered_database.groups.create({"name": random_string()})
response = api_client.delete(api_routes.admin_groups_item_id(group.id), headers=admin_user.token)
assert response.status_code == 200
# Delete Group
response = api_client.delete(api_routes.admin_groups_item_id(unique_user.group_id), headers=admin_user.token)
assert response.status_code == 200
# Ensure Group is Deleted
response = api_client.get(api_routes.admin_groups_item_id(unique_user.group_id), headers=admin_user.token)
response = api_client.get(api_routes.admin_groups_item_id(group.id), headers=admin_user.token)
assert response.status_code == 404

View file

@ -0,0 +1,76 @@
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from tests.utils import api_routes
from tests.utils.assertion_helpers import assert_ignore_keys
from tests.utils.factories import random_bool, random_string
from tests.utils.fixture_schemas import TestUser
def test_home_household_not_deletable(api_client: TestClient, admin_user: TestUser):
response = api_client.delete(api_routes.admin_households_item_id(admin_user.household_id), headers=admin_user.token)
assert response.status_code == 400
def test_admin_household_routes_are_restricted(api_client: TestClient, unique_user: TestUser, admin_user: TestUser):
response = api_client.get(api_routes.admin_households, headers=unique_user.token)
assert response.status_code == 403
response = api_client.post(api_routes.admin_households, json={}, headers=unique_user.token)
assert response.status_code == 403
response = api_client.get(api_routes.admin_households_item_id(admin_user.household_id), headers=unique_user.token)
assert response.status_code == 403
response = api_client.get(api_routes.admin_households_item_id(admin_user.household_id), headers=unique_user.token)
assert response.status_code == 403
def test_admin_create_household(api_client: TestClient, admin_user: TestUser):
response = api_client.post(
api_routes.admin_households,
json={"name": random_string(), "groupId": admin_user.group_id},
headers=admin_user.token,
)
assert response.status_code == 201
def test_admin_update_household(api_client: TestClient, admin_user: TestUser, unique_user: TestUser):
update_payload = {
"id": unique_user.household_id,
"groupId": admin_user.group_id,
"name": "New Name",
"preferences": {
"privateHousehold": random_bool(),
"firstDayOfWeek": 2,
"recipePublic": random_bool(),
"recipeShowNutrition": random_bool(),
"recipeShowAssets": random_bool(),
"recipeLandscapeView": random_bool(),
"recipeDisableComments": random_bool(),
"recipeDisableAmount": random_bool(),
},
}
response = api_client.put(
api_routes.admin_households_item_id(unique_user.household_id),
json=update_payload,
headers=admin_user.token,
)
assert response.status_code == 200
as_json = response.json()
assert as_json["name"] == update_payload["name"]
assert_ignore_keys(as_json["preferences"], update_payload["preferences"]) # type: ignore
def test_admin_delete_household(unfiltered_database: AllRepositories, api_client: TestClient, admin_user: TestUser):
group = unfiltered_database.groups.create({"name": random_string()})
household = unfiltered_database.households.create({"name": random_string(), "group_id": group.id})
response = api_client.delete(api_routes.admin_households_item_id(household.id), headers=admin_user.token)
assert response.status_code == 200
response = api_client.get(api_routes.admin_households_item_id(household.id), headers=admin_user.token)
assert response.status_code == 404

View file

@ -32,7 +32,7 @@ def test_init_superuser(api_client: TestClient, admin_user: TestUser):
admin_data = response.json()
assert admin_data["id"] == admin_user.user_id
assert admin_data["id"] == str(admin_user.user_id)
assert admin_data["groupId"] == admin_user.group_id
assert admin_data["fullName"] == "Change Me"
@ -93,7 +93,7 @@ def test_update_other_user_as_not_admin(api_client: TestClient, unique_user: Tes
settings = get_app_settings()
update_data = {
"id": unique_user.user_id,
"id": str(unique_user.user_id),
"fullName": "Updated Name",
"email": settings._DEFAULT_EMAIL,
"group": "Home",
@ -122,7 +122,7 @@ def test_self_demote_admin(api_client: TestClient, admin_user: TestUser):
def test_self_promote_admin(api_client: TestClient, unique_user: TestUser):
update_data = {
"id": unique_user.user_id,
"id": str(unique_user.user_id),
"fullName": "Updated Name",
"email": "user@example.com",
"group": "Home",

View file

@ -2,9 +2,9 @@ import random
import pytest
from fastapi.testclient import TestClient
from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.cookbook.cookbook import SaveCookBook
from mealie.schema.cookbook.cookbook import ReadCookBook, SaveCookBook
from mealie.schema.recipe.recipe import Recipe
from mealie.schema.recipe.recipe_category import TagSave
from tests.utils import api_routes
@ -12,41 +12,63 @@ from tests.utils.factories import random_int, random_string
from tests.utils.fixture_schemas import TestUser
@pytest.mark.parametrize("is_private_group", [True, False], ids=["group_is_private", "group_is_public"])
@pytest.mark.parametrize("is_private_group", [True, False])
@pytest.mark.parametrize("is_household_1_private", [True, False])
@pytest.mark.parametrize("is_household_2_private", [True, False])
def test_get_all_cookbooks(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
h2_user: TestUser,
is_private_group: bool,
is_household_1_private: bool,
is_household_2_private: bool,
):
## Set Up Group
group = database.groups.get_one(unique_user.group_id)
group = unique_user.repos.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = is_private_group
group.preferences.recipe_public = not is_private_group
database.group_preferences.update(group.id, group.preferences)
unique_user.repos.group_preferences.update(group.id, group.preferences)
## Set Up Cookbooks
default_cookbooks = database.cookbooks.create_many(
[SaveCookBook(name=random_string(), group_id=unique_user.group_id) for _ in range(random_int(15, 20))]
)
## Set Up Household and Cookbooks
household_private_map: dict[UUID4, bool] = {}
public_cookbooks: list[ReadCookBook] = []
private_cookbooks: list[ReadCookBook] = []
for database, is_private_household in [
(unique_user.repos, is_household_1_private),
(h2_user.repos, is_household_2_private),
]:
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
random.shuffle(default_cookbooks)
split_index = random_int(6, 12)
public_cookbooks = default_cookbooks[:split_index]
private_cookbooks = default_cookbooks[split_index:]
household_private_map[household.id] = is_private_household
household.preferences.private_household = is_private_household
household.preferences.recipe_public = not is_private_household
database.household_preferences.update(household.id, household.preferences)
for cookbook in public_cookbooks:
cookbook.public = True
## Set Up Cookbooks
default_cookbooks = database.cookbooks.create_many(
[
SaveCookBook(name=random_string(), group_id=unique_user.group_id, household_id=unique_user.household_id)
for _ in range(random_int(15, 20))
]
)
for cookbook in private_cookbooks:
cookbook.public = False
random.shuffle(default_cookbooks)
split_index = random_int(6, 12)
public_cookbooks.extend(default_cookbooks[:split_index])
private_cookbooks.extend(default_cookbooks[split_index:])
database.cookbooks.update_many(public_cookbooks + private_cookbooks)
for cookbook in default_cookbooks[:split_index]:
cookbook.public = True
for cookbook in default_cookbooks[split_index:]:
cookbook.public = False
database.cookbooks.update_many(default_cookbooks)
## Test Cookbooks
response = api_client.get(api_routes.explore_cookbooks_group_slug(unique_user.group_id))
response = api_client.get(api_routes.explore_groups_group_slug_cookbooks(unique_user.group_id))
if is_private_group:
assert response.status_code == 404
return
@ -56,55 +78,61 @@ def test_get_all_cookbooks(
fetched_ids: set[str] = {cookbook["id"] for cookbook in cookbooks_data["items"]}
for cookbook in public_cookbooks:
assert str(cookbook.id) in fetched_ids
is_private_household = household_private_map[cookbook.household_id]
if is_private_household:
assert str(cookbook.id) not in fetched_ids
else:
assert str(cookbook.id) in fetched_ids
for cookbook in private_cookbooks:
assert str(cookbook.id) not in fetched_ids
@pytest.mark.parametrize(
"is_private_group, is_private_cookbook",
[
(True, True),
(True, False),
(False, True),
(False, False),
],
ids=[
"group_is_private_cookbook_is_private",
"group_is_private_cookbook_is_public",
"group_is_public_cookbook_is_private",
"group_is_public_cookbook_is_public",
],
)
@pytest.mark.parametrize("is_private_group", [True, False])
@pytest.mark.parametrize("is_private_household", [True, False])
@pytest.mark.parametrize("is_private_cookbook", [True, False])
def test_get_one_cookbook(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
is_private_group: bool,
is_private_household: bool,
is_private_cookbook: bool,
):
database = unique_user.repos
## Set Up Group
group = database.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = is_private_group
group.preferences.recipe_public = not is_private_group
database.group_preferences.update(group.id, group.preferences)
## Set Up Household
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
household.preferences.private_household = is_private_household
household.preferences.recipe_public = not is_private_household
database.household_preferences.update(household.id, household.preferences)
## Set Up Cookbook
cookbook = database.cookbooks.create(
SaveCookBook(
name=random_string(),
group_id=unique_user.group_id,
household_id=unique_user.household_id,
public=not is_private_cookbook,
)
)
## Test Cookbook
response = api_client.get(api_routes.explore_cookbooks_group_slug_item_id(unique_user.group_id, cookbook.id))
if is_private_group or is_private_cookbook:
response = api_client.get(api_routes.explore_groups_group_slug_cookbooks_item_id(unique_user.group_id, cookbook.id))
if is_private_group or is_private_household or is_private_cookbook:
assert response.status_code == 404
if is_private_group:
assert response.json()["detail"] == "group not found"
else:
assert response.json()["detail"] == "cookbook not found"
return
assert response.status_code == 200
@ -112,18 +140,31 @@ def test_get_one_cookbook(
assert cookbook_data["id"] == str(cookbook.id)
def test_get_cookbooks_with_recipes(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
def test_get_cookbooks_with_recipes(api_client: TestClient, unique_user: TestUser, h2_user: TestUser):
database = unique_user.repos
# Create a public and private recipe with a known tag
group = database.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = False
group.preferences.recipe_public = True
database.group_preferences.update(group.id, group.preferences)
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
household.preferences.private_household = False
household.preferences.recipe_public = True
database.household_preferences.update(household.id, household.preferences)
tag = database.tags.create(TagSave(name=random_string(), group_id=unique_user.group_id))
public_recipe, private_recipe = database.recipes.create_many(
Recipe(user_id=unique_user.user_id, group_id=unique_user.group_id, name=random_string()) for _ in range(2)
Recipe(
user_id=unique_user.user_id,
group_id=unique_user.group_id,
name=random_string(),
)
for _ in range(2)
)
assert public_recipe.settings
@ -136,14 +177,40 @@ def test_get_cookbooks_with_recipes(api_client: TestClient, unique_user: TestUse
database.recipes.update_many([public_recipe, private_recipe])
# Create a recipe in another household that's public with the same known tag
other_database = h2_user.repos
other_household = other_database.households.get_one(h2_user.household_id)
assert other_household and other_household.preferences
other_household.preferences.private_household = False
other_household.preferences.recipe_public = True
other_database.household_preferences.update(household.id, household.preferences)
other_household_recipe = other_database.recipes.create(
Recipe(
user_id=h2_user.user_id,
group_id=h2_user.group_id,
name=random_string(),
)
)
assert other_household_recipe.settings
other_household_recipe.settings.public = True
other_household_recipe.tags = [tag]
other_database.recipes.update(other_household_recipe.slug, other_household_recipe)
# Create a public cookbook with tag
cookbook = database.cookbooks.create(
SaveCookBook(name=random_string(), group_id=unique_user.group_id, public=True, tags=[tag])
SaveCookBook(
name=random_string(),
group_id=unique_user.group_id,
household_id=unique_user.household_id,
public=True,
tags=[tag],
)
)
database.cookbooks.create(cookbook)
# Get the cookbook and make sure we only get the public recipe
response = api_client.get(api_routes.explore_cookbooks_group_slug_item_id(unique_user.group_id, cookbook.id))
# Get the cookbook and make sure we only get the public recipe from the correct household
response = api_client.get(api_routes.explore_groups_group_slug_cookbooks_item_id(unique_user.group_id, cookbook.id))
assert response.status_code == 200
cookbook_data = response.json()
assert cookbook_data["id"] == str(cookbook.id)
@ -152,3 +219,4 @@ def test_get_cookbooks_with_recipes(api_client: TestClient, unique_user: TestUse
assert len(cookbook_recipe_ids) == 1
assert str(public_recipe.id) in cookbook_recipe_ids
assert str(private_recipe.id) not in cookbook_recipe_ids
assert str(other_household_recipe.id) not in cookbook_recipe_ids

View file

@ -1,35 +1,46 @@
import pytest
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe_ingredient import SaveIngredientFood
from tests.utils import api_routes
from tests.utils.factories import random_int, random_string
from tests.utils.fixture_schemas import TestUser
@pytest.mark.parametrize("is_private_group", [True, False], ids=["group_is_private", "group_is_public"])
@pytest.mark.parametrize("is_private_group", [True, False])
@pytest.mark.parametrize("is_private_household", [True, False])
def test_get_all_foods(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
is_private_group: bool,
is_private_household: bool,
):
database = unique_user.repos
## Set Up Group
group = database.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = is_private_group
group.preferences.recipe_public = not is_private_group
database.group_preferences.update(group.id, group.preferences)
## Set Up Household
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
household.preferences.private_household = is_private_household
household.preferences.recipe_public = not is_private_household
database.household_preferences.update(household.id, household.preferences)
## Set Up Foods
foods = database.ingredient_foods.create_many(
[SaveIngredientFood(name=random_string(), group_id=unique_user.group_id) for _ in range(random_int(15, 20))]
)
## Test Foods
response = api_client.get(api_routes.explore_foods_group_slug(unique_user.group_id))
response = api_client.get(api_routes.explore_groups_group_slug_foods(unique_user.group_id))
# whether or not the household is private shouldn't affect food visibility
if is_private_group:
assert response.status_code == 404
return
@ -42,26 +53,38 @@ def test_get_all_foods(
assert str(food.id) in fetched_ids
@pytest.mark.parametrize("is_private_group", [True, False], ids=["group_is_private", "group_is_public"])
@pytest.mark.parametrize("is_private_group", [True, False])
@pytest.mark.parametrize("is_private_household", [True, False])
def test_get_one_food(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
is_private_group: bool,
is_private_household: bool,
):
database = unique_user.repos
## Set Up Group
group = database.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = is_private_group
group.preferences.recipe_public = not is_private_group
database.group_preferences.update(group.id, group.preferences)
## Set Up Household
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
household.preferences.private_household = is_private_household
household.preferences.recipe_public = not is_private_household
database.household_preferences.update(household.id, household.preferences)
## Set Up Food
food = database.ingredient_foods.create(SaveIngredientFood(name=random_string(), group_id=unique_user.group_id))
## Test Food
response = api_client.get(api_routes.explore_foods_group_slug_item_id(unique_user.group_id, food.id))
response = api_client.get(api_routes.explore_groups_group_slug_foods_item_id(unique_user.group_id, food.id))
# whether or not the household is private shouldn't affect food visibility
if is_private_group:
assert response.status_code == 404
return

View file

@ -3,7 +3,6 @@ from enum import Enum
import pytest
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe_category import CategorySave, TagSave
from mealie.schema.recipe.recipe_tool import RecipeToolSave
from tests.utils import api_routes
@ -17,53 +16,46 @@ class OrganizerType(Enum):
tools = "tools"
@pytest.mark.parametrize(
"organizer_type, is_private_group",
[
(OrganizerType.categories, True),
(OrganizerType.categories, False),
(OrganizerType.tags, True),
(OrganizerType.tags, False),
(OrganizerType.tools, True),
(OrganizerType.tools, False),
],
ids=[
"private_group_categories",
"public_group_categories",
"private_group_tags",
"public_group_tags",
"private_group_tools",
"public_group_tools",
],
)
@pytest.mark.parametrize("organizer_type", [OrganizerType.categories, OrganizerType.tags, OrganizerType.tools])
@pytest.mark.parametrize("is_private_group", [True, False])
@pytest.mark.parametrize("is_private_household", [True, False])
def test_get_all_organizers(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
organizer_type: OrganizerType,
is_private_group: bool,
is_private_household: bool,
):
database = unique_user.repos
## Set Up Group
group = database.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = is_private_group
group.preferences.recipe_public = not is_private_group
database.group_preferences.update(group.id, group.preferences)
## Set Up Household
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
household.preferences.private_household = is_private_household
household.preferences.recipe_public = not is_private_household
database.household_preferences.update(household.id, household.preferences)
## Set Up Organizers
if organizer_type is OrganizerType.categories:
item_class = CategorySave
repo = database.categories # type: ignore
route = api_routes.explore_organizers_group_slug_categories
route = api_routes.explore_groups_group_slug_organizers_categories
elif organizer_type is OrganizerType.tags:
item_class = TagSave
repo = database.tags # type: ignore
route = api_routes.explore_organizers_group_slug_tags
route = api_routes.explore_groups_group_slug_organizers_tags
else:
item_class = RecipeToolSave
repo = database.tools # type: ignore
route = api_routes.explore_organizers_group_slug_tools
route = api_routes.explore_groups_group_slug_organizers_tools
organizers = repo.create_many(
[item_class(name=random_string(), group_id=unique_user.group_id) for _ in range(random_int(15, 20))]
@ -71,6 +63,8 @@ def test_get_all_organizers(
## Test Organizers
response = api_client.get(route(unique_user.group_id))
# whether or not the household is private shouldn't affect food visibility
if is_private_group:
assert response.status_code == 404
return
@ -83,58 +77,53 @@ def test_get_all_organizers(
assert str(organizer.id) in fetched_ids
@pytest.mark.parametrize(
"organizer_type, is_private_group",
[
(OrganizerType.categories, True),
(OrganizerType.categories, False),
(OrganizerType.tags, True),
(OrganizerType.tags, False),
(OrganizerType.tools, True),
(OrganizerType.tools, False),
],
ids=[
"private_group_category",
"public_group_category",
"private_group_tag",
"public_group_tag",
"private_group_tool",
"public_group_tool",
],
)
@pytest.mark.parametrize("organizer_type", [OrganizerType.categories, OrganizerType.tags, OrganizerType.tools])
@pytest.mark.parametrize("is_private_group", [True, False])
@pytest.mark.parametrize("is_private_household", [True, False])
def test_get_one_organizer(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
organizer_type: OrganizerType,
is_private_group: bool,
is_private_household: bool,
):
database = unique_user.repos
## Set Up Group
group = database.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = is_private_group
group.preferences.recipe_public = not is_private_group
database.group_preferences.update(group.id, group.preferences)
## Set Up Household
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
household.preferences.private_household = is_private_household
household.preferences.recipe_public = not is_private_household
database.household_preferences.update(household.id, household.preferences)
## Set Up Organizer
if organizer_type is OrganizerType.categories:
item_class = CategorySave
repo = database.categories # type: ignore
route = api_routes.explore_organizers_group_slug_categories_item_id
route = api_routes.explore_groups_group_slug_organizers_categories_item_id
elif organizer_type is OrganizerType.tags:
item_class = TagSave
repo = database.tags # type: ignore
route = api_routes.explore_organizers_group_slug_tags_item_id
route = api_routes.explore_groups_group_slug_organizers_tags_item_id
else:
item_class = RecipeToolSave
repo = database.tools # type: ignore
route = api_routes.explore_organizers_group_slug_tools_item_id
route = api_routes.explore_groups_group_slug_organizers_tools_item_id
organizer = repo.create(item_class(name=random_string(), group_id=unique_user.group_id))
## Test Organizer
response = api_client.get(route(unique_user.group_id, organizer.id))
# whether or not the household is private shouldn't affect food visibility
if is_private_group:
assert response.status_code == 404
return

View file

@ -1,68 +1,79 @@
import random
from dataclasses import dataclass
from typing import Any
import pytest
from fastapi.testclient import TestClient
from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories
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_int, random_string
from tests.utils.fixture_schemas import TestUser
@dataclass(slots=True)
class PublicRecipeTestCase:
private_group: bool
public_recipe: bool
status_code: int
error: str | None
@pytest.mark.parametrize("is_private_group", [True, False], ids=["group_is_private", "group_is_public"])
@pytest.mark.parametrize("is_private_group", [True, False])
@pytest.mark.parametrize("is_household_1_private", [True, False])
@pytest.mark.parametrize("is_household_2_private", [True, False])
def test_get_all_public_recipes(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
h2_user: TestUser,
is_private_group: bool,
is_household_1_private: bool,
is_household_2_private: bool,
):
## Set Up Public and Private Recipes
group = database.groups.get_one(unique_user.group_id)
group = unique_user.repos.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = is_private_group
group.preferences.recipe_public = not is_private_group
database.group_preferences.update(group.id, group.preferences)
unique_user.repos.group_preferences.update(group.id, group.preferences)
default_recipes = database.recipes.create_many(
[
Recipe(
user_id=unique_user.user_id,
group_id=unique_user.group_id,
name=random_string(),
)
for _ in range(random_int(15, 20))
],
)
household_private_map: dict[UUID4, bool] = {}
public_recipes: list[Recipe] = []
private_recipes: list[Recipe] = []
for database, is_private_household in [
(unique_user.repos, is_household_1_private),
(h2_user.repos, is_household_2_private),
]:
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
random.shuffle(default_recipes)
split_index = random_int(6, 12)
public_recipes = default_recipes[:split_index]
private_recipes = default_recipes[split_index:]
household_private_map[household.id] = is_private_household
household.preferences.private_household = is_private_household
household.preferences.recipe_public = not is_private_household
database.household_preferences.update(household.id, household.preferences)
for recipe in public_recipes:
assert recipe.settings
recipe.settings.public = True
default_recipes = database.recipes.create_many(
[
Recipe(
user_id=unique_user.user_id,
group_id=unique_user.group_id,
name=random_string(),
)
for _ in range(random_int(15, 20))
],
)
for recipe in private_recipes:
assert recipe.settings
recipe.settings.public = False
random.shuffle(default_recipes)
split_index = random_int(6, 12)
public_recipes.extend(default_recipes[:split_index])
private_recipes.extend(default_recipes[split_index:])
database.recipes.update_many(public_recipes + private_recipes)
for recipe in default_recipes[:split_index]:
assert recipe.settings
recipe.settings.public = True
for recipe in default_recipes[split_index:]:
assert recipe.settings
recipe.settings.public = False
database.recipes.update_many(default_recipes)
## Query All Recipes
response = api_client.get(api_routes.explore_recipes_group_slug(group.slug))
response = api_client.get(api_routes.explore_groups_group_slug_recipes(group.slug))
if is_private_group:
assert response.status_code == 404
return
@ -72,7 +83,11 @@ def test_get_all_public_recipes(
fetched_ids: set[str] = {recipe["id"] for recipe in recipes_data["items"]}
for recipe in public_recipes:
assert str(recipe.id) in fetched_ids
is_private_household = household_private_map[recipe.household_id]
if is_private_household:
assert str(recipe.id) not in fetched_ids
else:
assert str(recipe.id) in fetched_ids
for recipe in private_recipes:
assert str(recipe.id) not in fetched_ids
@ -89,82 +104,222 @@ def test_get_all_public_recipes(
ids=[
"match_slug",
"not_match_slug",
"bypass_public_filter_1",
"bypass_public_filter_2",
"bypass_public_settings_filter_1",
"bypass_public_settings_filter_2",
],
)
def test_get_all_public_recipes_filtered(
api_client: TestClient,
unique_user: TestUser,
random_recipe: Recipe,
database: AllRepositories,
query_filter: str,
recipe_data: dict[str, Any],
should_fetch: bool,
):
database = unique_user.repos
## Set Up Recipe
group = database.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = False
group.preferences.recipe_public = True
database.group_preferences.update(group.id, group.preferences)
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
household.preferences.private_household = False
household.preferences.recipe_public = True
database.household_preferences.update(household.id, household.preferences)
assert random_recipe.settings
random_recipe.settings.public = True
database.recipes.update(random_recipe.slug, random_recipe.model_dump() | recipe_data)
## Query All Recipes
response = api_client.get(api_routes.explore_recipes_group_slug(group.slug), params={"queryFilter": query_filter})
response = api_client.get(
api_routes.explore_groups_group_slug_recipes(group.slug),
params={"queryFilter": query_filter},
)
assert response.status_code == 200
recipes_data = response.json()
fetched_ids: set[str] = {recipe["id"] for recipe in recipes_data["items"]}
assert should_fetch is (str(random_recipe.id) in fetched_ids)
@pytest.mark.parametrize(
"test_case",
(
PublicRecipeTestCase(private_group=False, public_recipe=True, status_code=200, error=None),
PublicRecipeTestCase(private_group=True, public_recipe=True, status_code=404, error="group not found"),
PublicRecipeTestCase(private_group=False, public_recipe=False, status_code=404, error="recipe not found"),
),
ids=("is public", "group private", "recipe private"),
)
def test_public_recipe_success(
@pytest.mark.parametrize("is_private_group", [True, False])
@pytest.mark.parametrize("is_private_household", [True, False])
@pytest.mark.parametrize("is_private_recipe", [True, False])
def test_get_one_recipe(
api_client: TestClient,
unique_user: TestUser,
random_recipe: Recipe,
database: AllRepositories,
test_case: PublicRecipeTestCase,
is_private_group: bool,
is_private_household: bool,
is_private_recipe: bool,
):
database = unique_user.repos
## Set Up Group
group = database.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = test_case.private_group
group.preferences.recipe_public = not test_case.private_group
group.preferences.private_group = is_private_group
database.group_preferences.update(group.id, group.preferences)
# Set Recipe `settings.public` attribute
## Set Up Household
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
household.preferences.private_household = is_private_household
household.preferences.recipe_public = not is_private_household
database.household_preferences.update(household.id, household.preferences)
## Set Recipe `settings.public` attribute
assert random_recipe.settings
random_recipe.settings.public = test_case.public_recipe
random_recipe.settings.public = not is_private_recipe
database.recipes.update(random_recipe.slug, random_recipe)
# Try to access recipe
## Try to access recipe
recipe_group = database.groups.get_by_slug_or_id(random_recipe.group_id)
recipe_household = database.households.get_by_slug_or_id(random_recipe.household_id)
assert recipe_group
assert recipe_household
response = api_client.get(
api_routes.explore_recipes_group_slug_recipe_slug(
recipe_group.slug,
random_recipe.slug,
)
api_routes.explore_groups_group_slug_recipes_recipe_slug(recipe_group.slug, random_recipe.slug)
)
assert response.status_code == test_case.status_code
if test_case.error:
assert response.json()["detail"] == test_case.error
if is_private_group or is_private_household or is_private_recipe:
assert response.status_code == 404
if is_private_group:
assert response.json()["detail"] == "group not found"
else:
assert response.json()["detail"] == "recipe not found"
return
as_json = response.json()
assert as_json["name"] == random_recipe.name
assert as_json["slug"] == random_recipe.slug
@pytest.mark.parametrize("is_private_cookbook", [True, False])
def test_public_recipe_cookbook_filter(
api_client: TestClient,
unique_user: TestUser,
is_private_cookbook: bool,
):
database = unique_user.repos
## Set Up Group
group = database.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = False
database.group_preferences.update(group.id, group.preferences)
## Set Up Household
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
household.preferences.private_household = False
household.preferences.recipe_public = True
database.household_preferences.update(household.id, household.preferences)
## Set Up Cookbook
cookbook = database.cookbooks.create(
SaveCookBook(
name=random_string(),
group_id=unique_user.group_id,
household_id=unique_user.household_id,
public=not is_private_cookbook,
)
)
## Try to access recipe query
response = api_client.get(
api_routes.explore_groups_group_slug_recipes(group.slug), params={"cookbook": cookbook.id}
)
if is_private_cookbook:
assert response.status_code == 404
else:
assert response.status_code == 200
def test_public_recipe_cookbook_filter_with_recipes(api_client: TestClient, unique_user: TestUser, h2_user: TestUser):
database = unique_user.repos
# Create a public and private recipe with a known tag
group = database.groups.get_one(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = False
database.group_preferences.update(group.id, group.preferences)
household = database.households.get_one(unique_user.household_id)
assert household and household.preferences
household.preferences.private_household = False
household.preferences.recipe_public = True
database.household_preferences.update(household.id, household.preferences)
tag = database.tags.create(TagSave(name=random_string(), group_id=unique_user.group_id))
public_recipe, private_recipe = database.recipes.create_many(
Recipe(
user_id=unique_user.user_id,
group_id=unique_user.group_id,
name=random_string(),
)
for _ in range(2)
)
assert public_recipe.settings
public_recipe.settings.public = True
public_recipe.tags = [tag]
assert private_recipe.settings
private_recipe.settings.public = False
private_recipe.tags = [tag]
database.recipes.update_many([public_recipe, private_recipe])
# Create a recipe in another household that's public with the same known tag
other_database = h2_user.repos
other_household = other_database.households.get_one(h2_user.household_id)
assert other_household and other_household.preferences
other_household.preferences.private_household = False
other_household.preferences.recipe_public = True
other_database.household_preferences.update(household.id, household.preferences)
other_household_recipe = other_database.recipes.create(
Recipe(
user_id=h2_user.user_id,
group_id=h2_user.group_id,
name=random_string(),
)
)
assert other_household_recipe.settings
other_household_recipe.settings.public = True
other_household_recipe.tags = [tag]
other_database.recipes.update(other_household_recipe.slug, other_household_recipe)
# Create a public cookbook with tag
cookbook = database.cookbooks.create(
SaveCookBook(
name=random_string(),
group_id=unique_user.group_id,
household_id=unique_user.household_id,
public=True,
tags=[tag],
)
)
# Get the cookbook's recipes and make sure we only get the public recipe from the correct household
response = api_client.get(
api_routes.explore_groups_group_slug_recipes(unique_user.group_id), params={"cookbook": cookbook.id}
)
assert response.status_code == 200
recipe_ids: set[str] = {recipe["id"] for recipe in response.json()["items"]}
assert len(recipe_ids) == 1
assert str(public_recipe.id) in recipe_ids
assert str(private_recipe.id) not in recipe_ids
assert str(other_household_recipe.id) not in recipe_ids

View file

@ -0,0 +1,364 @@
import inspect
import time
from functools import cached_property
from uuid import uuid4
import pytest
from pydantic import UUID4
from sqlalchemy.orm import Session
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.repos._utils import NOT_SET, NotSet
from mealie.repos.all_repositories import get_repositories
from mealie.repos.repository_generic import GroupRepositoryGeneric, HouseholdRepositoryGeneric, RepositoryGeneric
from mealie.schema._mealie.mealie_model import MealieModel
from mealie.schema.household.group_shopping_list import ShoppingListCreate
from mealie.schema.household.webhook import SaveWebhook
from mealie.schema.recipe.recipe import Recipe
from mealie.schema.recipe.recipe_ingredient import SaveIngredientFood
from mealie.schema.response.pagination import PaginationQuery
from mealie.services.household_services.shopping_lists import ShoppingListService
from tests.utils.factories import random_email, random_string
@pytest.mark.parametrize("group_id", [uuid4(), None, NOT_SET])
@pytest.mark.parametrize("household_id", [uuid4(), None, NOT_SET])
def test_get_repositories_sets_ids(
session: Session, group_id: UUID4 | None | NotSet, household_id: UUID4 | None | NotSet
):
kwargs = {}
if not isinstance(group_id, NotSet):
kwargs["group_id"] = group_id
if not isinstance(household_id, NotSet):
kwargs["household_id"] = household_id
repositories = get_repositories(session, **kwargs)
assert repositories.group_id == group_id
assert repositories.household_id == household_id
# test that sentinel is used correctly
if isinstance(group_id, NotSet):
assert repositories.group_id is NOT_SET
if isinstance(household_id, NotSet):
assert repositories.household_id is NOT_SET
def test_repository_generic_constructor(session: Session):
RepositoryGeneric(session, "id", MealieModel, SqlAlchemyBase)
def test_repository_group_constructor(session: Session):
BASE_ARGS = (session, "id", MealieModel, SqlAlchemyBase)
with pytest.raises(ValueError):
GroupRepositoryGeneric(*BASE_ARGS, group_id=NOT_SET)
GroupRepositoryGeneric(*BASE_ARGS, group_id=None)
GroupRepositoryGeneric(*BASE_ARGS, group_id=uuid4())
def test_repository_household_constructor(session: Session):
BASE_ARGS = (session, "id", MealieModel, SqlAlchemyBase)
with pytest.raises(ValueError):
HouseholdRepositoryGeneric(*BASE_ARGS, group_id=NOT_SET, household_id=NOT_SET)
HouseholdRepositoryGeneric(*BASE_ARGS, group_id=uuid4(), household_id=NOT_SET)
HouseholdRepositoryGeneric(*BASE_ARGS, group_id=NOT_SET, household_id=uuid4())
HouseholdRepositoryGeneric(*BASE_ARGS, group_id=None, household_id=None)
HouseholdRepositoryGeneric(*BASE_ARGS, group_id=uuid4(), household_id=None)
HouseholdRepositoryGeneric(*BASE_ARGS, group_id=None, household_id=uuid4())
HouseholdRepositoryGeneric(*BASE_ARGS, group_id=uuid4(), household_id=uuid4())
@pytest.mark.parametrize("use_group_id", [True, False])
@pytest.mark.parametrize("use_household_id", [True, False])
def test_all_repositories_constructors(session: Session, use_group_id: bool, use_household_id: bool):
kwargs = {}
if use_group_id:
kwargs["group_id"] = uuid4()
if use_household_id:
kwargs["household_id"] = uuid4()
repositories = get_repositories(session, **kwargs)
for name, member in inspect.getmembers(repositories.__class__):
if not isinstance(member, cached_property):
continue
signature = inspect.signature(member.func)
repo_type = signature.return_annotation
try:
if not issubclass(repo_type, RepositoryGeneric):
continue
except TypeError:
continue
if issubclass(repo_type, HouseholdRepositoryGeneric):
if not (use_group_id and use_household_id):
with pytest.raises(ValueError):
getattr(repositories, name)
else:
repo = getattr(repositories, name)
assert repo.group_id == kwargs["group_id"]
assert repo.household_id == kwargs["household_id"]
elif issubclass(repo_type, GroupRepositoryGeneric):
if not use_group_id:
with pytest.raises(ValueError):
getattr(repositories, name)
else:
repo = getattr(repositories, name)
assert repo.group_id == kwargs["group_id"]
assert repo.household_id is None
else:
repo = getattr(repositories, name)
assert repo.group_id is None
assert repo.household_id is None
def test_group_repositories_filter_by_group(session: Session):
unfiltered_repos = get_repositories(session, group_id=None, household_id=None)
group_1 = unfiltered_repos.groups.create({"name": random_string()})
group_2 = unfiltered_repos.groups.create({"name": random_string()})
group_1_repos = get_repositories(session, group_id=group_1.id, household_id=None)
group_2_repos = get_repositories(session, group_id=group_2.id, household_id=None)
food_1 = group_1_repos.ingredient_foods.create(
SaveIngredientFood(id=uuid4(), group_id=group_1.id, name=random_string())
)
food_2 = group_2_repos.ingredient_foods.create(
SaveIngredientFood(id=uuid4(), group_id=group_2.id, name=random_string())
)
# unfiltered_repos should find both foods
assert food_1 == unfiltered_repos.ingredient_foods.get_one(food_1.id)
assert food_2 == unfiltered_repos.ingredient_foods.get_one(food_2.id)
all_foods = unfiltered_repos.ingredient_foods.page_all(PaginationQuery(page=1, per_page=-1)).items
assert food_1 in all_foods
assert food_2 in all_foods
# group_repos should only find foods with the correct group_id
assert food_1 == group_1_repos.ingredient_foods.get_one(food_1.id)
assert group_1_repos.ingredient_foods.get_one(food_2.id) is None
assert [food_1] == group_1_repos.ingredient_foods.page_all(PaginationQuery(page=1, per_page=-1)).items
assert group_2_repos.ingredient_foods.get_one(food_1.id) is None
assert food_2 == group_2_repos.ingredient_foods.get_one(food_2.id)
assert [food_2] == group_2_repos.ingredient_foods.page_all(PaginationQuery(page=1, per_page=-1)).items
def test_household_repositories_filter_by_household(session: Session):
unfiltered_repos = get_repositories(session, group_id=None, household_id=None)
group = unfiltered_repos.groups.create({"name": random_string()})
group_repos = get_repositories(session, group_id=group.id, household_id=None)
household_1 = group_repos.households.create({"name": random_string(), "group_id": group.id})
household_2 = group_repos.households.create({"name": random_string(), "group_id": group.id})
household_1_repos = get_repositories(session, group_id=group.id, household_id=household_1.id)
household_2_repos = get_repositories(session, group_id=group.id, household_id=household_2.id)
webhook_1 = household_1_repos.webhooks.create(
SaveWebhook(group_id=group.id, household_id=household_1.id, scheduled_time=time.time())
)
webhook_2 = household_2_repos.webhooks.create(
SaveWebhook(group_id=group.id, household_id=household_2.id, scheduled_time=time.time())
)
# unfiltered_repos and group_repos should find both webhooks
for repos in [unfiltered_repos, group_repos]:
assert webhook_1 == repos.webhooks.get_one(webhook_1.id)
assert webhook_2 == repos.webhooks.get_one(webhook_2.id)
all_webhooks = repos.webhooks.page_all(PaginationQuery(page=1, per_page=-1)).items
assert webhook_1 in all_webhooks
assert webhook_2 in all_webhooks
# household_repos should only find webhooks with the correct household_id
assert webhook_1 == household_1_repos.webhooks.get_one(webhook_1.id)
assert household_1_repos.webhooks.get_one(webhook_2.id) is None
assert [webhook_1] == household_1_repos.webhooks.page_all(PaginationQuery(page=1, per_page=-1)).items
assert household_2_repos.webhooks.get_one(webhook_1.id) is None
assert webhook_2 == household_2_repos.webhooks.get_one(webhook_2.id)
assert [webhook_2] == household_2_repos.webhooks.page_all(PaginationQuery(page=1, per_page=-1)).items
# a different group's repos shouldn't find anything
other_group = unfiltered_repos.groups.create({"name": random_string()})
for household_id in [household_1.id, household_2.id]:
other_group_repos = get_repositories(session, group_id=other_group.id, household_id=household_id)
assert other_group_repos.webhooks.get_one(webhook_1.id) is None
assert other_group_repos.webhooks.get_one(webhook_2.id) is None
assert other_group_repos.webhooks.page_all(PaginationQuery(page=1, per_page=-1)).items == []
def test_recipe_repo_filter_by_household_with_proxy(session: Session):
unfiltered_repos = get_repositories(session, group_id=None, household_id=None)
group = unfiltered_repos.groups.create({"name": random_string()})
group_repos = get_repositories(session, group_id=group.id, household_id=None)
household_1 = group_repos.households.create({"name": random_string(), "group_id": group.id})
household_2 = group_repos.households.create({"name": random_string(), "group_id": group.id})
user_1 = group_repos.users.create(
{
"username": random_string(),
"email": random_email(),
"group": group.name,
"household": household_1.name,
"full_name": random_string(),
"password": random_string(),
"admin": False,
}
)
user_2 = group_repos.users.create(
{
"username": random_string(),
"email": random_email(),
"group": group.name,
"household": household_2.name,
"full_name": random_string(),
"password": random_string(),
"admin": False,
}
)
household_1_repos = get_repositories(session, group_id=group.id, household_id=household_1.id)
household_2_repos = get_repositories(session, group_id=group.id, household_id=household_2.id)
recipe_1 = household_1_repos.recipes.create(
Recipe(
user_id=user_1.id,
group_id=group.id,
name=random_string(),
)
)
recipe_2 = household_2_repos.recipes.create(
Recipe(
user_id=user_2.id,
group_id=group.id,
name=random_string(),
)
)
assert recipe_1.id and recipe_2.id
assert household_1_repos.recipes.get_one(recipe_1.slug) == recipe_1
assert household_1_repos.recipes.get_one(recipe_2.slug) is None
result = household_1_repos.recipes.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(result) == 1
assert result[0].id == recipe_1.id
assert household_2_repos.recipes.get_one(recipe_1.slug) is None
assert household_2_repos.recipes.get_one(recipe_2.slug) == recipe_2
result = household_2_repos.recipes.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(result) == 1
assert result[0].id == recipe_2.id
def test_generic_repo_filter_by_household_with_proxy(session: Session):
unfiltered_repos = get_repositories(session, group_id=None, household_id=None)
group = unfiltered_repos.groups.create({"name": random_string()})
group_repos = get_repositories(session, group_id=group.id, household_id=None)
household_1 = group_repos.households.create({"name": random_string(), "group_id": group.id})
household_2 = group_repos.households.create({"name": random_string(), "group_id": group.id})
user_1 = group_repos.users.create(
{
"username": random_string(),
"email": random_email(),
"group": group.name,
"household": household_1.name,
"full_name": random_string(),
"password": random_string(),
"admin": False,
}
)
user_2 = group_repos.users.create(
{
"username": random_string(),
"email": random_email(),
"group": group.name,
"household": household_2.name,
"full_name": random_string(),
"password": random_string(),
"admin": False,
}
)
household_1_repos = get_repositories(session, group_id=group.id, household_id=household_1.id)
household_2_repos = get_repositories(session, group_id=group.id, household_id=household_2.id)
shopping_list_service_1 = ShoppingListService(household_1_repos)
shopping_list_service_2 = ShoppingListService(household_2_repos)
shopping_list_1 = shopping_list_service_1.create_one_list(ShoppingListCreate(name=random_string()), user_1.id)
shopping_list_2 = shopping_list_service_2.create_one_list(ShoppingListCreate(name=random_string()), user_2.id)
assert household_1_repos.group_shopping_lists.get_one(shopping_list_1.id) == shopping_list_1
assert household_1_repos.group_shopping_lists.get_one(shopping_list_2.id) is None
result = household_1_repos.group_shopping_lists.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(result) == 1
assert result[0].id == shopping_list_1.id
assert household_2_repos.group_shopping_lists.get_one(shopping_list_1.id) is None
assert household_2_repos.group_shopping_lists.get_one(shopping_list_2.id) == shopping_list_2
result = household_2_repos.group_shopping_lists.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(result) == 1
assert result[0].id == shopping_list_2.id
def test_changing_user_changes_household(session: Session):
unfiltered_repos = get_repositories(session, group_id=None, household_id=None)
group = unfiltered_repos.groups.create({"name": random_string()})
group_repos = get_repositories(session, group_id=group.id, household_id=None)
household_1 = group_repos.households.create({"name": random_string(), "group_id": group.id})
household_2 = group_repos.households.create({"name": random_string(), "group_id": group.id})
user_1 = group_repos.users.create(
{
"username": random_string(),
"email": random_email(),
"group": group.name,
"household": household_1.name,
"full_name": random_string(),
"password": random_string(),
"admin": False,
}
)
user_2 = group_repos.users.create(
{
"username": random_string(),
"email": random_email(),
"group": group.name,
"household": household_2.name,
"full_name": random_string(),
"password": random_string(),
"admin": False,
}
)
household_1_repos = get_repositories(session, group_id=group.id, household_id=household_1.id)
household_2_repos = get_repositories(session, group_id=group.id, household_id=household_2.id)
# create shopping list with user_1/household_1
shopping_list = ShoppingListService(household_1_repos).create_one_list(
ShoppingListCreate(name=random_string()), user_1.id
)
# only household_1_repos should find the list
response = household_1_repos.group_shopping_lists.get_one(shopping_list.id)
assert response
assert response.user_id == user_1.id
response = household_2_repos.group_shopping_lists.get_one(shopping_list.id)
assert response is None
items = household_1_repos.group_shopping_lists.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(items) == 1
assert items[0].id == shopping_list.id
assert household_2_repos.group_shopping_lists.page_all(PaginationQuery(page=1, per_page=-1)).items == []
# update shopping list to user_2/household_2 using household_1_repos
shopping_list.user_id = user_2.id
household_1_repos.group_shopping_lists.update(shopping_list.id, shopping_list)
# now only household_2_repos should find the list
response = household_1_repos.group_shopping_lists.get_one(shopping_list.id)
assert response is None
response = household_2_repos.group_shopping_lists.get_one(shopping_list.id)
assert response
assert response.user_id == user_2.id
items = household_2_repos.group_shopping_lists.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(items) == 1
assert items[0].id == shopping_list.id
assert household_1_repos.group_shopping_lists.page_all(PaginationQuery(page=1, per_page=-1)).items == []

View file

@ -3,13 +3,12 @@ from uuid import UUID
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe import Recipe
from tests.utils import api_routes, random_string
from tests.utils.fixture_schemas import TestUser
@dataclass(slots=True)
@dataclass()
class SimpleCase:
value: str
is_valid: bool
@ -41,8 +40,10 @@ def test_validators_email(api_client: TestClient, unique_user: TestUser):
assert response_data["valid"] == user.is_valid
def test_validators_group_name(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
def test_validators_group_name(api_client: TestClient, unique_user: TestUser):
database = unique_user.repos
group = database.groups.get_one(unique_user.group_id)
assert group
groups = [
SimpleCase(value=group.name, is_valid=False),
@ -65,7 +66,7 @@ class RecipeValidators:
def test_validators_recipe(api_client: TestClient, random_recipe: Recipe):
recipes = [
RecipeValidators(name=random_recipe.name, group=random_recipe.group_id, is_valid=False),
RecipeValidators(name=random_recipe.name or "", group=random_recipe.group_id, is_valid=False),
RecipeValidators(name=random_string(), group=random_recipe.group_id, is_valid=True),
RecipeValidators(name=random_string(), group=random_recipe.group_id, is_valid=True),
]

View file

@ -3,6 +3,7 @@ from fastapi.testclient import TestClient
from mealie.schema.group.group_preferences import UpdateGroupPreferences
from tests.utils import api_routes
from tests.utils.assertion_helpers import assert_ignore_keys
from tests.utils.factories import random_bool
from tests.utils.fixture_schemas import TestUser
@ -12,8 +13,7 @@ def test_get_preferences(api_client: TestClient, unique_user: TestUser) -> None:
preferences = response.json()
assert preferences["recipePublic"] in {True, False}
assert preferences["recipeShowNutrition"] in {True, False}
assert preferences["privateGroup"] in {True, False}
def test_preferences_in_group(api_client: TestClient, unique_user: TestUser) -> None:
@ -26,12 +26,11 @@ def test_preferences_in_group(api_client: TestClient, unique_user: TestUser) ->
assert group["preferences"] is not None
# Spot Check
assert group["preferences"]["recipePublic"] in {True, False}
assert group["preferences"]["recipeShowNutrition"] in {True, False}
assert group["preferences"]["privateGroup"] in {True, False}
def test_update_preferences(api_client: TestClient, unique_user: TestUser) -> None:
new_data = UpdateGroupPreferences(recipe_public=False, recipe_show_nutrition=True)
new_data = UpdateGroupPreferences(private_group=random_bool())
response = api_client.put(api_routes.groups_preferences, json=new_data.model_dump(), headers=unique_user.token)
@ -40,7 +39,6 @@ def test_update_preferences(api_client: TestClient, unique_user: TestUser) -> No
preferences = response.json()
assert preferences is not None
assert preferences["recipePublic"] is False
assert preferences["recipeShowNutrition"] is True
assert preferences["privateGroup"] == new_data.private_group
assert_ignore_keys(new_data.model_dump(by_alias=True), preferences, ["id", "groupId"])

View file

@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.response.pagination import PaginationQuery
from tests.utils import api_routes
from tests.utils.fixture_schemas import TestUser
@ -11,46 +11,49 @@ def test_seed_invalid_locale(api_client: TestClient, unique_user: TestUser):
assert resp.status_code == 422
def test_seed_foods(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
def test_seed_foods(api_client: TestClient, unique_user: TestUser):
CREATED_FOODS = 220
database = unique_user.repos
# Check that the foods was created
foods = database.ingredient_foods.by_group(unique_user.group_id).get_all()
foods = database.ingredient_foods.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(foods) == 0
resp = api_client.post(api_routes.groups_seeders_foods, json={"locale": "en-US"}, headers=unique_user.token)
assert resp.status_code == 200
# Check that the foods was created
foods = database.ingredient_foods.by_group(unique_user.group_id).get_all()
foods = database.ingredient_foods.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(foods) == CREATED_FOODS
def test_seed_units(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
def test_seed_units(api_client: TestClient, unique_user: TestUser):
CREATED_UNITS = 23
database = unique_user.repos
# Check that the foods was created
units = database.ingredient_units.by_group(unique_user.group_id).get_all()
units = database.ingredient_units.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(units) == 0
resp = api_client.post(api_routes.groups_seeders_units, json={"locale": "en-US"}, headers=unique_user.token)
assert resp.status_code == 200
# Check that the foods was created
units = database.ingredient_units.by_group(unique_user.group_id).get_all()
units = database.ingredient_units.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(units) == CREATED_UNITS
def test_seed_labels(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
def test_seed_labels(api_client: TestClient, unique_user: TestUser):
CREATED_LABELS = 21
database = unique_user.repos
# Check that the foods was created
labels = database.group_multi_purpose_labels.by_group(unique_user.group_id).get_all()
labels = database.group_multi_purpose_labels.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(labels) == 0
resp = api_client.post(api_routes.groups_seeders_labels, json={"locale": "en-US"}, headers=unique_user.token)
assert resp.status_code == 200
# Check that the foods was created
labels = database.group_multi_purpose_labels.by_group(unique_user.group_id).get_all()
labels = database.group_multi_purpose_labels.page_all(PaginationQuery(page=1, per_page=-1)).items
assert len(labels) == CREATED_LABELS

View file

@ -0,0 +1,62 @@
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from tests.utils import api_routes, random_int, random_string
from tests.utils.fixture_schemas import TestUser
def test_get_group_members(api_client: TestClient, unique_user: TestUser, h2_user: TestUser):
response = api_client.get(api_routes.groups_members, headers=unique_user.token)
assert response.status_code == 200
members = response.json()
assert len(members) >= 2
all_ids = [x["id"] for x in members]
assert str(unique_user.user_id) in all_ids
assert str(h2_user.user_id) in all_ids
def test_get_group_members_filtered(api_client: TestClient, unique_user: TestUser, h2_user: TestUser):
response = api_client.get(
api_routes.groups_members, params={"householdId": h2_user.household_id}, headers=unique_user.token
)
assert response.status_code == 200
members = response.json()
assert len(members) >= 1
all_ids = [x["id"] for x in members]
assert str(unique_user.user_id) not in all_ids
assert str(h2_user.user_id) in all_ids
def test_get_households(api_client: TestClient, admin_user: TestUser):
households = [admin_user.repos.households.create({"name": random_string()}) for _ in range(5)]
response = api_client.get(api_routes.groups_households, headers=admin_user.token)
response_ids = [item["id"] for item in response.json()]
for household in households:
assert str(household.id) in response_ids
def test_get_households_filtered(unfiltered_database: AllRepositories, api_client: TestClient, admin_user: TestUser):
group_1_id = admin_user.group_id
group_2_id = str(unfiltered_database.groups.create({"name": random_string()}).id)
group_1_households = [
unfiltered_database.households.create({"name": random_string(), "group_id": group_1_id})
for _ in range(random_int(2, 5))
]
group_2_households = [
unfiltered_database.households.create({"name": random_string(), "group_id": group_2_id})
for _ in range(random_int(2, 5))
]
response = api_client.get(api_routes.groups_households, headers=admin_user.token)
response_ids = [item["id"] for item in response.json()]
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

View file

@ -6,7 +6,6 @@ import pytest
from fastapi.testclient import TestClient
from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.cookbook.cookbook import ReadCookBook, SaveCookBook
from tests import utils
from tests.utils import api_routes
@ -14,7 +13,7 @@ from tests.utils.factories import random_string
from tests.utils.fixture_schemas import TestUser
def get_page_data(group_id: UUID | str):
def get_page_data(group_id: UUID | str, household_id: UUID4 | str):
name_and_slug = random_string(10)
return {
"name": name_and_slug,
@ -23,6 +22,7 @@ def get_page_data(group_id: UUID | str):
"position": 0,
"categories": [],
"group_id": str(group_id),
"household_id": str(household_id),
}
@ -35,11 +35,13 @@ class TestCookbook:
@pytest.fixture(scope="function")
def cookbooks(database: AllRepositories, unique_user: TestUser) -> list[TestCookbook]:
def cookbooks(unique_user: TestUser) -> 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)))
cb = database.cookbooks.create(SaveCookBook(**get_page_data(unique_user.group_id, unique_user.household_id)))
data.append(cb)
yield_data.append(TestCookbook(id=cb.id, slug=cb.slug, name=cb.name, data=cb.model_dump()))
@ -53,14 +55,14 @@ def cookbooks(database: AllRepositories, unique_user: TestUser) -> list[TestCook
def test_create_cookbook(api_client: TestClient, unique_user: TestUser):
page_data = get_page_data(unique_user.group_id)
response = api_client.post(api_routes.groups_cookbooks, json=page_data, headers=unique_user.token)
page_data = get_page_data(unique_user.group_id, unique_user.household_id)
response = api_client.post(api_routes.households_cookbooks, json=page_data, headers=unique_user.token)
assert response.status_code == 201
def test_read_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
sample = random.choice(cookbooks)
response = api_client.get(api_routes.groups_cookbooks_item_id(sample.id), headers=unique_user.token)
response = api_client.get(api_routes.households_cookbooks_item_id(sample.id), headers=unique_user.token)
assert response.status_code == 200
page_data = response.json()
@ -74,16 +76,16 @@ def test_read_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks:
def test_update_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
cookbook = random.choice(cookbooks)
update_data = get_page_data(unique_user.group_id)
update_data = get_page_data(unique_user.group_id, unique_user.household_id)
update_data["name"] = random_string(10)
response = api_client.put(
api_routes.groups_cookbooks_item_id(cookbook.id), json=update_data, headers=unique_user.token
api_routes.households_cookbooks_item_id(cookbook.id), json=update_data, headers=unique_user.token
)
assert response.status_code == 200
response = api_client.get(api_routes.groups_cookbooks_item_id(cookbook.id), headers=unique_user.token)
response = api_client.get(api_routes.households_cookbooks_item_id(cookbook.id), headers=unique_user.token)
assert response.status_code == 200
page_data = response.json()
@ -99,10 +101,12 @@ def test_update_cookbooks_many(api_client: TestClient, unique_user: TestUser, co
page["position"] = x
page["group_id"] = str(unique_user.group_id)
response = api_client.put(api_routes.groups_cookbooks, json=utils.jsonify(reverse_order), headers=unique_user.token)
response = api_client.put(
api_routes.households_cookbooks, json=utils.jsonify(reverse_order), headers=unique_user.token
)
assert response.status_code == 200
response = api_client.get(api_routes.groups_cookbooks, headers=unique_user.token)
response = api_client.get(api_routes.households_cookbooks, headers=unique_user.token)
assert response.status_code == 200
known_ids = [x.id for x in cookbooks]
@ -115,9 +119,9 @@ def test_update_cookbooks_many(api_client: TestClient, unique_user: TestUser, co
def test_delete_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
sample = random.choice(cookbooks)
response = api_client.delete(api_routes.groups_cookbooks_item_id(sample.id), headers=unique_user.token)
response = api_client.delete(api_routes.households_cookbooks_item_id(sample.id), headers=unique_user.token)
assert response.status_code == 200
response = api_client.get(api_routes.groups_cookbooks_item_id(sample.slug), headers=unique_user.token)
response = api_client.get(api_routes.households_cookbooks_item_id(sample.slug), headers=unique_user.token)
assert response.status_code == 404

View file

@ -9,7 +9,7 @@ from tests.utils.fixture_schemas import TestUser
@pytest.fixture(scope="function")
def invite(api_client: TestClient, unique_user: TestUser) -> None:
# Test Creation
r = api_client.post(api_routes.groups_invitations, json={"uses": 2}, headers=unique_user.token)
r = api_client.post(api_routes.households_invitations, json={"uses": 2}, headers=unique_user.token)
assert r.status_code == 201
invitation = r.json()
return invitation["token"]
@ -17,7 +17,7 @@ def invite(api_client: TestClient, unique_user: TestUser) -> None:
def test_get_all_invitation(api_client: TestClient, unique_user: TestUser, invite: str) -> None:
# Get All Invites
r = api_client.get(api_routes.groups_invitations, headers=unique_user.token)
r = api_client.get(api_routes.households_invitations, headers=unique_user.token)
assert r.status_code == 200
@ -27,13 +27,15 @@ def test_get_all_invitation(api_client: TestClient, unique_user: TestUser, invit
for item in items:
assert item["groupId"] == unique_user.group_id
assert item["householdId"] == unique_user.household_id
assert item["token"] == invite
def register_user(api_client, invite):
def register_user(api_client: TestClient, invite: str):
# Test User can Join Group
registration = user_registration_factory()
registration.group = ""
registration.household = ""
registration.group_token = invite
response = api_client.post(api_routes.users_register, json=registration.model_dump(by_alias=True))
@ -52,11 +54,12 @@ def test_group_invitation_link(api_client: TestClient, unique_user: TestUser, in
token = r.json().get("access_token")
assert token is not None
# Check user Group is Same
# Check user Group and Household match
r = api_client.get(api_routes.users_self, headers={"Authorization": f"Bearer {token}"})
assert r.status_code == 200
assert r.json()["groupId"] == unique_user.group_id
assert r.json()["householdId"] == unique_user.household_id
def test_group_invitation_delete_after_uses(api_client: TestClient, invite: str) -> None:

View file

@ -9,7 +9,9 @@ from tests.utils.fixture_schemas import TestUser
def route_all_slice(page: int, perPage: int, start_date: str, end_date: str):
return f"{api_routes.groups_mealplans}?page={page}&perPage={perPage}&start_date={start_date}&end_date={end_date}"
return (
f"{api_routes.households_mealplans}?page={page}&perPage={perPage}&start_date={start_date}&end_date={end_date}"
)
def test_create_mealplan_no_recipe(api_client: TestClient, unique_user: TestUser):
@ -20,7 +22,7 @@ def test_create_mealplan_no_recipe(api_client: TestClient, unique_user: TestUser
).model_dump()
new_plan["date"] = datetime.now(timezone.utc).date().strftime("%Y-%m-%d")
response = api_client.post(api_routes.groups_mealplans, json=new_plan, headers=unique_user.token)
response = api_client.post(api_routes.households_mealplans, json=new_plan, headers=unique_user.token)
assert response.status_code == 201
@ -44,7 +46,7 @@ def test_create_mealplan_with_recipe(api_client: TestClient, unique_user: TestUs
new_plan["date"] = datetime.now(timezone.utc).date().strftime("%Y-%m-%d")
new_plan["recipeId"] = str(recipe_id)
response = api_client.post(api_routes.groups_mealplans, json=new_plan, headers=unique_user.token)
response = api_client.post(api_routes.households_mealplans, json=new_plan, headers=unique_user.token)
response_json = response.json()
assert response.status_code == 201
@ -61,7 +63,7 @@ def test_crud_mealplan(api_client: TestClient, unique_user: TestUser):
# Create
new_plan["date"] = datetime.now(timezone.utc).date().strftime("%Y-%m-%d")
response = api_client.post(api_routes.groups_mealplans, json=new_plan, headers=unique_user.token)
response = api_client.post(api_routes.households_mealplans, json=new_plan, headers=unique_user.token)
response_json = response.json()
assert response.status_code == 201
plan_id = response_json["id"]
@ -71,7 +73,7 @@ def test_crud_mealplan(api_client: TestClient, unique_user: TestUser):
response_json["text"] = random_string()
response = api_client.put(
api_routes.groups_mealplans_item_id(plan_id), headers=unique_user.token, json=response_json
api_routes.households_mealplans_item_id(plan_id), headers=unique_user.token, json=response_json
)
assert response.status_code == 200
@ -80,11 +82,11 @@ def test_crud_mealplan(api_client: TestClient, unique_user: TestUser):
assert response.json()["text"] == response_json["text"]
# Delete
response = api_client.delete(api_routes.groups_mealplans_item_id(plan_id), headers=unique_user.token)
response = api_client.delete(api_routes.households_mealplans_item_id(plan_id), headers=unique_user.token)
assert response.status_code == 200
response = api_client.get(api_routes.groups_mealplans_item_id(plan_id), headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans_item_id(plan_id), headers=unique_user.token)
assert response.status_code == 404
@ -98,10 +100,12 @@ def test_get_all_mealplans(api_client: TestClient, unique_user: TestUser):
).model_dump()
new_plan["date"] = datetime.now(timezone.utc).date().strftime("%Y-%m-%d")
response = api_client.post(api_routes.groups_mealplans, json=new_plan, headers=unique_user.token)
response = api_client.post(api_routes.households_mealplans, json=new_plan, headers=unique_user.token)
assert response.status_code == 201
response = api_client.get(api_routes.groups_mealplans, headers=unique_user.token, params={"page": 1, "perPage": -1})
response = api_client.get(
api_routes.households_mealplans, headers=unique_user.token, params={"page": 1, "perPage": -1}
)
assert response.status_code == 200
assert len(response.json()["items"]) >= 3
@ -120,7 +124,7 @@ def test_get_slice_mealplans(api_client: TestClient, unique_user: TestUser):
# Add the meal plans to the database
for meal_plan in meal_plans:
meal_plan["date"] = meal_plan["date"].strftime("%Y-%m-%d")
response = api_client.post(api_routes.groups_mealplans, json=meal_plan, headers=unique_user.token)
response = api_client.post(api_routes.households_mealplans, json=meal_plan, headers=unique_user.token)
assert response.status_code == 201
# Get meal slice of meal plans from database
@ -151,11 +155,11 @@ def test_get_mealplan_today(api_client: TestClient, unique_user: TestUser):
# Add the meal plans to the database
for meal_plan in test_meal_plans:
meal_plan["date"] = meal_plan["date"].strftime("%Y-%m-%d")
response = api_client.post(api_routes.groups_mealplans, json=meal_plan, headers=unique_user.token)
response = api_client.post(api_routes.households_mealplans, json=meal_plan, headers=unique_user.token)
assert response.status_code == 201
# Get meal plan for today
response = api_client.get(api_routes.groups_mealplans_today, headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans_today, headers=unique_user.token)
assert response.status_code == 200

View file

@ -3,8 +3,7 @@ from uuid import UUID
import pytest
from fastapi.testclient import TestClient
from mealie.repos.all_repositories import AllRepositories
from mealie.schema.meal_plan.plan_rules import PlanRulesOut, PlanRulesSave
from mealie.schema.meal_plan.plan_rules import PlanRulesOut
from mealie.schema.recipe.recipe import RecipeCategory
from mealie.schema.recipe.recipe_category import CategorySave
from tests import utils
@ -13,10 +12,8 @@ from tests.utils.fixture_schemas import TestUser
@pytest.fixture(scope="function")
def category(
database: AllRepositories,
unique_user: TestUser,
):
def category(unique_user: TestUser):
database = unique_user.repos
slug = utils.random_string(length=10)
model = database.categories.create(CategorySave(group_id=unique_user.group_id, slug=slug, name=slug))
@ -29,42 +26,45 @@ def category(
@pytest.fixture(scope="function")
def plan_rule(database: AllRepositories, unique_user: TestUser):
schema = PlanRulesSave(
group_id=unique_user.group_id,
day="monday",
entry_type="breakfast",
categories=[],
)
model = database.group_meal_plan_rules.create(schema)
yield model
try:
database.group_meal_plan_rules.delete(model.id)
except Exception:
pass
def test_group_mealplan_rules_create(
api_client: TestClient, unique_user: TestUser, category: RecipeCategory, database: AllRepositories
):
def plan_rule(api_client: TestClient, unique_user: TestUser):
payload = {
"groupId": unique_user.group_id,
"householdId": unique_user.household_id,
"day": "monday",
"entryType": "breakfast",
"categories": [],
}
response = api_client.post(
api_routes.households_mealplans_rules, json=utils.jsonify(payload), headers=unique_user.token
)
assert response.status_code == 201
plan_rule = PlanRulesOut.model_validate(response.json())
yield plan_rule
# cleanup
api_client.delete(api_routes.households_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
def test_group_mealplan_rules_create(api_client: TestClient, unique_user: TestUser, category: RecipeCategory):
database = unique_user.repos
payload = {
"groupId": unique_user.group_id,
"householdId": unique_user.household_id,
"day": "monday",
"entryType": "breakfast",
"categories": [category.model_dump()],
}
response = api_client.post(
api_routes.groups_mealplans_rules, json=utils.jsonify(payload), headers=unique_user.token
api_routes.households_mealplans_rules, json=utils.jsonify(payload), headers=unique_user.token
)
assert response.status_code == 201
# Validate the response data
response_data = response.json()
assert response_data["groupId"] == str(unique_user.group_id)
assert response_data["householdId"] == str(unique_user.household_id)
assert response_data["day"] == "monday"
assert response_data["entryType"] == "breakfast"
assert len(response_data["categories"]) == 1
@ -72,8 +72,10 @@ def test_group_mealplan_rules_create(
# Validate database entry
rule = database.group_meal_plan_rules.get_one(UUID(response_data["id"]))
assert rule
assert str(rule.group_id) == unique_user.group_id
assert str(rule.household_id) == unique_user.household_id
assert rule.day == "monday"
assert rule.entry_type == "breakfast"
assert len(rule.categories) == 1
@ -84,13 +86,14 @@ def test_group_mealplan_rules_create(
def test_group_mealplan_rules_read(api_client: TestClient, unique_user: TestUser, plan_rule: PlanRulesOut):
response = api_client.get(api_routes.groups_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
assert response.status_code == 200
# Validate the response data
response_data = response.json()
assert response_data["id"] == str(plan_rule.id)
assert response_data["groupId"] == str(unique_user.group_id)
assert response_data["householdId"] == str(unique_user.household_id)
assert response_data["day"] == "monday"
assert response_data["entryType"] == "breakfast"
assert len(response_data["categories"]) == 0
@ -99,12 +102,13 @@ def test_group_mealplan_rules_read(api_client: TestClient, unique_user: TestUser
def test_group_mealplan_rules_update(api_client: TestClient, unique_user: TestUser, plan_rule: PlanRulesOut):
payload = {
"groupId": unique_user.group_id,
"householdId": unique_user.household_id,
"day": "tuesday",
"entryType": "lunch",
}
response = api_client.put(
api_routes.groups_mealplans_rules_item_id(plan_rule.id), json=payload, headers=unique_user.token
api_routes.households_mealplans_rules_item_id(plan_rule.id), json=payload, headers=unique_user.token
)
assert response.status_code == 200
@ -112,16 +116,18 @@ def test_group_mealplan_rules_update(api_client: TestClient, unique_user: TestUs
response_data = response.json()
assert response_data["id"] == str(plan_rule.id)
assert response_data["groupId"] == str(unique_user.group_id)
assert response_data["householdId"] == str(unique_user.household_id)
assert response_data["day"] == "tuesday"
assert response_data["entryType"] == "lunch"
assert len(response_data["categories"]) == 0
def test_group_mealplan_rules_delete(
api_client: TestClient, unique_user: TestUser, plan_rule: PlanRulesOut, database: AllRepositories
):
response = api_client.delete(api_routes.groups_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
def test_group_mealplan_rules_delete(api_client: TestClient, unique_user: TestUser, plan_rule: PlanRulesOut):
response = api_client.get(api_routes.households_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
assert response.status_code == 200
# Validate no entry in database
assert database.group_meal_plan_rules.get_one(plan_rule.id) is None
response = api_client.delete(api_routes.households_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
assert response.status_code == 200
response = api_client.get(api_routes.households_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
assert response.status_code == 404

View file

@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
from mealie.schema.group.group_events import GroupEventNotifierCreate, GroupEventNotifierOptions
from mealie.schema.household.group_events import GroupEventNotifierCreate, GroupEventNotifierOptions
from mealie.services.event_bus_service.event_bus_listeners import AppriseEventListener
from mealie.services.event_bus_service.event_bus_service import Event
from mealie.services.event_bus_service.event_types import (
@ -59,7 +59,7 @@ def event_generator():
def test_create_notification(api_client: TestClient, unique_user: TestUser):
payload = notifier_generator()
response = api_client.post(api_routes.groups_events_notifications, json=payload, headers=unique_user.token)
response = api_client.post(api_routes.households_events_notifications, json=payload, headers=unique_user.token)
assert response.status_code == 201
payload_as_dict = response.json()
@ -72,13 +72,13 @@ def test_create_notification(api_client: TestClient, unique_user: TestUser):
# Cleanup
response = api_client.delete(
api_routes.groups_events_notifications_item_id(payload_as_dict["id"]), headers=unique_user.token
api_routes.households_events_notifications_item_id(payload_as_dict["id"]), headers=unique_user.token
)
def test_ensure_apprise_url_is_secret(api_client: TestClient, unique_user: TestUser):
payload = notifier_generator()
response = api_client.post(api_routes.groups_events_notifications, json=payload, headers=unique_user.token)
response = api_client.post(api_routes.households_events_notifications, json=payload, headers=unique_user.token)
assert response.status_code == 201
payload_as_dict = response.json()
@ -89,7 +89,7 @@ def test_ensure_apprise_url_is_secret(api_client: TestClient, unique_user: TestU
def test_update_apprise_notification(api_client: TestClient, unique_user: TestUser):
payload = notifier_generator()
response = api_client.post(api_routes.groups_events_notifications, json=payload, headers=unique_user.token)
response = api_client.post(api_routes.households_events_notifications, json=payload, headers=unique_user.token)
assert response.status_code == 201
update_payload = response.json()
@ -100,7 +100,7 @@ def test_update_apprise_notification(api_client: TestClient, unique_user: TestUs
update_payload["options"] = preferences_generator()
response = api_client.put(
api_routes.groups_events_notifications_item_id(update_payload["id"]),
api_routes.households_events_notifications_item_id(update_payload["id"]),
json=update_payload,
headers=unique_user.token,
)
@ -109,7 +109,7 @@ def test_update_apprise_notification(api_client: TestClient, unique_user: TestUs
# Re-Get The Item
response = api_client.get(
api_routes.groups_events_notifications_item_id(update_payload["id"]), headers=unique_user.token
api_routes.households_events_notifications_item_id(update_payload["id"]), headers=unique_user.token
)
assert response.status_code == 200
@ -122,24 +122,24 @@ def test_update_apprise_notification(api_client: TestClient, unique_user: TestUs
# Cleanup
response = api_client.delete(
api_routes.groups_events_notifications_item_id(update_payload["id"]), headers=unique_user.token
api_routes.households_events_notifications_item_id(update_payload["id"]), headers=unique_user.token
)
def test_delete_apprise_notification(api_client: TestClient, unique_user: TestUser):
payload = notifier_generator()
response = api_client.post(api_routes.groups_events_notifications, json=payload, headers=unique_user.token)
response = api_client.post(api_routes.households_events_notifications, json=payload, headers=unique_user.token)
assert response.status_code == 201
payload_as_dict = response.json()
response = api_client.delete(
api_routes.groups_events_notifications_item_id(payload_as_dict["id"]), headers=unique_user.token
api_routes.households_events_notifications_item_id(payload_as_dict["id"]), headers=unique_user.token
)
assert response.status_code == 204
response = api_client.get(
api_routes.groups_events_notifications_item_id(payload_as_dict["id"]), headers=unique_user.token
api_routes.households_events_notifications_item_id(payload_as_dict["id"]), headers=unique_user.token
)
assert response.status_code == 404

View file

@ -1,7 +1,7 @@
import pytest
from fastapi.testclient import TestClient
from mealie.schema.group.group_recipe_action import (
from mealie.schema.household.group_recipe_action import (
CreateGroupRecipeAction,
GroupRecipeActionOut,
GroupRecipeActionType,
@ -22,7 +22,7 @@ def new_link_action() -> CreateGroupRecipeAction:
def test_group_recipe_actions_create_one(api_client: TestClient, unique_user: TestUser):
action_in = new_link_action()
response = api_client.post(
api_routes.groups_recipe_actions,
api_routes.households_recipe_actions,
json=action_in.model_dump(),
headers=unique_user.token,
)
@ -31,6 +31,7 @@ def test_group_recipe_actions_create_one(api_client: TestClient, unique_user: Te
action_out = GroupRecipeActionOut(**data)
assert action_out.id
assert str(action_out.group_id) == unique_user.group_id
assert str(action_out.household_id) == unique_user.household_id
assert action_out.action_type == action_in.action_type
assert action_out.title == action_in.title
assert action_out.url == action_in.url
@ -40,14 +41,14 @@ def test_group_recipe_actions_get_all(api_client: TestClient, unique_user: TestU
expected_ids: set[str] = set()
for _ in range(random_int(3, 5)):
response = api_client.post(
api_routes.groups_recipe_actions,
api_routes.households_recipe_actions,
json=new_link_action().model_dump(),
headers=unique_user.token,
)
data = assert_deserialize(response, 201)
expected_ids.add(data["id"])
response = api_client.get(api_routes.groups_recipe_actions, headers=unique_user.token)
response = api_client.get(api_routes.households_recipe_actions, headers=unique_user.token)
data = assert_deserialize(response, 200)
fetched_ids = {item["id"] for item in data["items"]}
for expected_id in expected_ids:
@ -60,7 +61,7 @@ def test_group_recipe_actions_get_one(
):
action_in = new_link_action()
response = api_client.post(
api_routes.groups_recipe_actions,
api_routes.households_recipe_actions,
json=action_in.model_dump(),
headers=unique_user.token,
)
@ -73,7 +74,7 @@ def test_group_recipe_actions_get_one(
fetch_user = g2_user
response = api_client.get(
api_routes.groups_recipe_actions_item_id(expected_action_out.id),
api_routes.households_recipe_actions_item_id(expected_action_out.id),
headers=fetch_user.token,
)
if not is_own_group:
@ -88,7 +89,7 @@ def test_group_recipe_actions_get_one(
def test_group_recipe_actions_update_one(api_client: TestClient, unique_user: TestUser):
action_in = new_link_action()
response = api_client.post(
api_routes.groups_recipe_actions,
api_routes.households_recipe_actions,
json=action_in.model_dump(),
headers=unique_user.token,
)
@ -98,7 +99,7 @@ def test_group_recipe_actions_update_one(api_client: TestClient, unique_user: Te
new_title = random_string()
data["title"] = new_title
response = api_client.put(
api_routes.groups_recipe_actions_item_id(action_id),
api_routes.households_recipe_actions_item_id(action_id),
json=data,
headers=unique_user.token,
)
@ -111,15 +112,15 @@ def test_group_recipe_actions_update_one(api_client: TestClient, unique_user: Te
def test_group_recipe_actions_delete_one(api_client: TestClient, unique_user: TestUser):
action_in = new_link_action()
response = api_client.post(
api_routes.groups_recipe_actions,
api_routes.households_recipe_actions,
json=action_in.model_dump(),
headers=unique_user.token,
)
data = assert_deserialize(response, 201)
action_id = data["id"]
response = api_client.delete(api_routes.groups_recipe_actions_item_id(action_id), headers=unique_user.token)
response = api_client.delete(api_routes.households_recipe_actions_item_id(action_id), headers=unique_user.token)
assert response.status_code == 200
response = api_client.get(api_routes.groups_recipe_actions_item_id(action_id), headers=unique_user.token)
response = api_client.get(api_routes.households_recipe_actions_item_id(action_id), headers=unique_user.token)
assert response.status_code == 404

View file

@ -7,7 +7,7 @@ from fastapi.testclient import TestClient
from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.group.group_shopping_list import ShoppingListItemOut, ShoppingListOut
from mealie.schema.household.group_shopping_list import ShoppingListItemOut, ShoppingListOut
from mealie.schema.recipe.recipe_ingredient import SaveIngredientFood
from tests import utils
from tests.utils import api_routes
@ -42,14 +42,14 @@ def test_shopping_list_items_create_one(
) -> None:
item = create_item(shopping_list.id)
response = api_client.post(api_routes.groups_shopping_items, json=item, headers=unique_user.token)
response = api_client.post(api_routes.households_shopping_items, json=item, headers=unique_user.token)
as_json = utils.assert_deserialize(response, 201)
assert len(as_json["createdItems"]) == 1
# Test Item is Getable
created_item_id = as_json["createdItems"][0]["id"]
response = api_client.get(
api_routes.groups_shopping_items_item_id(created_item_id),
api_routes.households_shopping_items_item_id(created_item_id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -59,7 +59,7 @@ def test_shopping_list_items_create_one(
# Test Item In List
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
response_list = utils.assert_deserialize(response, 200)
@ -76,7 +76,7 @@ def test_shopping_list_items_create_many(
items = [create_item(shopping_list.id) for _ in range(10)]
response = api_client.post(
api_routes.groups_shopping_items_create_bulk,
api_routes.households_shopping_items_create_bulk,
json=items,
headers=unique_user.token,
)
@ -88,7 +88,7 @@ def test_shopping_list_items_create_many(
# test items in list
created_item_ids = [item["id"] for item in as_json["createdItems"]]
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -107,15 +107,13 @@ def test_shopping_list_items_create_many(
def test_shopping_list_items_auto_assign_label_with_food_without_label(
api_client: TestClient,
unique_user: TestUser,
shopping_list: ShoppingListOut,
database: AllRepositories,
api_client: TestClient, unique_user: TestUser, shopping_list: ShoppingListOut
):
database = unique_user.repos
food = database.ingredient_foods.create(SaveIngredientFood(name=random_string(10), group_id=unique_user.group_id))
item = create_item(shopping_list.id, food_id=str(food.id))
response = api_client.post(api_routes.groups_shopping_items, json=item, headers=unique_user.token)
response = api_client.post(api_routes.households_shopping_items, json=item, headers=unique_user.token)
as_json = utils.assert_deserialize(response, 201)
assert len(as_json["createdItems"]) == 1
@ -125,18 +123,16 @@ def test_shopping_list_items_auto_assign_label_with_food_without_label(
def test_shopping_list_items_auto_assign_label_with_food_with_label(
api_client: TestClient,
unique_user: TestUser,
shopping_list: ShoppingListOut,
database: AllRepositories,
api_client: TestClient, unique_user: TestUser, shopping_list: ShoppingListOut
):
database = unique_user.repos
label = database.group_multi_purpose_labels.create({"name": random_string(10), "group_id": unique_user.group_id})
food = database.ingredient_foods.create(
SaveIngredientFood(name=random_string(10), group_id=unique_user.group_id, label_id=label.id)
)
item = create_item(shopping_list.id, food_id=str(food.id))
response = api_client.post(api_routes.groups_shopping_items, json=item, headers=unique_user.token)
response = api_client.post(api_routes.households_shopping_items, json=item, headers=unique_user.token)
as_json = utils.assert_deserialize(response, 201)
assert len(as_json["createdItems"]) == 1
@ -151,9 +147,9 @@ def test_shopping_list_items_auto_assign_label_with_food_search(
api_client: TestClient,
unique_user: TestUser,
shopping_list: ShoppingListOut,
database: AllRepositories,
use_fuzzy_name: bool,
):
database = unique_user.repos
label = database.group_multi_purpose_labels.create({"name": random_string(10), "group_id": unique_user.group_id})
food = database.ingredient_foods.create(
SaveIngredientFood(name=random_string(20), group_id=unique_user.group_id, label_id=label.id)
@ -165,7 +161,7 @@ def test_shopping_list_items_auto_assign_label_with_food_search(
name = name + random_string(2)
item["note"] = name
response = api_client.post(api_routes.groups_shopping_items, json=item, headers=unique_user.token)
response = api_client.post(api_routes.households_shopping_items, json=item, headers=unique_user.token)
as_json = utils.assert_deserialize(response, 201)
assert len(as_json["createdItems"]) == 1
@ -183,7 +179,7 @@ def test_shopping_list_items_get_one(
for _ in range(3):
item = random.choice(list_with_items.list_items)
response = api_client.get(api_routes.groups_shopping_items_item_id(item.id), headers=unique_user.token)
response = api_client.get(api_routes.households_shopping_items_item_id(item.id), headers=unique_user.token)
assert response.status_code == 200
@ -197,13 +193,13 @@ def test_shopping_list_items_get_all(
"perPage": -1,
"queryFilter": f"shopping_list_id={list_with_items.id}",
}
response = api_client.get(api_routes.groups_shopping_items, params=params, headers=unique_user.token)
response = api_client.get(api_routes.households_shopping_items, params=params, headers=unique_user.token)
pagination_json = utils.assert_deserialize(response, 200)
assert len(pagination_json["items"]) == len(list_with_items.list_items)
def test_shopping_list_items_get_one_404(api_client: TestClient, unique_user: TestUser) -> None:
response = api_client.get(api_routes.groups_shopping_items_item_id(uuid4()), headers=unique_user.token)
response = api_client.get(api_routes.households_shopping_items_item_id(uuid4()), headers=unique_user.token)
assert response.status_code == 404
@ -221,7 +217,7 @@ def test_shopping_list_items_update_one(
update_data["id"] = str(item.id)
response = api_client.put(
api_routes.groups_shopping_items_item_id(item.id),
api_routes.households_shopping_items_item_id(item.id),
json=update_data,
headers=unique_user.token,
)
@ -235,7 +231,7 @@ def test_shopping_list_items_update_one(
# make sure the list didn't change sizes
response = api_client.get(
api_routes.groups_shopping_lists_item_id(list_with_items.id),
api_routes.households_shopping_lists_item_id(list_with_items.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -251,7 +247,7 @@ def test_shopping_list_items_update_many(
item["quantity"] += 10
response = api_client.post(
api_routes.groups_shopping_items_create_bulk,
api_routes.households_shopping_items_create_bulk,
json=items,
headers=unique_user.token,
)
@ -265,7 +261,7 @@ def test_shopping_list_items_update_many(
item_quantity_map[update_item["id"]] = update_item["quantity"]
response = api_client.put(
api_routes.groups_shopping_items,
api_routes.households_shopping_items,
json=as_json["createdItems"],
headers=unique_user.token,
)
@ -277,7 +273,7 @@ def test_shopping_list_items_update_many(
# make sure the list didn't change sizes
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -306,12 +302,12 @@ def test_shopping_list_items_update_many_reorder(
# update list
# the default serializer fails on certain complex objects, so we use FastAPI's serializer first
as_dict = utils.jsonify(as_dict)
response = api_client.put(api_routes.groups_shopping_items, json=as_dict, headers=unique_user.token)
response = api_client.put(api_routes.households_shopping_items, json=as_dict, headers=unique_user.token)
assert response.status_code == 200
# retrieve list and check positions against list
response = api_client.get(
api_routes.groups_shopping_lists_item_id(list_with_items.id),
api_routes.households_shopping_lists_item_id(list_with_items.id),
headers=unique_user.token,
)
response_list = utils.assert_deserialize(response, 200)
@ -329,11 +325,11 @@ def test_shopping_list_items_delete_one(
item = random.choice(list_with_items.list_items)
# Delete Item
response = api_client.delete(api_routes.groups_shopping_items_item_id(item.id), headers=unique_user.token)
response = api_client.delete(api_routes.households_shopping_items_item_id(item.id), headers=unique_user.token)
assert response.status_code == 200
# Validate Get Item Returns 404
response = api_client.get(api_routes.groups_shopping_items_item_id(item.id), headers=unique_user.token)
response = api_client.get(api_routes.households_shopping_items_item_id(item.id), headers=unique_user.token)
assert response.status_code == 404
@ -353,7 +349,7 @@ def test_shopping_list_items_update_many_consolidates_common_items(
# update list
response = api_client.put(
api_routes.groups_shopping_items,
api_routes.households_shopping_items,
json=serialize_list_items(list_items),
headers=unique_user.token,
)
@ -361,7 +357,7 @@ def test_shopping_list_items_update_many_consolidates_common_items(
# retrieve list and check positions against list
response = api_client.get(
api_routes.groups_shopping_lists_item_id(list_with_items.id),
api_routes.households_shopping_lists_item_id(list_with_items.id),
headers=unique_user.token,
)
response_list = utils.assert_deserialize(response, 200)
@ -385,7 +381,7 @@ def test_shopping_list_items_add_mergeable(
merged_qty = sum([item["quantity"] for item in duplicate_items]) # type: ignore
response = api_client.post(
api_routes.groups_shopping_items_create_bulk,
api_routes.households_shopping_items_create_bulk,
json=items + duplicate_items,
headers=unique_user.token,
)
@ -409,7 +405,7 @@ def test_shopping_list_items_add_mergeable(
new_item["note"] = item_to_merge_into["note"]
updated_quantity = new_item["quantity"] + item_to_merge_into["quantity"]
response = api_client.post(api_routes.groups_shopping_items, json=new_item, headers=unique_user.token)
response = api_client.post(api_routes.households_shopping_items, json=new_item, headers=unique_user.token)
item_json = utils.assert_deserialize(response, 201)
# we should have received an updated item, not a created item
@ -421,7 +417,7 @@ def test_shopping_list_items_add_mergeable(
# fetch the list and make sure we have the correct number of items
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
list_json = utils.assert_deserialize(response, 200)
@ -439,7 +435,7 @@ def test_shopping_list_items_update_mergable(
item.note = list_with_items.list_items[i - 1].note
payload = utils.jsonify([item.model_dump() for item in list_with_items.list_items])
response = api_client.put(api_routes.groups_shopping_items, json=payload, headers=unique_user.token)
response = api_client.put(api_routes.households_shopping_items, json=payload, headers=unique_user.token)
as_json = utils.assert_deserialize(response, 200)
assert len(as_json["createdItems"]) == 0
@ -458,7 +454,7 @@ def test_shopping_list_items_update_mergable(
# confirm the number of items on the list matches
response = api_client.get(
api_routes.groups_shopping_lists_item_id(list_with_items.id),
api_routes.households_shopping_lists_item_id(list_with_items.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -474,7 +470,7 @@ def test_shopping_list_items_update_mergable(
merged_quantity = sum([item["quantity"] for item in items_to_merge])
payload = utils.jsonify(items_to_merge)
response = api_client.put(api_routes.groups_shopping_items, json=payload, headers=unique_user.token)
response = api_client.put(api_routes.households_shopping_items, json=payload, headers=unique_user.token)
as_json = utils.assert_deserialize(response, 200)
assert len(as_json["createdItems"]) == 0
@ -503,7 +499,7 @@ def test_shopping_list_items_checked_off(
checked_item.checked = True
response = api_client.put(
api_routes.groups_shopping_items_item_id(checked_item.id),
api_routes.households_shopping_items_item_id(checked_item.id),
json=utils.jsonify(checked_item.model_dump()),
headers=unique_user.token,
)
@ -517,7 +513,7 @@ def test_shopping_list_items_checked_off(
# get the reference item and make sure it didn't change
response = api_client.get(
api_routes.groups_shopping_items_item_id(reference_item.id),
api_routes.households_shopping_items_item_id(reference_item.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -531,7 +527,7 @@ def test_shopping_list_items_checked_off(
# rename an item to match another item and check both off, and make sure they are not merged
response = api_client.get(
api_routes.groups_shopping_lists_item_id(list_with_items.id),
api_routes.households_shopping_lists_item_id(list_with_items.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -543,7 +539,7 @@ def test_shopping_list_items_checked_off(
item_2.note = item_1.note
response = api_client.put(
api_routes.groups_shopping_items,
api_routes.households_shopping_items,
json=utils.jsonify([item_1.model_dump(), item_2.model_dump()]),
headers=unique_user.token,
)
@ -571,7 +567,7 @@ def test_shopping_list_items_with_zero_quantity(
item["quantity"] = 0
response = api_client.post(
api_routes.groups_shopping_items_create_bulk,
api_routes.households_shopping_items_create_bulk,
json=normal_items + zero_qty_items,
headers=unique_user.token,
)
@ -580,7 +576,7 @@ def test_shopping_list_items_with_zero_quantity(
# confirm the number of items on the list matches
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -594,7 +590,7 @@ def test_shopping_list_items_with_zero_quantity(
new_item_to_merge["note"] = target_item["note"]
response = api_client.post(
api_routes.groups_shopping_items,
api_routes.households_shopping_items,
json=new_item_to_merge,
headers=unique_user.token,
)
@ -610,7 +606,7 @@ def test_shopping_list_items_with_zero_quantity(
# confirm the number of items on the list stayed the same
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -622,7 +618,7 @@ def test_shopping_list_items_with_zero_quantity(
update_item_to_merge["quantity"] = 0
response = api_client.put(
api_routes.groups_shopping_items_item_id(update_item_to_merge["id"]),
api_routes.households_shopping_items_item_id(update_item_to_merge["id"]),
json=update_item_to_merge,
headers=unique_user.token,
)
@ -639,7 +635,7 @@ def test_shopping_list_items_with_zero_quantity(
# confirm the number of items on the list shrunk by one
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -659,7 +655,7 @@ def test_shopping_list_item_extras(
new_item_data = create_item(shopping_list.id)
new_item_data["extras"] = {key_str_1: val_str_1}
response = api_client.post(api_routes.groups_shopping_items, json=new_item_data, headers=unique_user.token)
response = api_client.post(api_routes.households_shopping_items, json=new_item_data, headers=unique_user.token)
collection = utils.assert_deserialize(response, 201)
item_as_json = collection["createdItems"][0]
@ -672,7 +668,7 @@ def test_shopping_list_item_extras(
item_as_json["extras"][key_str_2] = val_str_2
response = api_client.put(
api_routes.groups_shopping_items_item_id(item_as_json["id"]),
api_routes.households_shopping_items_item_id(item_as_json["id"]),
json=item_as_json,
headers=unique_user.token,
)

View file

@ -2,8 +2,7 @@ import random
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.group.group_shopping_list import (
from mealie.schema.household.group_shopping_list import (
ShoppingListItemOut,
ShoppingListItemUpdate,
ShoppingListItemUpdateBulk,
@ -18,7 +17,7 @@ from tests.utils.fixture_schemas import TestUser
def test_shopping_lists_get_all(api_client: TestClient, unique_user: TestUser, shopping_lists: list[ShoppingListOut]):
response = api_client.get(api_routes.groups_shopping_lists, headers=unique_user.token)
response = api_client.get(api_routes.households_shopping_lists, headers=unique_user.token)
assert response.status_code == 200
all_lists = response.json()["items"]
@ -35,11 +34,12 @@ def test_shopping_lists_create_one(api_client: TestClient, unique_user: TestUser
"name": random_string(10),
}
response = api_client.post(api_routes.groups_shopping_lists, json=payload, headers=unique_user.token)
response = api_client.post(api_routes.households_shopping_lists, json=payload, headers=unique_user.token)
response_list = utils.assert_deserialize(response, 201)
assert response_list["name"] == payload["name"]
assert response_list["groupId"] == str(unique_user.group_id)
assert response_list["householdId"] == str(unique_user.household_id)
assert response_list["userId"] == str(unique_user.user_id)
@ -47,7 +47,7 @@ def test_shopping_lists_get_one(api_client: TestClient, unique_user: TestUser, s
shopping_list = shopping_lists[0]
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
assert response.status_code == 200
@ -57,6 +57,7 @@ def test_shopping_lists_get_one(api_client: TestClient, unique_user: TestUser, s
assert response_list["id"] == str(shopping_list.id)
assert response_list["name"] == shopping_list.name
assert response_list["groupId"] == str(shopping_list.group_id)
assert response_list["householdId"] == str(unique_user.household_id)
assert response_list["userId"] == str(shopping_list.user_id)
@ -69,12 +70,13 @@ def test_shopping_lists_update_one(
"name": random_string(10),
"id": str(sample_list.id),
"groupId": str(sample_list.group_id),
"householdId": str(sample_list.household_id),
"userId": str(sample_list.user_id),
"listItems": [],
}
response = api_client.put(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
json=payload,
headers=unique_user.token,
)
@ -85,6 +87,7 @@ def test_shopping_lists_update_one(
assert response_list["id"] == str(sample_list.id)
assert response_list["name"] == payload["name"]
assert response_list["groupId"] == str(sample_list.group_id)
assert response_list["householdId"] == str(sample_list.household_id)
assert response_list["userId"] == str(sample_list.user_id)
@ -94,13 +97,13 @@ def test_shopping_lists_delete_one(
sample_list = random.choice(shopping_lists)
response = api_client.delete(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
assert response.status_code == 200
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
assert response.status_code == 404
@ -116,14 +119,14 @@ def test_shopping_lists_add_recipe(
recipe = recipe_ingredient_only
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
headers=unique_user.token,
)
assert response.status_code == 200
# get list and verify items against ingredients
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -144,13 +147,13 @@ def test_shopping_lists_add_recipe(
# add the recipe again and check the resulting items
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
headers=unique_user.token,
)
assert response.status_code == 200
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -206,12 +209,12 @@ def test_shopping_lists_add_one_with_zero_quantity(
# add the recipe to the list and make sure there are three list items
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(shopping_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(shopping_list.id, recipe.id),
headers=unique_user.token,
)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
shopping_list_out = ShoppingListOut.model_validate(utils.assert_deserialize(response, 200))
@ -239,14 +242,14 @@ def test_shopping_lists_add_custom_recipe_items(
recipe = recipe_ingredient_only
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
headers=unique_user.token,
)
assert response.status_code == 200
custom_items = random.sample(recipe_ingredient_only.recipe_ingredient, k=3)
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
headers=unique_user.token,
json={"recipeIngredients": utils.jsonify(custom_items)},
)
@ -254,7 +257,7 @@ def test_shopping_lists_add_custom_recipe_items(
# get list and verify items against ingredients
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -288,13 +291,13 @@ def test_shopping_list_ref_removes_itself(
# add a recipe to a list, then check off all recipe items and make sure the recipe ref is deleted
recipe = recipe_ingredient_only
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(shopping_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(shopping_list.id, recipe.id),
headers=unique_user.token,
)
utils.assert_deserialize(response, 200)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
shopping_list_json = utils.assert_deserialize(response, 200)
@ -305,14 +308,14 @@ def test_shopping_list_ref_removes_itself(
item["checked"] = True
response = api_client.put(
api_routes.groups_shopping_items,
api_routes.households_shopping_items,
json=shopping_list_json["listItems"],
headers=unique_user.token,
)
utils.assert_deserialize(response, 200)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
shopping_list_json = utils.assert_deserialize(response, 200)
@ -362,12 +365,12 @@ def test_shopping_lists_add_recipe_with_merge(
# add the recipe to the list and make sure there are only three list items, and their quantities/refs are correct
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(shopping_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(shopping_list.id, recipe.id),
headers=unique_user.token,
)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
shopping_list_out = ShoppingListOut.model_validate(utils.assert_deserialize(response, 200))
@ -410,12 +413,12 @@ def test_shopping_list_add_recipe_scale(
recipe = recipe_ingredient_only
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
headers=unique_user.token,
)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -440,13 +443,13 @@ def test_shopping_list_add_recipe_scale(
payload = {"recipeIncrementQuantity": recipe_scale}
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
headers=unique_user.token,
json=payload,
)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -475,7 +478,7 @@ def test_shopping_lists_remove_recipe(
# add two instances of the recipe
payload = {"recipeIncrementQuantity": 2}
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
json=payload,
headers=unique_user.token,
)
@ -483,14 +486,14 @@ def test_shopping_lists_remove_recipe(
# remove one instance of the recipe
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id_delete(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id_delete(sample_list.id, recipe.id),
headers=unique_user.token,
)
assert response.status_code == 200
# get list and verify items against ingredients
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -511,13 +514,13 @@ def test_shopping_lists_remove_recipe(
# remove the recipe again and check if the list is empty
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id_delete(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id_delete(sample_list.id, recipe.id),
headers=unique_user.token,
)
assert response.status_code == 200
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -536,13 +539,13 @@ def test_shopping_lists_remove_recipe_multiple_quantity(
for _ in range(3):
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
headers=unique_user.token,
)
assert response.status_code == 200
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -556,13 +559,13 @@ def test_shopping_lists_remove_recipe_multiple_quantity(
# Remove Recipe
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id_delete(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id_delete(sample_list.id, recipe.id),
headers=unique_user.token,
)
# Get List and Check for Ingredients
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -593,13 +596,13 @@ def test_shopping_list_remove_recipe_scale(
# first add a bunch of quantity to the list
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
headers=unique_user.token,
json=payload,
)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -621,13 +624,13 @@ def test_shopping_list_remove_recipe_scale(
# remove some of the recipes
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id_delete(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id_delete(sample_list.id, recipe.id),
headers=unique_user.token,
json=payload,
)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -658,13 +661,13 @@ def test_recipe_decrement_max(
# first add a bunch of quantity to the list
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(sample_list.id, recipe.id),
headers=unique_user.token,
json=payload,
)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -686,7 +689,7 @@ def test_recipe_decrement_max(
item_json["quantity"] += item_additional_quantity
response = api_client.put(
api_routes.groups_shopping_items_item_id(item_json["id"]),
api_routes.households_shopping_items_item_id(item_json["id"]),
json=item_json,
headers=unique_user.token,
)
@ -697,13 +700,13 @@ def test_recipe_decrement_max(
# now remove way too many instances of the recipe
payload = {"recipeDecrementQuantity": recipe_scale * 100}
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id_delete(sample_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id_delete(sample_list.id, recipe.id),
headers=unique_user.token,
json=payload,
)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(sample_list.id),
api_routes.households_shopping_lists_item_id(sample_list.id),
headers=unique_user.token,
)
as_json = utils.assert_deserialize(response, 200)
@ -754,19 +757,19 @@ def test_recipe_manipulation_with_zero_quantities(
# add the recipe to the list twice and make sure the quantity is still zero
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(shopping_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(shopping_list.id, recipe.id),
headers=unique_user.token,
)
utils.assert_deserialize(response, 200)
response = api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id(shopping_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id(shopping_list.id, recipe.id),
headers=unique_user.token,
)
utils.assert_deserialize(response, 200)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
updated_list = ShoppingListOut.model_validate_json(response.content)
@ -790,12 +793,12 @@ def test_recipe_manipulation_with_zero_quantities(
# remove the recipe once and make sure the item is still on the list
api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id_delete(shopping_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id_delete(shopping_list.id, recipe.id),
headers=unique_user.token,
)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
updated_list = ShoppingListOut.model_validate_json(response.content)
@ -819,12 +822,12 @@ def test_recipe_manipulation_with_zero_quantities(
# remove the recipe one more time and make sure the item is gone and the list is empty
api_client.post(
api_routes.groups_shopping_lists_item_id_recipe_recipe_id_delete(shopping_list.id, recipe.id),
api_routes.households_shopping_lists_item_id_recipe_recipe_id_delete(shopping_list.id, recipe.id),
headers=unique_user.token,
)
response = api_client.get(
api_routes.groups_shopping_lists_item_id(shopping_list.id),
api_routes.households_shopping_lists_item_id(shopping_list.id),
headers=unique_user.token,
)
updated_list = ShoppingListOut.model_validate_json(response.content)
@ -845,7 +848,7 @@ def test_shopping_list_extras(
new_list_data: dict = {"name": random_string()}
new_list_data["extras"] = {key_str_1: val_str_1}
response = api_client.post(api_routes.groups_shopping_lists, json=new_list_data, headers=unique_user.token)
response = api_client.post(api_routes.households_shopping_lists, json=new_list_data, headers=unique_user.token)
list_as_json = utils.assert_deserialize(response, 201)
# make sure the extra persists
@ -857,7 +860,7 @@ def test_shopping_list_extras(
list_as_json["extras"][key_str_2] = val_str_2
response = api_client.put(
api_routes.groups_shopping_lists_item_id(list_as_json["id"]),
api_routes.households_shopping_lists_item_id(list_as_json["id"]),
json=list_as_json,
headers=unique_user.token,
)
@ -872,60 +875,68 @@ def test_shopping_list_extras(
def test_modify_shopping_list_items_updates_shopping_list(
database: AllRepositories,
api_client: TestClient,
unique_user: TestUser,
shopping_lists: list[ShoppingListOut],
api_client: TestClient, unique_user: TestUser, shopping_lists: list[ShoppingListOut]
):
shopping_list = random.choice(shopping_lists)
last_update_at = shopping_list.update_at
last_update_at = shopping_list.updated_at
assert last_update_at
# Create
new_item_data = {"note": random_string(), "shopping_list_id": str(shopping_list.id)}
response = api_client.post(api_routes.groups_shopping_items, json=new_item_data, headers=unique_user.token)
response = api_client.post(api_routes.households_shopping_items, json=new_item_data, headers=unique_user.token)
data = assert_deserialize(response, 201)
updated_list = database.group_shopping_lists.get_one(shopping_list.id)
assert updated_list and updated_list.update_at
assert updated_list.update_at > last_update_at
last_update_at = updated_list.update_at
updated_list = ShoppingListOut.model_validate_json(
api_client.get(
api_routes.households_shopping_lists_item_id(shopping_list.id), headers=unique_user.token
).content
)
assert updated_list and updated_list.updated_at
assert updated_list.updated_at > last_update_at
last_update_at = updated_list.updated_at
list_item_id = data["createdItems"][0]["id"]
list_item = database.group_shopping_list_item.get_one(list_item_id)
list_item = ShoppingListItemOut.model_validate_json(
api_client.get(api_routes.households_shopping_items_item_id(list_item_id), headers=unique_user.token).content
)
assert list_item
# Update
list_item.note = random_string()
response = api_client.put(
api_routes.groups_shopping_items_item_id(list_item_id),
api_routes.households_shopping_items_item_id(list_item_id),
json=utils.jsonify(list_item.cast(ShoppingListItemUpdate).model_dump()),
headers=unique_user.token,
)
assert response.status_code == 200
updated_list = database.group_shopping_lists.get_one(shopping_list.id)
assert updated_list and updated_list.update_at
assert updated_list.update_at > last_update_at
last_update_at = updated_list.update_at
updated_list = ShoppingListOut.model_validate_json(
api_client.get(
api_routes.households_shopping_lists_item_id(shopping_list.id), headers=unique_user.token
).content
)
assert updated_list and updated_list.updated_at
assert updated_list.updated_at > last_update_at
last_update_at = updated_list.updated_at
# Delete
response = api_client.delete(
api_routes.groups_shopping_items_item_id(list_item_id),
api_routes.households_shopping_items_item_id(list_item_id),
headers=unique_user.token,
)
assert response.status_code == 200
updated_list = database.group_shopping_lists.get_one(shopping_list.id)
assert updated_list and updated_list.update_at
assert updated_list.update_at > last_update_at
updated_list = ShoppingListOut.model_validate_json(
api_client.get(
api_routes.households_shopping_lists_item_id(shopping_list.id), headers=unique_user.token
).content
)
assert updated_list and updated_list.updated_at
assert updated_list.updated_at > last_update_at
def test_bulk_modify_shopping_list_items_updates_shopping_list(
database: AllRepositories,
api_client: TestClient,
unique_user: TestUser,
shopping_lists: list[ShoppingListOut],
api_client: TestClient, unique_user: TestUser, shopping_lists: list[ShoppingListOut]
):
shopping_list = random.choice(shopping_lists)
last_update_at = shopping_list.update_at
last_update_at = shopping_list.updated_at
assert last_update_at
# Create
@ -933,40 +944,57 @@ def test_bulk_modify_shopping_list_items_updates_shopping_list(
{"note": random_string(), "shopping_list_id": str(shopping_list.id)} for _ in range(random_int(3, 5))
]
response = api_client.post(
api_routes.groups_shopping_items_create_bulk,
api_routes.households_shopping_items_create_bulk,
json=new_item_data,
headers=unique_user.token,
)
data = assert_deserialize(response, 201)
updated_list = database.group_shopping_lists.get_one(shopping_list.id)
assert updated_list and updated_list.update_at
assert updated_list.update_at > last_update_at
last_update_at = updated_list.update_at
updated_list = ShoppingListOut.model_validate_json(
api_client.get(
api_routes.households_shopping_lists_item_id(shopping_list.id), headers=unique_user.token
).content
)
assert updated_list and updated_list.updated_at
assert updated_list.updated_at > last_update_at
last_update_at = updated_list.updated_at
# Update
list_item_ids = [item["id"] for item in data["createdItems"]]
list_items: list[ShoppingListItemOut] = []
for list_item_id in list_item_ids:
list_item = database.group_shopping_list_item.get_one(list_item_id)
list_item = ShoppingListItemOut.model_validate_json(
api_client.get(
api_routes.households_shopping_items_item_id(list_item_id), headers=unique_user.token
).content
)
assert list_item
assert list_item
list_item.note = random_string()
list_items.append(list_item)
payload = [utils.jsonify(list_item.cast(ShoppingListItemUpdateBulk).model_dump()) for list_item in list_items]
response = api_client.put(api_routes.groups_shopping_items, json=payload, headers=unique_user.token)
response = api_client.put(api_routes.households_shopping_items, json=payload, headers=unique_user.token)
assert response.status_code == 200
updated_list = database.group_shopping_lists.get_one(shopping_list.id)
assert updated_list and updated_list.update_at
assert updated_list.update_at > last_update_at
last_update_at = updated_list.update_at
updated_list = ShoppingListOut.model_validate_json(
api_client.get(
api_routes.households_shopping_lists_item_id(shopping_list.id), headers=unique_user.token
).content
)
assert updated_list and updated_list.updated_at
assert updated_list.updated_at > last_update_at
last_update_at = updated_list.updated_at
# Delete
response = api_client.delete(
api_routes.groups_shopping_items,
api_routes.households_shopping_items,
params={"ids": [str(list_item.id) for list_item in list_items]},
headers=unique_user.token,
)
assert response.status_code == 200
updated_list = database.group_shopping_lists.get_one(shopping_list.id)
assert updated_list and updated_list.update_at
assert updated_list.update_at > last_update_at
updated_list = ShoppingListOut.model_validate_json(
api_client.get(
api_routes.households_shopping_lists_item_id(shopping_list.id), headers=unique_user.token
).content
)
assert updated_list and updated_list.updated_at
assert updated_list.updated_at > last_update_at

View file

@ -20,7 +20,7 @@ def webhook_data():
def test_create_webhook(api_client: TestClient, unique_user: TestUser, webhook_data):
response = api_client.post(
api_routes.groups_webhooks,
api_routes.households_webhooks,
json=jsonify(webhook_data),
headers=unique_user.token,
)
@ -29,13 +29,13 @@ def test_create_webhook(api_client: TestClient, unique_user: TestUser, webhook_d
def test_read_webhook(api_client: TestClient, unique_user: TestUser, webhook_data):
response = api_client.post(
api_routes.groups_webhooks,
api_routes.households_webhooks,
json=jsonify(webhook_data),
headers=unique_user.token,
)
item_id = response.json()["id"]
response = api_client.get(api_routes.groups_webhooks_item_id(item_id), headers=unique_user.token)
response = api_client.get(api_routes.households_webhooks_item_id(item_id), headers=unique_user.token)
webhook = assert_deserialize(response, 200)
assert webhook["id"] == item_id
@ -47,7 +47,7 @@ def test_read_webhook(api_client: TestClient, unique_user: TestUser, webhook_dat
def test_update_webhook(api_client: TestClient, webhook_data, unique_user: TestUser):
response = api_client.post(
api_routes.groups_webhooks,
api_routes.households_webhooks,
json=jsonify(webhook_data),
headers=unique_user.token,
)
@ -59,7 +59,7 @@ def test_update_webhook(api_client: TestClient, webhook_data, unique_user: TestU
webhook_data["enabled"] = False
response = api_client.put(
api_routes.groups_webhooks_item_id(item_id),
api_routes.households_webhooks_item_id(item_id),
json=jsonify(webhook_data),
headers=unique_user.token,
)
@ -72,15 +72,15 @@ def test_update_webhook(api_client: TestClient, webhook_data, unique_user: TestU
def test_delete_webhook(api_client: TestClient, webhook_data, unique_user: TestUser):
response = api_client.post(
api_routes.groups_webhooks,
api_routes.households_webhooks,
json=jsonify(webhook_data),
headers=unique_user.token,
)
item_dict = assert_deserialize(response, 201)
item_id = item_dict["id"]
response = api_client.delete(api_routes.groups_webhooks_item_id(item_id), headers=unique_user.token)
response = api_client.delete(api_routes.households_webhooks_item_id(item_id), headers=unique_user.token)
assert response.status_code == 200
response = api_client.get(api_routes.groups_webhooks_item_id(item_id), headers=unique_user.token)
response = api_client.get(api_routes.households_webhooks_item_id(item_id), headers=unique_user.token)
assert response.status_code == 404

View file

@ -0,0 +1,47 @@
from fastapi.testclient import TestClient
from mealie.schema.household.household_preferences import UpdateHouseholdPreferences
from tests.utils import api_routes
from tests.utils.assertion_helpers import assert_ignore_keys
from tests.utils.factories import random_bool
from tests.utils.fixture_schemas import TestUser
def test_get_preferences(api_client: TestClient, unique_user: TestUser) -> None:
response = api_client.get(api_routes.households_preferences, headers=unique_user.token)
assert response.status_code == 200
preferences = response.json()
assert preferences["recipePublic"] in {True, False}
assert preferences["recipeShowNutrition"] in {True, False}
def test_preferences_in_household(api_client: TestClient, unique_user: TestUser) -> None:
response = api_client.get(api_routes.households_self, headers=unique_user.token)
assert response.status_code == 200
household = response.json()
assert household["preferences"] is not None
# Spot Check
assert household["preferences"]["recipePublic"] in {True, False}
assert household["preferences"]["recipeShowNutrition"] in {True, False}
def test_update_preferences(api_client: TestClient, unique_user: TestUser) -> None:
new_data = UpdateHouseholdPreferences(recipe_public=random_bool(), recipe_show_nutrition=random_bool())
response = api_client.put(api_routes.households_preferences, json=new_data.model_dump(), headers=unique_user.token)
assert response.status_code == 200
preferences = response.json()
assert preferences is not None
assert preferences["recipePublic"] == new_data.recipe_public
assert preferences["recipeShowNutrition"] == new_data.recipe_show_nutrition
assert_ignore_keys(new_data.model_dump(by_alias=True), preferences, ["id", "householdId"])

View file

@ -2,7 +2,6 @@ from uuid import uuid4
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from tests.utils import api_routes
from tests.utils.factories import random_bool
from tests.utils.fixture_schemas import TestUser
@ -17,39 +16,28 @@ def get_permissions_payload(user_id: str, can_manage=None) -> dict:
}
def test_get_group_members(api_client: TestClient, user_tuple: list[TestUser]):
usr_1, usr_2 = user_tuple
response = api_client.get(api_routes.groups_members, headers=usr_1.token)
assert response.status_code == 200
members = response.json()
assert len(members) >= 2
all_ids = [x["id"] for x in members]
assert str(usr_1.user_id) in all_ids
assert str(usr_2.user_id) in all_ids
def test_set_memeber_permissions(api_client: TestClient, user_tuple: list[TestUser], database: AllRepositories):
def test_set_member_permissions(api_client: TestClient, user_tuple: list[TestUser]):
usr_1, usr_2 = user_tuple
# Set Acting User
acting_user = database.users.get_one(usr_1.user_id)
acting_user = usr_1.repos.users.get_one(usr_1.user_id)
assert acting_user
acting_user.can_manage = True
database.users.update(acting_user.id, acting_user)
usr_1.repos.users.update(acting_user.id, acting_user)
payload = get_permissions_payload(str(usr_2.user_id))
# Test
response = api_client.put(api_routes.groups_permissions, json=payload, headers=usr_1.token)
response = api_client.put(api_routes.households_permissions, json=payload, headers=usr_1.token)
assert response.status_code == 200
def test_set_memeber_permissions_unauthorized(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
def test_set_member_permissions_unauthorized(api_client: TestClient, unique_user: TestUser):
database = unique_user.repos
# Setup
user = database.users.get_one(unique_user.user_id)
assert user
user.can_manage = False
database.users.update(user.id, user)
@ -62,34 +50,38 @@ def test_set_memeber_permissions_unauthorized(api_client: TestClient, unique_use
}
# Test
response = api_client.put(api_routes.groups_permissions, json=payload, headers=unique_user.token)
response = api_client.put(api_routes.households_permissions, json=payload, headers=unique_user.token)
assert response.status_code == 403
def test_set_memeber_permissions_other_group(
def test_set_member_permissions_other_household(
api_client: TestClient,
unique_user: TestUser,
g2_user: TestUser,
database: AllRepositories,
h2_user: TestUser,
):
database = unique_user.repos
user = database.users.get_one(unique_user.user_id)
assert user
user.can_manage = True
database.users.update(user.id, user)
payload = get_permissions_payload(str(g2_user.user_id))
response = api_client.put(api_routes.groups_permissions, json=payload, headers=unique_user.token)
payload = get_permissions_payload(str(h2_user.user_id))
response = api_client.put(api_routes.households_permissions, json=payload, headers=unique_user.token)
assert response.status_code == 403
def test_set_memeber_permissions_no_user(
def test_set_member_permissions_no_user(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
):
database = unique_user.repos
user = database.users.get_one(unique_user.user_id)
assert user
user.can_manage = True
database.users.update(user.id, user)
payload = get_permissions_payload(str(uuid4()))
response = api_client.put(api_routes.groups_permissions, json=payload, headers=unique_user.token)
response = api_client.put(api_routes.households_permissions, json=payload, headers=unique_user.token)
assert response.status_code == 404

View file

@ -0,0 +1,20 @@
from fastapi.testclient import TestClient
from tests.utils import api_routes
from tests.utils.fixture_schemas import TestUser
def test_get_household_members(api_client: TestClient, user_tuple: list[TestUser], h2_user: TestUser):
usr_1, usr_2 = user_tuple
response = api_client.get(api_routes.households_members, headers=usr_1.token)
assert response.status_code == 200
members = response.json()
assert len(members) >= 2
all_ids = [x["id"] for x in members]
assert str(usr_1.user_id) in all_ids
assert str(usr_2.user_id) in all_ids
assert str(h2_user.user_id) not in all_ids

View file

@ -3,7 +3,7 @@ import random
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.group.group_shopping_list import ShoppingListOut
from mealie.schema.household.group_shopping_list import ShoppingListOut
from mealie.schema.labels.multi_purpose_label import MultiPurposeLabelOut
from mealie.services.seeder.seeder_service import SeederService
from tests.utils import api_routes, jsonify
@ -23,7 +23,7 @@ def create_labels(api_client: TestClient, unique_user: TestUser, count: int = 10
def test_new_list_creates_list_labels(api_client: TestClient, unique_user: TestUser):
labels = create_labels(api_client, unique_user)
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
api_routes.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.model_validate(response.json())
@ -37,14 +37,14 @@ def test_new_label_creates_list_labels(api_client: TestClient, unique_user: Test
# create a list with some labels
create_labels(api_client, unique_user)
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
api_routes.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.model_validate(response.json())
existing_label_settings = new_list.label_settings
# create more labels and make sure they were added to the list's label settings
new_labels = create_labels(api_client, unique_user)
response = api_client.get(api_routes.groups_shopping_lists_item_id(new_list.id), headers=unique_user.token)
response = api_client.get(api_routes.households_shopping_lists_item_id(new_list.id), headers=unique_user.token)
updated_list = ShoppingListOut.model_validate(response.json())
updated_label_settings = updated_list.label_settings
assert len(updated_label_settings) == len(existing_label_settings) + len(new_labels)
@ -58,23 +58,66 @@ def test_new_label_creates_list_labels(api_client: TestClient, unique_user: Test
assert label.id in label_settings_label_ids
def test_seed_label_creates_list_labels(database: AllRepositories, api_client: TestClient, unique_user: TestUser):
def test_new_label_creates_list_labels_in_all_households(
api_client: TestClient, unique_user: TestUser, h2_user: TestUser
):
# unique_user and h2_user are in the same group, so these labels should be for both of them
create_labels(api_client, unique_user)
# create a list with some labels for each user
response = api_client.post(
api_routes.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list_h1 = ShoppingListOut.model_validate(response.json())
existing_label_settings_h1 = new_list_h1.label_settings
response = api_client.post(
api_routes.households_shopping_lists, json={"name": random_string()}, headers=h2_user.token
)
new_list_h2 = ShoppingListOut.model_validate(response.json())
existing_label_settings_h2 = new_list_h2.label_settings
# create more labels and make sure they were added to both lists' label settings
new_labels = create_labels(api_client, unique_user)
for user, new_list, existing_label_settings in [
(unique_user, new_list_h1, existing_label_settings_h1),
(h2_user, new_list_h2, existing_label_settings_h2),
]:
response = api_client.get(api_routes.households_shopping_lists_item_id(new_list.id), headers=user.token)
updated_list = ShoppingListOut.model_validate(response.json())
updated_label_settings = updated_list.label_settings
assert len(updated_label_settings) == len(existing_label_settings) + len(new_labels)
label_settings_ids = [setting.id for setting in updated_list.label_settings]
for label_setting in existing_label_settings:
assert label_setting.id in label_settings_ids
label_settings_label_ids = [setting.label_id for setting in updated_list.label_settings]
for label in new_labels:
assert label.id in label_settings_label_ids
def test_seed_label_creates_list_labels(api_client: TestClient, unique_user: TestUser):
CREATED_LABELS = 21
database = unique_user.repos
# create a list with some labels
create_labels(api_client, unique_user)
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
api_routes.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.model_validate(response.json())
existing_label_settings = new_list.label_settings
# seed labels and make sure they were added to the list's label settings
group = database.groups.get_one(unique_user.group_id)
seeder = SeederService(database, None, group) # type: ignore
assert group
database = AllRepositories(database.session, group_id=group.id)
seeder = SeederService(database)
seeder.seed_labels("en-US")
response = api_client.get(api_routes.groups_shopping_lists_item_id(new_list.id), headers=unique_user.token)
response = api_client.get(api_routes.households_shopping_lists_item_id(new_list.id), headers=unique_user.token)
updated_list = ShoppingListOut.model_validate(response.json())
updated_label_settings = updated_list.label_settings
assert len(updated_label_settings) == len(existing_label_settings) + CREATED_LABELS
@ -87,7 +130,7 @@ def test_seed_label_creates_list_labels(database: AllRepositories, api_client: T
def test_delete_label_deletes_list_labels(api_client: TestClient, unique_user: TestUser):
new_labels = create_labels(api_client, unique_user)
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
api_routes.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.model_validate(response.json())
@ -95,7 +138,7 @@ def test_delete_label_deletes_list_labels(api_client: TestClient, unique_user: T
label_to_delete = random.choice(new_labels)
api_client.delete(api_routes.groups_labels_item_id(label_to_delete.id), headers=unique_user.token)
response = api_client.get(api_routes.groups_shopping_lists_item_id(new_list.id), headers=unique_user.token)
response = api_client.get(api_routes.households_shopping_lists_item_id(new_list.id), headers=unique_user.token)
updated_list = ShoppingListOut.model_validate(response.json())
assert len(updated_list.label_settings) == len(existing_label_settings) - 1
@ -114,7 +157,7 @@ def test_update_list_doesnt_change_list_labels(api_client: TestClient, unique_us
updated_name = random_string()
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": original_name}, headers=unique_user.token
api_routes.households_shopping_lists, json={"name": original_name}, headers=unique_user.token
)
new_list = ShoppingListOut.model_validate(response.json())
assert new_list.name == original_name
@ -122,13 +165,13 @@ def test_update_list_doesnt_change_list_labels(api_client: TestClient, unique_us
updated_list_data = new_list.model_dump()
updated_list_data.pop("created_at", None)
updated_list_data.pop("update_at", None)
updated_list_data.pop("updated_at", None)
updated_list_data["name"] = updated_name
updated_list_data["label_settings"][0]["position"] = random_int(999, 9999)
response = api_client.put(
api_routes.groups_shopping_lists_item_id(new_list.id),
api_routes.households_shopping_lists_item_id(new_list.id),
json=jsonify(updated_list_data),
headers=unique_user.token,
)
@ -140,14 +183,14 @@ def test_update_list_doesnt_change_list_labels(api_client: TestClient, unique_us
def test_update_list_labels(api_client: TestClient, unique_user: TestUser):
create_labels(api_client, unique_user)
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
api_routes.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.model_validate(response.json())
changed_setting = random.choice(new_list.label_settings)
changed_setting.position = random_int(999, 9999)
response = api_client.put(
api_routes.groups_shopping_lists_item_id_label_settings(new_list.id),
api_routes.households_shopping_lists_item_id_label_settings(new_list.id),
json=jsonify(new_list.label_settings),
headers=unique_user.token,
)
@ -168,7 +211,7 @@ def test_update_list_labels(api_client: TestClient, unique_user: TestUser):
def test_list_label_order(api_client: TestClient, unique_user: TestUser):
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
api_routes.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.model_validate(response.json())
for i, setting in enumerate(new_list.label_settings):
@ -179,7 +222,7 @@ def test_list_label_order(api_client: TestClient, unique_user: TestUser):
random.shuffle(new_list.label_settings)
response = api_client.put(
api_routes.groups_shopping_lists_item_id_label_settings(new_list.id),
api_routes.households_shopping_lists_item_id_label_settings(new_list.id),
json=jsonify(new_list.label_settings),
headers=unique_user.token,
)

View file

@ -16,9 +16,8 @@ from tests.utils.fixture_schemas import TestUser
@pytest.fixture(scope="function")
def ten_slugs(
api_client: TestClient, unique_user: TestUser, database: AllRepositories
) -> Generator[list[str], None, None]:
def ten_slugs(api_client: TestClient, unique_user: TestUser) -> Generator[list[str], None, None]:
database = unique_user.repos
slugs: list[str] = []
for _ in range(10):
@ -38,9 +37,9 @@ def ten_slugs(
pass
def test_bulk_tag_recipes(
api_client: TestClient, unique_user: TestUser, database: AllRepositories, ten_slugs: list[str]
):
def test_bulk_tag_recipes(api_client: TestClient, unique_user: TestUser, ten_slugs: list[str]):
database = unique_user.repos
# Setup Tags
tags = []
for _ in range(3):
@ -66,9 +65,10 @@ def test_bulk_tag_recipes(
def test_bulk_categorize_recipes(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
ten_slugs: list[str],
):
database = unique_user.repos
# Setup Tags
categories = []
for _ in range(3):
@ -94,9 +94,9 @@ def test_bulk_categorize_recipes(
def test_bulk_delete_recipes(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
ten_slugs: list[str],
):
database = unique_user.repos
payload = {"recipes": ten_slugs}
response = api_client.post(api_routes.recipes_bulk_actions_delete, json=payload, headers=unique_user.token)

View file

@ -1,7 +1,10 @@
from uuid import UUID
import pytest
from fastapi.testclient import TestClient
from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe import Recipe
from tests.utils import api_routes
from tests.utils.factories import random_string
@ -39,7 +42,7 @@ def test_create_comment(api_client: TestClient, unique_recipe: Recipe, unique_us
assert response_data["recipeId"] == str(unique_recipe.id)
assert response_data["text"] == create_data["text"]
assert response_data["userId"] == unique_user.user_id
assert response_data["userId"] == str(unique_user.user_id)
# Check for Proper Association
response = api_client.get(api_routes.recipes_slug_comments(unique_recipe.slug), headers=unique_user.token)
@ -50,7 +53,7 @@ def test_create_comment(api_client: TestClient, unique_recipe: Recipe, unique_us
assert len(response_data) == 1
assert response_data[0]["recipeId"] == str(unique_recipe.id)
assert response_data[0]["text"] == create_data["text"]
assert response_data[0]["userId"] == unique_user.user_id
assert response_data[0]["userId"] == str(unique_user.user_id)
def test_update_comment(api_client: TestClient, unique_recipe: Recipe, unique_user: TestUser):
@ -73,7 +76,7 @@ def test_update_comment(api_client: TestClient, unique_recipe: Recipe, unique_us
assert response_data["recipeId"] == str(unique_recipe.id)
assert response_data["text"] == update_data["text"]
assert response_data["userId"] == unique_user.user_id
assert response_data["userId"] == str(unique_user.user_id)
def test_delete_comment(api_client: TestClient, unique_recipe: Recipe, unique_user: TestUser):
@ -93,7 +96,20 @@ def test_delete_comment(api_client: TestClient, unique_recipe: Recipe, unique_us
assert response.status_code == 404
def test_admin_can_delete(api_client: TestClient, unique_recipe: Recipe, unique_user: TestUser, admin_user: TestUser):
def test_admin_can_delete(
unfiltered_database: AllRepositories,
api_client: TestClient,
unique_recipe: Recipe,
unique_user: TestUser,
admin_user: TestUser,
):
# Make sure admin belongs to same group/household as user
admin_data = unfiltered_database.users.get_one(admin_user.user_id)
assert admin_data
admin_data.group_id = UUID(unique_user.group_id)
admin_data.household_id = UUID(unique_user.household_id)
unfiltered_database.users.update(admin_user.user_id, admin_data)
# Create Comment
create_data = random_comment(unique_recipe.id)
response = api_client.post(api_routes.comments, json=create_data, headers=unique_user.token)

View file

@ -9,10 +9,10 @@ from pathlib import Path
from uuid import uuid4
from zipfile import ZipFile
from httpx import Response
import pytest
from bs4 import BeautifulSoup
from fastapi.testclient import TestClient
from httpx import Response
from pytest import MonkeyPatch
from recipe_scrapers._abstract import AbstractScraper
from recipe_scrapers._schemaorg import SchemaOrg
@ -20,14 +20,13 @@ from recipe_scrapers.plugins import SchemaOrgFillPlugin
from slugify import slugify
from mealie.pkgs.safehttp.transport import AsyncSafeTransport
from mealie.repos.repository_factory import AllRepositories
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
from mealie.schema.recipe.recipe_tool import RecipeToolSave
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 import utils
from tests.utils import api_routes
from tests.utils.factories import random_int, random_string
from tests.utils.fixture_schemas import TestUser
@ -82,7 +81,7 @@ def get_init(html_path: Path):
current_method = getattr(self.__class__, name)
current_method = SchemaOrgFillPlugin.run(current_method)
setattr(self.__class__, name, current_method)
setattr(self.__class__, "plugins_initialized", True)
self.__class__.plugins_initialized = True
return init_override
@ -162,7 +161,8 @@ def test_create_by_url(
assert tag["name"] in expected_tags
def test_create_recipe_from_zip(database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str):
def test_create_recipe_from_zip(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
recipe_name = random_string()
recipe = RecipeSummary(
id=uuid4(),
@ -181,9 +181,8 @@ def test_create_recipe_from_zip(database: AllRepositories, api_client: TestClien
assert fetched_recipe
def test_create_recipe_from_zip_invalid_group(
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
):
def test_create_recipe_from_zip_invalid_group(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
recipe_name = random_string()
recipe = RecipeSummary(
id=uuid4(),
@ -205,9 +204,8 @@ def test_create_recipe_from_zip_invalid_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
):
def test_create_recipe_from_zip_invalid_user(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
recipe_name = random_string()
recipe = RecipeSummary(
id=uuid4(),
@ -229,10 +227,9 @@ def test_create_recipe_from_zip_invalid_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(
def test_create_recipe_from_zip_existing_category(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
categories = database.categories.create_many(
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
)
category = random.choice(categories)
@ -259,10 +256,9 @@ def test_create_recipe_from_zip_existing_category(
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(
def test_create_recipe_from_zip_existing_tag(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
tags = database.tags.create_many(
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
)
tag = random.choice(tags)
@ -290,9 +286,10 @@ def test_create_recipe_from_zip_existing_tag(
def test_create_recipe_from_zip_existing_category_wrong_ids(
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
api_client: TestClient, unique_user: TestUser, tempdir: str
):
categories = database.categories.by_group(unique_user.group_id).create_many(
database = unique_user.repos
categories = database.categories.create_many(
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
)
category = random.choice(categories)
@ -320,10 +317,9 @@ def test_create_recipe_from_zip_existing_category_wrong_ids(
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(
def test_create_recipe_from_zip_existing_tag_wrong_ids(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
tags = database.tags.create_many(
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
)
tag = random.choice(tags)
@ -351,9 +347,8 @@ def test_create_recipe_from_zip_existing_tag_wrong_ids(
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
):
def test_create_recipe_from_zip_invalid_category(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
invalid_name = random_string()
invalid_category = RecipeCategory(id=uuid4(), name=invalid_name, slug=invalid_name)
@ -382,9 +377,8 @@ def test_create_recipe_from_zip_invalid_category(
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
):
def test_create_recipe_from_zip_invalid_tag(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
invalid_name = random_string()
invalid_tag = RecipeTag(id=uuid4(), name=invalid_name, slug=invalid_name)
@ -713,17 +707,15 @@ def test_get_recipe_by_slug_or_id(api_client: TestClient, unique_user: utils.Tes
@pytest.mark.parametrize("organizer_type", ["tags", "categories", "tools"])
def test_get_recipes_organizer_filter(
api_client: TestClient, unique_user: utils.TestUser, organizer_type: str, database: AllRepositories
):
def test_get_recipes_organizer_filter(api_client: TestClient, unique_user: utils.TestUser, organizer_type: str):
database = unique_user.repos
# create recipes with different organizers
tags = database.tags.by_group(unique_user.group_id).create_many(
[TagSave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)]
)
categories = database.categories.by_group(unique_user.group_id).create_many(
tags = database.tags.create_many([TagSave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)])
categories = database.categories.create_many(
[CategorySave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)]
)
tools = database.tools.by_group(unique_user.group_id).create_many(
tools = database.tools.create_many(
[RecipeToolSave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)]
)
@ -743,7 +735,7 @@ def test_get_recipes_organizer_filter(
)
)
recipes = database.recipes.by_group(unique_user.group_id).create_many(new_recipes_data) # type: ignore
recipes = database.recipes.create_many(new_recipes_data) # type: ignore
# get recipes by organizer
if organizer_type == "tags":

View file

@ -14,8 +14,8 @@ def test_ownership_on_new_with_admin(api_client: TestClient, admin_user: TestUse
recipe = api_client.get(api_routes.recipes + f"/{recipe_name}", headers=admin_user.token).json()
assert recipe["userId"] == admin_user.user_id
assert recipe["groupId"] == admin_user.group_id
assert recipe["userId"] == str(admin_user.user_id)
assert recipe["groupId"] == str(admin_user.group_id)
def test_ownership_on_new_with_user(api_client: TestClient, g2_user: TestUser):
@ -29,8 +29,8 @@ def test_ownership_on_new_with_user(api_client: TestClient, g2_user: TestUser):
recipe = response.json()
assert recipe["userId"] == g2_user.user_id
assert recipe["groupId"] == g2_user.group_id
assert recipe["userId"] == str(g2_user.user_id)
assert recipe["groupId"] == str(g2_user.group_id)
def test_get_all_only_includes_group_recipes(api_client: TestClient, unique_user: TestUser):
@ -47,8 +47,8 @@ def test_get_all_only_includes_group_recipes(api_client: TestClient, unique_user
assert len(recipes) == 5
for recipe in recipes:
assert recipe["groupId"] == unique_user.group_id
assert recipe["userId"] == unique_user.user_id
assert recipe["groupId"] == str(unique_user.group_id)
assert recipe["userId"] == str(unique_user.user_id)
def test_unique_slug_by_group(api_client: TestClient, unique_user: TestUser, g2_user: TestUser) -> None:

View file

@ -1,11 +1,9 @@
import random
from collections.abc import Generator
from uuid import UUID
import pytest
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe import Recipe
from mealie.schema.user.user import UserRatingUpdate
from tests.utils import api_routes
@ -14,9 +12,10 @@ from tests.utils.fixture_schemas import TestUser
@pytest.fixture(scope="function")
def recipes(database: AllRepositories, user_tuple: tuple[TestUser, TestUser]) -> Generator[list[Recipe], None, None]:
def recipes(user_tuple: tuple[TestUser, TestUser]) -> Generator[list[Recipe], None, None]:
unique_user = random.choice(user_tuple)
recipes_repo = database.recipes.by_group(UUID(unique_user.group_id))
database = unique_user.repos
recipes_repo = database.recipes
recipes: list[Recipe] = []
for _ in range(random_int(10, 20)):
@ -51,7 +50,6 @@ def test_user_recipe_favorites(
unique_user = user_tuple[1]
response = api_client.get(api_routes.users_id_favorites(unique_user.user_id), headers=unique_user.token)
assert response.json()["ratings"] == []
recipes_to_favorite = random.sample(recipes, random_int(5, len(recipes)))
@ -89,7 +87,11 @@ def test_user_recipe_favorites(
assert len(ratings) == len(recipes_to_favorite) - len(recipe_favorites_to_remove)
fetched_recipe_ids = {rating["recipeId"] for rating in ratings}
removed_recipe_ids = {str(recipe.id) for recipe in recipe_favorites_to_remove}
assert fetched_recipe_ids == favorited_recipe_ids - removed_recipe_ids
for recipe_id in removed_recipe_ids:
assert recipe_id not in fetched_recipe_ids
for recipe_id in fetched_recipe_ids:
assert recipe_id in favorited_recipe_ids
@pytest.mark.parametrize("add_favorite", [True, False])
@ -119,8 +121,6 @@ def test_set_user_recipe_ratings(
unique_user = user_tuple[1]
response = api_client.get(api_routes.users_id_ratings(unique_user.user_id), headers=unique_user.token)
assert response.json()["ratings"] == []
recipes_to_rate = random.sample(recipes, random_int(8, len(recipes)))
expected_ratings_by_recipe_id: dict[str, UserRatingUpdate] = {}
@ -144,12 +144,16 @@ def test_set_user_recipe_ratings(
response = api_client.get(get_url, headers=unique_user.token)
ratings = response.json()["ratings"]
assert len(ratings) == len(recipes_to_rate)
for rating in ratings:
recipe_id = rating["recipeId"]
assert rating["rating"] == expected_ratings_by_recipe_id[recipe_id].rating
if recipe_id not in expected_ratings_by_recipe_id:
continue
assert rating["rating"] == expected_ratings_by_recipe_id.pop(recipe_id).rating
assert not rating["isFavorite"]
assert not expected_ratings_by_recipe_id # we should have popped all of them
def test_set_user_rating_invalid_recipe_404(api_client: TestClient, user_tuple: tuple[TestUser, TestUser]):
unique_user = random.choice(user_tuple)
@ -289,9 +293,10 @@ def test_set_rating_to_zero(api_client: TestClient, user_tuple: tuple[TestUser,
def test_delete_recipe_deletes_ratings(
database: AllRepositories, api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe]
api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe]
):
unique_user = random.choice(user_tuple)
database = unique_user.repos
recipe = random.choice(recipes)
rating = UserRatingUpdate(rating=random.uniform(1, 5), is_favorite=random.choice([True, False, None]))
response = api_client.post(
@ -306,6 +311,7 @@ def test_delete_recipe_deletes_ratings(
assert response.json()
database.recipes.delete(recipe.id, match_key="id")
database.session.commit()
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
assert response.status_code == 404

View file

@ -4,15 +4,15 @@ import pytest
import sqlalchemy
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe_share_token import RecipeShareTokenSave
from mealie.schema.recipe.recipe_share_token import RecipeShareToken, RecipeShareTokenSave
from tests.utils import api_routes
from tests.utils.factories import random_string
from tests.utils.fixture_schemas import TestUser
@pytest.fixture(scope="function")
def slug(api_client: TestClient, unique_user: TestUser, database: AllRepositories) -> Generator[str, None, None]:
def slug(api_client: TestClient, unique_user: TestUser) -> Generator[str, None, None]:
database = unique_user.repos
payload = {"name": random_string(length=20)}
response = api_client.post(api_routes.recipes, json=payload, headers=unique_user.token)
assert response.status_code == 201
@ -27,14 +27,13 @@ def slug(api_client: TestClient, unique_user: TestUser, database: AllRepositorie
pass
def test_recipe_share_tokens_get_all(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
def test_recipe_share_tokens_get_all(api_client: TestClient, unique_user: TestUser, slug: str):
database = unique_user.repos
# Create 5 Tokens
recipe = database.recipes.get_one(slug)
assert recipe
tokens = []
for _ in range(5):
token = database.recipe_share_tokens.create(
@ -50,14 +49,13 @@ def test_recipe_share_tokens_get_all(
assert len(response_data) == 5
def test_recipe_share_tokens_get_all_with_id(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
def test_recipe_share_tokens_get_all_with_id(api_client: TestClient, unique_user: TestUser, slug: str):
database = unique_user.repos
# Create 5 Tokens
recipe = database.recipes.get_one(slug)
assert recipe
tokens = []
for _ in range(3):
token = database.recipe_share_tokens.create(
@ -73,13 +71,10 @@ def test_recipe_share_tokens_get_all_with_id(
assert len(response_data) == 3
def test_recipe_share_tokens_create_and_get_one(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
def test_recipe_share_tokens_create_and_get_one(api_client: TestClient, unique_user: TestUser, slug: str):
database = unique_user.repos
recipe = database.recipes.get_one(slug)
assert recipe
payload = {
"recipeId": str(recipe.id),
@ -95,14 +90,13 @@ def test_recipe_share_tokens_create_and_get_one(
assert response_data["recipe"]["id"] == str(recipe.id)
def test_recipe_share_tokens_delete_one(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
def test_recipe_share_tokens_delete_one(api_client: TestClient, unique_user: TestUser, slug: str):
database = unique_user.repos
# Create Token
token: RecipeShareToken | None = None
recipe = database.recipes.get_one(slug)
assert recipe
token = database.recipe_share_tokens.create(
RecipeShareTokenSave(recipe_id=recipe.id, group_id=unique_user.group_id)

View file

@ -15,7 +15,7 @@ def test_associate_ingredient_with_step(api_client: TestClient, unique_user: Tes
# Associate an ingredient with a step
steps = {} # key=step_id, value=ingredient_id
for idx, step in enumerate(recipe.recipe_instructions):
for idx, step in enumerate(recipe.recipe_instructions or []):
ingredients = random.choices(recipe.recipe_ingredient, k=2)
step.ingredient_references = [
@ -39,7 +39,7 @@ def test_associate_ingredient_with_step(api_client: TestClient, unique_user: Tes
data: dict = json.loads(response.text)
for idx, stp in enumerate(data.get("recipeInstructions")):
for idx, stp in enumerate(data.get("recipeInstructions") or []):
all_refs = [ref["referenceId"] for ref in stp.get("ingredientReferences")]
assert len(all_refs) == 2

View file

@ -39,7 +39,7 @@ def test_create_timeline_event(api_client: TestClient, unique_user: TestUser, re
recipe = recipes[0]
new_event = {
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"event_type": "info",
"message": random_string(),
@ -63,7 +63,7 @@ def test_get_all_timeline_events(api_client: TestClient, unique_user: TestUser,
events_data = [
{
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"event_type": "info",
"message": random_string(),
@ -98,7 +98,7 @@ def test_get_timeline_event(api_client: TestClient, unique_user: TestUser, recip
recipe = recipes[0]
new_event_data = {
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"event_type": "info",
"message": random_string(),
@ -127,7 +127,7 @@ def test_update_timeline_event(api_client: TestClient, unique_user: TestUser, re
recipe = recipes[0]
new_event_data = {
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": old_subject,
"event_type": "info",
}
@ -157,7 +157,7 @@ def test_delete_timeline_event(api_client: TestClient, unique_user: TestUser, re
recipe = recipes[0]
new_event_data = {
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"event_type": "info",
"message": random_string(),
@ -187,7 +187,7 @@ def test_timeline_event_message_alias(api_client: TestClient, unique_user: TestU
recipe = recipes[0]
new_event_data = {
"recipeId": str(recipe.id),
"userId": unique_user.user_id,
"userId": str(unique_user.user_id),
"subject": random_string(),
"eventType": "info",
"eventMessage": random_string(), # eventMessage is the correct alias for the message
@ -234,7 +234,7 @@ def test_timeline_event_update_image(
recipe = recipes[0]
new_event_data = {
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"message": random_string(),
"event_type": "info",
@ -281,7 +281,7 @@ def test_create_recipe_with_timeline_event(api_client: TestClient, unique_user:
def test_invalid_recipe_id(api_client: TestClient, unique_user: TestUser):
new_event_data = {
"recipe_id": str(uuid4()),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"event_type": "info",
"message": random_string(),

View file

@ -1,30 +1,30 @@
import pytest
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from tests.utils import TestUser, api_routes
from tests.utils.factories import random_email, random_int, random_string
@pytest.mark.parametrize("use_admin_user", [True, False])
def test_get_all_users_admin(
request: pytest.FixtureRequest, database: AllRepositories, api_client: TestClient, use_admin_user: bool
):
def test_get_all_users_admin(request: pytest.FixtureRequest, api_client: TestClient, use_admin_user: bool):
user: TestUser
if use_admin_user:
user = request.getfixturevalue("admin_user")
else:
user = request.getfixturevalue("unique_user")
database = user.repos
user_ids: set[str] = set()
for _ in range(random_int(2, 5)):
group = database.groups.create({"name": random_string()})
household = database.households.create({"name": random_string(), "group_id": group.id})
for _ in range(random_int(2, 5)):
new_user = database.users.create(
{
"username": random_string(),
"email": random_email(),
"group": group.name,
"household": household.name,
"full_name": random_string(),
"password": random_string(),
"admin": False,
@ -43,56 +43,3 @@ def test_get_all_users_admin(
response_user_ids = {user["id"] for user in response.json()["items"]}
for user_id in user_ids:
assert user_id in response_user_ids
@pytest.mark.parametrize("use_admin_user", [True, False])
def test_get_all_group_users(
request: pytest.FixtureRequest, database: AllRepositories, api_client: TestClient, use_admin_user: bool
):
user: TestUser
if use_admin_user:
user = request.getfixturevalue("admin_user")
else:
user = request.getfixturevalue("unique_user")
other_group_user_ids: set[str] = set()
for _ in range(random_int(2, 5)):
group = database.groups.create({"name": random_string()})
for _ in range(random_int(2, 5)):
new_user = database.users.create(
{
"username": random_string(),
"email": random_email(),
"group": group.name,
"full_name": random_string(),
"password": random_string(),
"admin": False,
}
)
other_group_user_ids.add(str(new_user.id))
user_group = database.groups.get_by_slug_or_id(user.group_id)
assert user_group
same_group_user_ids: set[str] = {user.user_id}
for _ in range(random_int(2, 5)):
new_user = database.users.create(
{
"username": random_string(),
"email": random_email(),
"group": user_group.name,
"full_name": random_string(),
"password": random_string(),
"admin": False,
}
)
same_group_user_ids.add(str(new_user.id))
response = api_client.get(api_routes.users_group_users, params={"perPage": -1}, headers=user.token)
assert response.status_code == 200
response_user_ids = {user["id"] for user in response.json()["items"]}
# assert only users from the same group are returned
for user_id in other_group_user_ids:
assert user_id not in response_user_ids
for user_id in same_group_user_ids:
assert user_id in response_user_ids

View file

@ -4,7 +4,6 @@ import pytest
from fastapi.testclient import TestClient
from mealie.core.config import get_app_settings
from mealie.repos.repository_factory import AllRepositories
from mealie.services.user_services.user_service import UserService
from tests.utils import api_routes
from tests.utils.factories import random_string
@ -45,11 +44,12 @@ def test_get_logged_in_user_invalid_token(api_client: TestClient, use_token: boo
assert response.status_code == 401
def test_user_lockout_after_bad_attemps(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
def test_user_lockout_after_bad_attemps(api_client: TestClient, unique_user: TestUser):
"""
if the user has more than 5 bad login attempts the user will be locked out for 4 hours
This only applies if there is a user in the database with the same username
"""
database = unique_user.repos
settings = get_app_settings()
for _ in range(settings.SECURITY_MAX_LOGIN_ATTEMPTS):