1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-03 04:25:24 +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

@ -4,25 +4,25 @@ CWD = Path(__file__).parent
locale_dir = CWD / "locale"
backup_version_44e8d670719d_1 = CWD / "backups/backup_version_44e8d670719d_1.zip"
backup_version_44e8d670719d_1 = CWD / "backups/backup-version-44e8d670719d-1.zip"
"""44e8d670719d: add extras to shopping lists, list items, and ingredient foods"""
backup_version_44e8d670719d_2 = CWD / "backups/backup_version_44e8d670719d_2.zip"
backup_version_44e8d670719d_2 = CWD / "backups/backup-version-44e8d670719d-2.zip"
"""44e8d670719d: add extras to shopping lists, list items, and ingredient foods"""
backup_version_44e8d670719d_3 = CWD / "backups/backup_version_44e8d670719d_3.zip"
backup_version_44e8d670719d_3 = CWD / "backups/backup-version-44e8d670719d-3.zip"
"""44e8d670719d: add extras to shopping lists, list items, and ingredient foods"""
backup_version_44e8d670719d_4 = CWD / "backups/backup_version_44e8d670719d_4.zip"
backup_version_44e8d670719d_4 = CWD / "backups/backup-version-44e8d670719d-4.zip"
"""44e8d670719d: add extras to shopping lists, list items, and ingredient foods"""
backup_version_ba1e4a6cfe99_1 = CWD / "backups/backup_version_ba1e4a6cfe99_1.zip"
backup_version_ba1e4a6cfe99_1 = CWD / "backups/backup-version-ba1e4a6cfe99-1.zip"
"""ba1e4a6cfe99: added plural names and alias tables for foods and units"""
backup_version_bcfdad6b7355_1 = CWD / "backups/backup_version_bcfdad6b7355_1.zip"
backup_version_bcfdad6b7355_1 = CWD / "backups/backup-version-bcfdad6b7355-1.zip"
"""bcfdad6b7355: remove tool name and slug unique contraints"""
backup_version_09aba125b57a_1 = CWD / "backups/backup_version_09aba125b57a_1.zip"
backup_version_09aba125b57a_1 = CWD / "backups/backup-version-09aba125b57a-1.zip"
"""09aba125b57a: add OIDC auth method (Safari-mangled ZIP structure)"""
migrations_paprika = CWD / "migrations/paprika.zip"

View file

@ -1,7 +1,11 @@
from uuid import UUID
from pytest import fixture
from sqlalchemy.orm import Session
from starlette.testclient import TestClient
from mealie.core.config import get_app_settings
from mealie.repos.all_repositories import get_repositories
from tests import utils
from tests.utils import api_routes
@ -14,8 +18,8 @@ def admin_token(api_client: TestClient):
return utils.login(form_data, api_client)
@fixture(scope="session")
def admin_user(api_client: TestClient):
@fixture(scope="module")
def admin_user(session: Session, api_client: TestClient):
settings = get_app_settings()
form_data = {"username": settings._DEFAULT_EMAIL, "password": settings._DEFAULT_PASSWORD}
@ -26,17 +30,27 @@ def admin_user(api_client: TestClient):
assert token is not None
assert user_data.get("admin") is True
assert user_data.get("groupId") is not None
assert user_data.get("id") is not None
assert (user_id := user_data.get("id")) is not None
assert (group_id := user_data.get("groupId")) is not None
assert (household_id := user_data.get("householdId")) is not None
if not isinstance(user_id, UUID):
user_id = UUID(user_id)
if not isinstance(group_id, UUID):
group_id = UUID(group_id)
if not isinstance(household_id, UUID):
household_id = UUID(household_id)
try:
yield utils.TestUser(
_group_id=user_data.get("groupId"),
user_id=user_data.get("id"),
_group_id=group_id,
_household_id=household_id,
user_id=user_id,
password=settings._DEFAULT_PASSWORD,
username=user_data.get("username"),
email=user_data.get("email"),
token=token,
repos=get_repositories(session, group_id=group_id, household_id=household_id),
)
finally:
# TODO: Delete User after test

View file

@ -1,14 +1,21 @@
from collections.abc import Generator
import pytest
from sqlalchemy.orm import Session, sessionmaker
from mealie.db.db_setup import SessionLocal
from mealie.repos.all_repositories import AllRepositories, get_repositories
@pytest.fixture()
def database() -> AllRepositories:
@pytest.fixture(scope="module")
def session() -> Generator[sessionmaker[Session], None, None]:
try:
db = SessionLocal()
yield get_repositories(db)
sess = SessionLocal()
yield sess
finally:
db.close()
sess.close()
@pytest.fixture()
def unfiltered_database(session: Session) -> Generator[AllRepositories, None, None]:
yield get_repositories(session, group_id=None, household_id=None)

View file

@ -2,6 +2,7 @@ from dataclasses import dataclass
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from tests import utils
from tests.fixtures.fixture_users import build_unique_user
@ -15,8 +16,8 @@ class MultiTenant:
@pytest.fixture(scope="module")
def multitenants(api_client: TestClient) -> MultiTenant:
def multitenants(session: Session, api_client: TestClient) -> MultiTenant:
yield MultiTenant(
user_one=build_unique_user(random_string(12), api_client),
user_two=build_unique_user(random_string(12), api_client),
user_one=build_unique_user(session, random_string(12), api_client),
user_two=build_unique_user(session, random_string(12), api_client),
)

View file

@ -4,7 +4,6 @@ from collections.abc import Generator
import sqlalchemy
from pytest import fixture
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe import Recipe
from mealie.schema.recipe.recipe_category import CategoryOut, CategorySave
from mealie.schema.recipe.recipe_ingredient import RecipeIngredient
@ -30,7 +29,8 @@ def recipe_store():
@fixture(scope="function")
def recipe_ingredient_only(database: AllRepositories, unique_user: TestUser):
def recipe_ingredient_only(unique_user: TestUser):
database = unique_user.repos
# Create a recipe
recipe = Recipe(
user_id=unique_user.user_id,
@ -55,7 +55,8 @@ def recipe_ingredient_only(database: AllRepositories, unique_user: TestUser):
@fixture(scope="function")
def recipe_categories(database: AllRepositories, unique_user: TestUser) -> Generator[list[CategoryOut], None, None]:
def recipe_categories(unique_user: TestUser) -> Generator[list[CategoryOut], None, None]:
database = unique_user.repos
models: list[CategoryOut] = []
for _ in range(3):
category = CategorySave(
@ -73,7 +74,8 @@ def recipe_categories(database: AllRepositories, unique_user: TestUser) -> Gener
@fixture(scope="function")
def random_recipe(database: AllRepositories, unique_user: TestUser) -> Generator[Recipe, None, None]:
def random_recipe(unique_user: TestUser) -> Generator[Recipe, None, None]:
database = unique_user.repos
recipe = Recipe(
user_id=unique_user.user_id,
group_id=unique_user.group_id,

View file

@ -2,8 +2,7 @@ import pytest
import sqlalchemy
from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.group.group_shopping_list import ShoppingListItemCreate, ShoppingListOut, ShoppingListSave
from mealie.schema.household.group_shopping_list import ShoppingListItemCreate, ShoppingListOut, ShoppingListSave
from tests.utils.factories import random_string
from tests.utils.fixture_schemas import TestUser
@ -24,12 +23,17 @@ def create_item(list_id: UUID4) -> dict:
@pytest.fixture(scope="function")
def shopping_lists(database: AllRepositories, unique_user: TestUser):
def shopping_lists(unique_user: TestUser):
database = unique_user.repos
models: list[ShoppingListOut] = []
for _ in range(3):
model = database.group_shopping_lists.create(
ShoppingListSave(name=random_string(10), group_id=unique_user.group_id, user_id=unique_user.user_id),
ShoppingListSave(
name=random_string(10),
group_id=unique_user.group_id,
user_id=unique_user.user_id,
),
)
models.append(model)
@ -44,9 +48,14 @@ def shopping_lists(database: AllRepositories, unique_user: TestUser):
@pytest.fixture(scope="function")
def shopping_list(database: AllRepositories, unique_user: TestUser):
def shopping_list(unique_user: TestUser):
database = unique_user.repos
model = database.group_shopping_lists.create(
ShoppingListSave(name=random_string(10), group_id=unique_user.group_id, user_id=unique_user.user_id),
ShoppingListSave(
name=random_string(10),
group_id=unique_user.group_id,
user_id=unique_user.user_id,
),
)
yield model
@ -58,9 +67,14 @@ def shopping_list(database: AllRepositories, unique_user: TestUser):
@pytest.fixture(scope="function")
def list_with_items(database: AllRepositories, unique_user: TestUser):
def list_with_items(unique_user: TestUser):
database = unique_user.repos
list_model = database.group_shopping_lists.create(
ShoppingListSave(name=random_string(10), group_id=unique_user.group_id, user_id=unique_user.user_id),
ShoppingListSave(
name=random_string(10),
group_id=unique_user.group_id,
user_id=unique_user.user_id,
),
)
for _ in range(10):

View file

@ -1,7 +1,9 @@
import json
from collections.abc import Generator
from uuid import UUID
from pytest import fixture
from sqlalchemy.orm import Session
from starlette.testclient import TestClient
from mealie.db.db_setup import session_context
@ -12,7 +14,7 @@ from tests.utils import api_routes
from tests.utils.factories import random_string
def build_unique_user(group: str, api_client: TestClient) -> utils.TestUser:
def build_unique_user(session: Session, group: str, api_client: TestClient) -> utils.TestUser:
group = group or random_string(12)
registration = utils.user_registration_factory()
@ -26,19 +28,98 @@ def build_unique_user(group: str, api_client: TestClient) -> utils.TestUser:
user_data = api_client.get(api_routes.users_self, headers=token).json()
assert token is not None
user_id = user_data.get("id")
group_id = user_data.get("groupId")
household_id = user_data.get("householdId")
if not isinstance(user_id, UUID):
user_id = UUID(user_id)
if not isinstance(group_id, UUID):
group_id = UUID(group_id)
if not isinstance(household_id, UUID):
household_id = UUID(household_id)
return utils.TestUser(
_group_id=user_data.get("groupId"),
user_id=user_data.get("id"),
_group_id=group_id,
_household_id=household_id,
user_id=user_id,
email=user_data.get("email"),
username=user_data.get("username"),
password=registration.password,
token=token,
repos=get_repositories(session, group_id=group_id, household_id=household_id),
)
@fixture(scope="module")
def g2_user(admin_token, api_client: TestClient):
def h2_user(session: Session, admin_token, api_client: TestClient, unique_user: utils.TestUser):
"""Another user in the same group as `unique_user`, but in a different household"""
group = api_client.get(api_routes.groups_self, headers=unique_user.token).json()
household_name = random_string(12)
api_client.post(
api_routes.admin_households,
json={
"name": household_name,
"groupId": group["id"],
},
headers=admin_token,
)
user_data = {
"fullName": utils.random_string(),
"username": utils.random_string(),
"email": utils.random_email(),
"password": "useruser",
"group": group["name"],
"household": household_name,
"admin": False,
"tokens": [],
}
response = api_client.post(api_routes.users, json=user_data, headers=admin_token)
assert response.status_code == 201
# Log in as this user
form_data = {"username": user_data["email"], "password": "useruser"}
token = utils.login(form_data, api_client)
self_response = api_client.get(api_routes.users_self, headers=token)
assert self_response.status_code == 200
data = json.loads(self_response.text)
user_id = data["id"]
household_id = data["householdId"]
group_id = data["groupId"]
assert user_id
assert group_id
assert household_id
if not isinstance(user_id, UUID):
user_id = UUID(user_id)
if not isinstance(group_id, UUID):
group_id = UUID(group_id)
if not isinstance(household_id, UUID):
household_id = UUID(household_id)
try:
yield utils.TestUser(
user_id=user_id,
_group_id=group_id,
_household_id=household_id,
token=token,
email=user_data["email"],
username=user_data["username"],
password=user_data["password"],
repos=get_repositories(session, group_id=group_id, household_id=household_id),
)
finally:
# TODO: Delete User after test
pass
@fixture(scope="module")
def g2_user(session: Session, admin_token, api_client: TestClient):
group = random_string(12)
# Create the user
create_data = {
"fullName": utils.random_string(),
@ -46,11 +127,12 @@ def g2_user(admin_token, api_client: TestClient):
"email": utils.random_email(),
"password": "useruser",
"group": group,
"household": "Family",
"admin": False,
"tokens": [],
}
response = api_client.post(api_routes.admin_groups, json={"name": group}, headers=admin_token)
api_client.post(api_routes.admin_groups, json={"name": group}, headers=admin_token)
response = api_client.post(api_routes.users, json=create_data, headers=admin_token)
assert response.status_code == 201
@ -66,15 +148,25 @@ def g2_user(admin_token, api_client: TestClient):
user_id = json.loads(self_response.text).get("id")
group_id = json.loads(self_response.text).get("groupId")
household_id = json.loads(self_response.text).get("householdId")
if not isinstance(user_id, UUID):
user_id = UUID(user_id)
if not isinstance(group_id, UUID):
group_id = UUID(group_id)
if not isinstance(household_id, UUID):
household_id = UUID(household_id)
try:
yield utils.TestUser(
user_id=user_id,
_group_id=group_id,
_household_id=household_id,
token=token,
email=create_data["email"], # type: ignore
username=create_data.get("username"), # type: ignore
password=create_data.get("password"), # type: ignore
repos=get_repositories(session, group_id=group_id, household_id=household_id),
)
finally:
# TODO: Delete User after test
@ -82,7 +174,7 @@ def g2_user(admin_token, api_client: TestClient):
@fixture(scope="module")
def unique_user(api_client: TestClient):
def unique_user(session: Session, api_client: TestClient):
registration = utils.user_registration_factory()
response = api_client.post("/api/users/register", json=registration.model_dump(by_alias=True))
assert response.status_code == 201
@ -94,14 +186,27 @@ def unique_user(api_client: TestClient):
user_data = api_client.get(api_routes.users_self, headers=token).json()
assert token is not None
assert (user_id := user_data.get("id")) is not None
assert (group_id := user_data.get("groupId")) is not None
assert (household_id := user_data.get("householdId")) is not None
if not isinstance(user_id, UUID):
user_id = UUID(user_id)
if not isinstance(group_id, UUID):
group_id = UUID(group_id)
if not isinstance(household_id, UUID):
household_id = UUID(household_id)
try:
yield utils.TestUser(
_group_id=user_data.get("groupId"),
user_id=user_data.get("id"),
_group_id=group_id,
_household_id=household_id,
user_id=user_id,
email=user_data.get("email"),
username=user_data.get("username"),
password=registration.password,
token=token,
repos=get_repositories(session, group_id=group_id, household_id=household_id),
)
finally:
# TODO: Delete User after test
@ -109,8 +214,9 @@ def unique_user(api_client: TestClient):
@fixture(scope="module")
def user_tuple(admin_token, api_client: TestClient) -> Generator[list[utils.TestUser], None, None]:
def user_tuple(session: Session, admin_token, api_client: TestClient) -> Generator[list[utils.TestUser], None, None]:
group_name = utils.random_string()
# Create the user
create_data_1 = {
"fullName": utils.random_string(),
@ -118,6 +224,7 @@ def user_tuple(admin_token, api_client: TestClient) -> Generator[list[utils.Test
"email": utils.random_email(),
"password": "useruser",
"group": group_name,
"household": "Family",
"admin": False,
"tokens": [],
}
@ -128,6 +235,7 @@ def user_tuple(admin_token, api_client: TestClient) -> Generator[list[utils.Test
"email": utils.random_email(),
"password": "useruser",
"group": group_name,
"household": "Family",
"admin": False,
"tokens": [],
}
@ -147,14 +255,27 @@ def user_tuple(admin_token, api_client: TestClient) -> Generator[list[utils.Test
assert response.status_code == 200
user_data = json.loads(response.text)
user_id = user_data.get("id")
group_id = user_data.get("groupId")
household_id = user_data.get("householdId")
if not isinstance(user_id, UUID):
user_id = UUID(user_id)
if not isinstance(group_id, UUID):
group_id = UUID(group_id)
if not isinstance(household_id, UUID):
household_id = UUID(household_id)
users_out.append(
utils.TestUser(
_group_id=user_data.get("groupId"),
user_id=user_data.get("id"),
_group_id=group_id,
_household_id=household_id,
user_id=user_id,
username=user_data.get("username"),
email=user_data.get("email"),
password="useruser",
token=token,
repos=get_repositories(session, group_id=group_id, household_id=household_id),
)
)
@ -164,7 +285,7 @@ def user_tuple(admin_token, api_client: TestClient) -> Generator[list[utils.Test
pass
@fixture(scope="session")
@fixture(scope="module")
def user_token(admin_token, api_client: TestClient):
# Create the user
create_data = {
@ -191,7 +312,7 @@ def ldap_user():
# Create an LDAP user directly instead of using TestClient since we don't have
# a LDAP service set up
with session_context() as session:
db = get_repositories(session)
db = get_repositories(session, group_id=None, household_id=None)
user = db.users.create(
{
"username": utils.random_string(10),
@ -204,5 +325,5 @@ def ldap_user():
)
yield user
with session_context() as session:
db = get_repositories(session)
db = get_repositories(session, group_id=None, household_id=None)
db.users.delete(user.id)

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):

View file

@ -23,7 +23,7 @@ all_cases = [
def test_multitenant_cases_get_all(
api_client: TestClient,
multitenants: MultiTenant,
database: AllRepositories,
unfiltered_database: AllRepositories,
test_case_type: type[ABCMultiTenantTestCase],
):
"""
@ -34,7 +34,7 @@ def test_multitenant_cases_get_all(
user1 = multitenants.user_one
user2 = multitenants.user_two
test_case = test_case_type(database, api_client)
test_case = test_case_type(unfiltered_database, api_client)
with test_case:
expected_ids = test_case.seed_action(user1.group_id)
@ -60,7 +60,7 @@ def test_multitenant_cases_get_all(
def test_multitenant_cases_same_named_resources(
api_client: TestClient,
multitenants: MultiTenant,
database: AllRepositories,
unfiltered_database: AllRepositories,
test_case_type: type[ABCMultiTenantTestCase],
):
"""
@ -71,7 +71,7 @@ def test_multitenant_cases_same_named_resources(
user1 = multitenants.user_one
user2 = multitenants.user_two
test_case = test_case_type(database, api_client)
test_case = test_case_type(unfiltered_database, api_client)
with test_case:
expected_ids, expected_ids2 = test_case.seed_multi(user1.group_id, user2.group_id)

View file

@ -1,9 +0,0 @@
from mealie.repos.repository_factory import AllRepositories
from tests.fixtures.fixture_multitenant import MultiTenant
def test_multitenant_recipe_data_storage(
multitenants: MultiTenant,
database: AllRepositories,
):
pass

View file

@ -1,11 +1,14 @@
from mealie.repos.repository_factory import AllRepositories
from uuid import UUID
from mealie.schema.recipe.recipe import Recipe
from mealie.schema.recipe.recipe_ingredient import RecipeIngredient, SaveIngredientFood
from tests.utils.factories import random_string
from tests.utils.fixture_schemas import TestUser
def test_food_merger(database: AllRepositories, unique_user: TestUser):
def test_food_merger(unique_user: TestUser):
recipe: Recipe | None = None
database = unique_user.repos
slug1 = random_string(10)
food_1 = database.ingredient_foods.create(
@ -25,8 +28,8 @@ def test_food_merger(database: AllRepositories, unique_user: TestUser):
recipe = database.recipes.create(
Recipe(
name=slug1,
user_id=unique_user.group_id,
group_id=unique_user.group_id,
user_id=unique_user.user_id,
group_id=UUID(unique_user.group_id),
recipe_ingredient=[
RecipeIngredient(note="", food=food_1), # type: ignore
RecipeIngredient(note="", food=food_2), # type: ignore
@ -43,6 +46,7 @@ def test_food_merger(database: AllRepositories, unique_user: TestUser):
database.ingredient_foods.merge(food_2.id, food_1.id)
recipe = database.recipes.get_one(recipe.slug)
assert recipe
for ingredient in recipe.recipe_ingredient:
assert ingredient.food.id == food_1.id # type: ignore

View file

@ -4,9 +4,9 @@ from mealie.repos.repository_factory import AllRepositories
from tests.utils.factories import random_int, random_string
def test_create_group_resolve_similar_names(database: AllRepositories):
def test_create_group_resolve_similar_names(unfiltered_database: AllRepositories):
base_group_name = random_string()
groups = database.groups.create_many({"name": base_group_name} for _ in range(random_int(3, 10)))
groups = unfiltered_database.groups.create_many({"name": base_group_name} for _ in range(random_int(3, 10)))
seen_names = set()
seen_slugs = set()
@ -19,17 +19,17 @@ def test_create_group_resolve_similar_names(database: AllRepositories):
assert base_group_name in group.name
def test_group_get_by_slug_or_id(database: AllRepositories):
groups = [database.groups.create({"name": random_string()}) for _ in range(random_int(3, 10))]
def test_group_get_by_slug_or_id(unfiltered_database: AllRepositories):
groups = [unfiltered_database.groups.create({"name": random_string()}) for _ in range(random_int(3, 10))]
for group in groups:
assert database.groups.get_by_slug_or_id(group.id) == group
assert database.groups.get_by_slug_or_id(group.slug) == group
assert unfiltered_database.groups.get_by_slug_or_id(group.id) == group
assert unfiltered_database.groups.get_by_slug_or_id(group.slug) == group
def test_update_group_updates_slug(database: AllRepositories):
group = database.groups.create({"name": random_string()})
def test_update_group_updates_slug(unfiltered_database: AllRepositories):
group = unfiltered_database.groups.create({"name": random_string()})
assert group.slug == slugify(group.name)
new_name = random_string()
group = database.groups.update(group.id, {"name": new_name})
group = unfiltered_database.groups.update(group.id, {"name": new_name})
assert group.slug == slugify(new_name)

View file

@ -12,7 +12,7 @@ from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories
from mealie.repos.repository_units import RepositoryUnit
from mealie.schema.group.group_shopping_list import (
from mealie.schema.household.group_shopping_list import (
ShoppingListItemCreate,
ShoppingListMultiPurposeLabelCreate,
ShoppingListMultiPurposeLabelOut,
@ -64,14 +64,15 @@ def get_label_position_from_label_id(label_id: UUID4, label_settings: list[Shopp
raise Exception("Something went wrong when parsing label settings")
def test_repository_pagination(database: AllRepositories, unique_user: TestUser):
def test_repository_pagination(unique_user: TestUser):
database = unique_user.repos
group = database.groups.get_one(unique_user.group_id)
assert group
seeder = SeederService(database, None, group) # type: ignore
seeder = SeederService(AllRepositories(database.session, group_id=group.id))
seeder.seed_foods("en-US")
foods_repo = database.ingredient_foods.by_group(unique_user.group_id) # type: ignore
foods_repo = database.ingredient_foods
query = PaginationQuery(
page=1,
@ -99,14 +100,15 @@ def test_repository_pagination(database: AllRepositories, unique_user: TestUser)
assert result.id not in seen
def test_pagination_response_and_metadata(database: AllRepositories, unique_user: TestUser):
def test_pagination_response_and_metadata(unique_user: TestUser):
database = unique_user.repos
group = database.groups.get_one(unique_user.group_id)
assert group
seeder = SeederService(database, None, group) # type: ignore
seeder = SeederService(AllRepositories(database.session, group_id=group.id))
seeder.seed_foods("en-US")
foods_repo = database.ingredient_foods.by_group(unique_user.group_id) # type: ignore
foods_repo = database.ingredient_foods
# this should get all results
query = PaginationQuery(
@ -128,14 +130,15 @@ def test_pagination_response_and_metadata(database: AllRepositories, unique_user
assert last_page_of_results.items[-1] == all_results.items[-1]
def test_pagination_guides(database: AllRepositories, unique_user: TestUser):
def test_pagination_guides(unique_user: TestUser):
database = unique_user.repos
group = database.groups.get_one(unique_user.group_id)
assert group
seeder = SeederService(database, None, group) # type: ignore
seeder = SeederService(AllRepositories(database.session, group_id=group.id))
seeder.seed_foods("en-US")
foods_repo = database.ingredient_foods.by_group(unique_user.group_id) # type: ignore
foods_repo = database.ingredient_foods
foods_route = (
"/foods" # this doesn't actually have to be accurate, it's just a placeholder to test for query params
)
@ -173,7 +176,8 @@ def test_pagination_guides(database: AllRepositories, unique_user: TestUser):
@pytest.fixture(scope="function")
def query_units(database: AllRepositories, unique_user: TestUser):
def query_units(unique_user: TestUser):
database = unique_user.repos
unit_1 = database.ingredient_units.create(
SaveIngredientUnit(name="test unit 1", group_id=unique_user.group_id, use_abbreviation=True)
)
@ -193,7 +197,7 @@ def query_units(database: AllRepositories, unique_user: TestUser):
)
unit_ids = [unit.id for unit in [unit_1, unit_2, unit_3]]
units_repo = database.ingredient_units.by_group(unique_user.group_id) # type: ignore
units_repo = database.ingredient_units
yield units_repo, unit_1, unit_2, unit_3
@ -211,7 +215,8 @@ def test_pagination_filter_basic(query_units: tuple[RepositoryUnit, IngredientUn
assert unit_results[0].id == unit_2.id
def test_pagination_filter_null(database: AllRepositories, unique_user: TestUser):
def test_pagination_filter_null(unique_user: TestUser):
database = unique_user.repos
recipe_not_made_1 = database.recipes.create(
Recipe(
user_id=unique_user.user_id,
@ -237,7 +242,7 @@ def test_pagination_filter_null(database: AllRepositories, unique_user: TestUser
)
)
recipe_repo = database.recipes.by_group(unique_user.group_id) # type: ignore
recipe_repo = database.recipes
query = PaginationQuery(page=1, per_page=-1, query_filter="lastMade IS NONE")
recipe_results = recipe_repo.page_all(query).items
@ -300,7 +305,8 @@ def test_pagination_filter_in(query_units: tuple[RepositoryUnit, IngredientUnit,
assert unit_3.id in result_ids
def test_pagination_filter_in_advanced(database: AllRepositories, unique_user: TestUser):
def test_pagination_filter_in_advanced(unique_user: TestUser):
database = unique_user.repos
slug1, slug2 = (random_string(10) for _ in range(2))
tags = [
@ -418,7 +424,8 @@ def test_pagination_filter_like(query_units: tuple[RepositoryUnit, IngredientUni
assert unit_3.id in result_ids
def test_pagination_filter_keyword_namespace_conflict(database: AllRepositories, unique_user: TestUser):
def test_pagination_filter_keyword_namespace_conflict(unique_user: TestUser):
database = unique_user.repos
recipe_rating_1 = database.recipes.create(
Recipe(
user_id=unique_user.user_id,
@ -445,7 +452,7 @@ def test_pagination_filter_keyword_namespace_conflict(database: AllRepositories,
)
)
recipe_repo = database.recipes.by_group(unique_user.group_id) # type: ignore
recipe_repo = database.recipes
# "rating" contains the word "in", but we should not parse this as the keyword "IN"
query = PaginationQuery(page=1, per_page=-1, query_filter="rating > 2")
@ -467,7 +474,8 @@ def test_pagination_filter_keyword_namespace_conflict(database: AllRepositories,
assert recipe_rating_3.id in result_ids
def test_pagination_filter_logical_namespace_conflict(database: AllRepositories, unique_user: TestUser):
def test_pagination_filter_logical_namespace_conflict(unique_user: TestUser):
database = unique_user.repos
categories = [
CategorySave(group_id=unique_user.group_id, name=random_string(10)),
CategorySave(group_id=unique_user.group_id, name=random_string(10)),
@ -509,7 +517,7 @@ def test_pagination_filter_logical_namespace_conflict(database: AllRepositories,
# "recipeCategory" has the substring "or" in it, which shouldn't break queries
query = PaginationQuery(page=1, per_page=-1, query_filter=f'recipeCategory.id = "{category_1.id}"')
recipe_results = database.recipes.by_group(unique_user.group_id).page_all(query).items # type: ignore
recipe_results = database.recipes.page_all(query).items
assert len(recipe_results) == 1
recipe_ids = {recipe.id for recipe in recipe_results}
assert recipe_category_0.id not in recipe_ids
@ -616,9 +624,8 @@ def test_pagination_filter_datetimes(
[OrderDirection.asc, OrderDirection.desc],
ids=["ascending", "descending"],
)
def test_pagination_order_by_multiple(
database: AllRepositories, unique_user: TestUser, order_direction: OrderDirection
):
def test_pagination_order_by_multiple(unique_user: TestUser, order_direction: OrderDirection):
database = unique_user.repos
current_time = datetime.now(timezone.utc)
alphabet = ["a", "b", "c", "d", "e"]
@ -676,11 +683,9 @@ def test_pagination_order_by_multiple(
],
)
def test_pagination_order_by_multiple_directions(
database: AllRepositories,
unique_user: TestUser,
order_by_str: str,
order_direction: OrderDirection,
unique_user: TestUser, order_by_str: str, order_direction: OrderDirection
):
database = unique_user.repos
current_time = datetime.now(timezone.utc)
alphabet = ["a", "b", "c", "d", "e"]
@ -726,9 +731,8 @@ def test_pagination_order_by_multiple_directions(
[OrderDirection.asc, OrderDirection.desc],
ids=["order_ascending", "order_descending"],
)
def test_pagination_order_by_nested_model(
database: AllRepositories, unique_user: TestUser, order_direction: OrderDirection
):
def test_pagination_order_by_nested_model(unique_user: TestUser, order_direction: OrderDirection):
database = unique_user.repos
current_time = datetime.now(timezone.utc)
alphabet = ["a", "b", "c", "d", "e"]
@ -758,7 +762,8 @@ def test_pagination_order_by_nested_model(
assert query.items == sorted_foods
def test_pagination_order_by_doesnt_filter(database: AllRepositories, unique_user: TestUser):
def test_pagination_order_by_doesnt_filter(unique_user: TestUser):
database = unique_user.repos
current_time = datetime.now(timezone.utc)
label = database.group_multi_purpose_labels.create(
@ -771,7 +776,7 @@ def test_pagination_order_by_doesnt_filter(database: AllRepositories, unique_use
SaveIngredientFood(name=random_string(), group_id=unique_user.group_id)
)
query = database.ingredient_foods.by_group(unique_user.group_id).page_all(
query = database.ingredient_foods.page_all(
PaginationQuery(
per_page=-1,
query_filter=f"created_at>{current_time.isoformat()}",
@ -800,11 +805,9 @@ def test_pagination_order_by_doesnt_filter(database: AllRepositories, unique_use
],
)
def test_pagination_order_by_nulls(
database: AllRepositories,
unique_user: TestUser,
null_position: OrderByNullPosition,
order_direction: OrderDirection,
unique_user: TestUser, null_position: OrderByNullPosition, order_direction: OrderDirection
):
database = unique_user.repos
current_time = datetime.now(timezone.utc)
label = database.group_multi_purpose_labels.create(
@ -836,7 +839,9 @@ def test_pagination_order_by_nulls(
assert query.items[1] == food_without_label
def test_pagination_shopping_list_items_with_labels(database: AllRepositories, unique_user: TestUser):
def test_pagination_shopping_list_items_with_labels(unique_user: TestUser):
database = unique_user.repos
# create a shopping list and populate it with some items with labels, and some without labels
shopping_list = database.group_shopping_lists.create(
ShoppingListSave(
@ -926,7 +931,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
for mealplan_to_create in [mealplan_today, mealplan_tomorrow]:
data = mealplan_to_create.model_dump()
data["date"] = data["date"].strftime("%Y-%m-%d")
response = api_client.post(api_routes.groups_mealplans, json=data, headers=unique_user.token)
response = api_client.post(api_routes.households_mealplans, json=data, headers=unique_user.token)
assert response.status_code == 201
## Yesterday
@ -935,7 +940,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
"perPage": -1,
"queryFilter": f"date >= {yesterday.strftime('%Y-%m-%d')}",
}
response = api_client.get(api_routes.groups_mealplans, params=params, headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans, params=params, headers=unique_user.token)
assert response.status_code == 200
response_json = response.json()
@ -949,7 +954,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
"perPage": -1,
"queryFilter": f"date > {yesterday.strftime('%Y-%m-%d')}",
}
response = api_client.get(api_routes.groups_mealplans, params=params, headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans, params=params, headers=unique_user.token)
assert response.status_code == 200
response_json = response.json()
@ -964,7 +969,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
"perPage": -1,
"queryFilter": f"date >= {today.strftime('%Y-%m-%d')}",
}
response = api_client.get(api_routes.groups_mealplans, params=params, headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans, params=params, headers=unique_user.token)
assert response.status_code == 200
response_json = response.json()
@ -978,7 +983,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
"perPage": -1,
"queryFilter": f"date > {today.strftime('%Y-%m-%d')}",
}
response = api_client.get(api_routes.groups_mealplans, params=params, headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans, params=params, headers=unique_user.token)
assert response.status_code == 200
response_json = response.json()
@ -993,7 +998,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
"perPage": -1,
"queryFilter": f"date >= {tomorrow.strftime('%Y-%m-%d')}",
}
response = api_client.get(api_routes.groups_mealplans, params=params, headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans, params=params, headers=unique_user.token)
assert response.status_code == 200
response_json = response.json()
@ -1007,7 +1012,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
"perPage": -1,
"queryFilter": f"date > {tomorrow.strftime('%Y-%m-%d')}",
}
response = api_client.get(api_routes.groups_mealplans, params=params, headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans, params=params, headers=unique_user.token)
assert response.status_code == 200
response_json = response.json()
@ -1019,7 +1024,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
"perPage": -1,
"queryFilter": f"date >= {day_after_tomorrow.strftime('%Y-%m-%d')}",
}
response = api_client.get(api_routes.groups_mealplans, params=params, headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans, params=params, headers=unique_user.token)
assert response.status_code == 200
response_json = response.json()
assert len(response_json["items"]) == 0
@ -1029,7 +1034,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
"perPage": -1,
"queryFilter": f"date > {day_after_tomorrow.strftime('%Y-%m-%d')}",
}
response = api_client.get(api_routes.groups_mealplans, params=params, headers=unique_user.token)
response = api_client.get(api_routes.households_mealplans, params=params, headers=unique_user.token)
assert response.status_code == 200
response_json = response.json()
assert len(response_json["items"]) == 0
@ -1072,7 +1077,8 @@ def test_pagination_filter_advanced(query_units: tuple[RepositoryUnit, Ingredien
assert unit_3.id not in result_ids
def test_pagination_filter_advanced_frontend_sort(database: AllRepositories, unique_user: TestUser):
def test_pagination_filter_advanced_frontend_sort(unique_user: TestUser):
database = unique_user.repos
categories = [
CategorySave(group_id=unique_user.group_id, name=random_string(10)),
CategorySave(group_id=unique_user.group_id, name=random_string(10)),
@ -1174,7 +1180,7 @@ def test_pagination_filter_advanced_frontend_sort(database: AllRepositories, uni
)
)
repo = database.recipes.by_group(unique_user.group_id) # type: ignore
repo = database.recipes
qf = f'recipeCategory.id IN ["{category_1.id}"] AND tools.id IN ["{tool_1.id}"]'
query = PaginationQuery(page=1, per_page=-1, query_filter=qf)

View file

@ -3,9 +3,12 @@ from typing import cast
from uuid import UUID
import pytest
from sqlalchemy.orm import Session
from mealie.repos.all_repositories import get_repositories
from mealie.repos.repository_factory import AllRepositories
from mealie.repos.repository_recipes import RepositoryRecipes
from mealie.schema.household.household import HouseholdCreate
from mealie.schema.recipe import RecipeIngredient, SaveIngredientFood
from mealie.schema.recipe.recipe import Recipe, RecipeCategory, RecipeSummary
from mealie.schema.recipe.recipe_category import CategoryOut, CategorySave, TagSave
@ -17,18 +20,34 @@ from tests.utils.fixture_schemas import TestUser
@pytest.fixture()
def unique_local_group_id(database: AllRepositories) -> str:
return str(database.groups.create(GroupBase(name=random_string())).id)
def unique_local_group_id(unfiltered_database: AllRepositories) -> str:
return str(unfiltered_database.groups.create(GroupBase(name=random_string())).id)
@pytest.fixture()
def unique_local_user_id(database: AllRepositories, unique_local_group_id: str) -> str:
def unique_local_household_id(unfiltered_database: AllRepositories, unique_local_group_id: str) -> str:
database = get_repositories(unfiltered_database.session, group_id=UUID(unique_local_group_id), household_id=None)
return str(
database.households.create(HouseholdCreate(group_id=UUID(unique_local_group_id), name=random_string())).id
)
@pytest.fixture()
def unique_local_user_id(
unfiltered_database: AllRepositories, unique_local_group_id: str, unique_local_household_id: str
) -> str:
database = get_repositories(
unfiltered_database.session, group_id=UUID(unique_local_group_id), household_id=UUID(unique_local_household_id)
)
group = database.groups.get_one(unique_local_group_id)
household = database.households.get_one(unique_local_household_id)
return str(
database.users.create(
{
"username": random_string(),
"email": random_email(),
"group_id": unique_local_group_id,
"group": group.name,
"household": household.name,
"full_name": random_string(),
"password": random_string(),
"admin": False,
@ -38,11 +57,25 @@ def unique_local_user_id(database: AllRepositories, unique_local_group_id: str)
@pytest.fixture()
def search_recipes(database: AllRepositories, unique_local_group_id: str, unique_local_user_id: str) -> list[Recipe]:
def unique_ids(
unique_local_group_id: str, unique_local_household_id: str, unique_local_user_id: str
) -> tuple[str, str, str]:
return unique_local_group_id, unique_local_household_id, unique_local_user_id
@pytest.fixture()
def unique_db(session: Session, unique_ids: tuple[str, str, str]):
group_id, household_id, _ = unique_ids
return get_repositories(session, group_id=group_id, household_id=household_id)
@pytest.fixture()
def search_recipes(unique_db: AllRepositories, unique_ids: tuple[str, str, str]) -> list[Recipe]:
group_id, _, user_id = unique_ids
recipes = [
Recipe(
user_id=unique_local_user_id,
group_id=unique_local_group_id,
group_id=group_id,
user_id=user_id,
name="Steinbock Sloop",
description="My favorite horns are delicious",
recipe_ingredient=[
@ -50,61 +83,63 @@ def search_recipes(database: AllRepositories, unique_local_group_id: str, unique
],
),
Recipe(
user_id=unique_local_user_id,
group_id=unique_local_group_id,
group_id=group_id,
user_id=user_id,
name="Fiddlehead Fern Stir Fry",
recipe_ingredient=[
RecipeIngredient(note="moss"),
],
),
Recipe(
user_id=unique_local_user_id,
group_id=unique_local_group_id,
group_id=group_id,
user_id=user_id,
name="Animal Sloop",
),
# Test diacritics
Recipe(
user_id=unique_local_user_id,
group_id=unique_local_group_id,
group_id=group_id,
user_id=user_id,
name="Rátàtôuile",
),
# Add a bunch of recipes for stable randomization
Recipe(
user_id=unique_local_user_id,
group_id=unique_local_group_id,
group_id=group_id,
user_id=user_id,
name=f"{random_string(10)} soup",
),
Recipe(
user_id=unique_local_user_id,
group_id=unique_local_group_id,
group_id=group_id,
user_id=user_id,
name=f"{random_string(10)} soup",
),
Recipe(
user_id=unique_local_user_id,
group_id=unique_local_group_id,
group_id=group_id,
user_id=user_id,
name=f"{random_string(10)} soup",
),
Recipe(
user_id=unique_local_user_id,
group_id=unique_local_group_id,
group_id=group_id,
user_id=user_id,
name=f"{random_string(10)} soup",
),
Recipe(
user_id=unique_local_user_id,
group_id=unique_local_group_id,
group_id=group_id,
user_id=user_id,
name=f"{random_string(10)} soup",
),
Recipe(
user_id=unique_local_user_id,
group_id=unique_local_group_id,
group_id=group_id,
user_id=user_id,
name=f"{random_string(10)} soup",
),
]
return database.recipes.create_many(recipes)
return unique_db.recipes.create_many(recipes)
def test_recipe_repo_get_by_categories_basic(database: AllRepositories, unique_user: TestUser):
def test_recipe_repo_get_by_categories_basic(unique_user: TestUser):
database = unique_user.repos
# Bootstrap the database with categories
slug1, slug2, slug3 = (random_string(10) for _ in range(3))
@ -149,12 +184,13 @@ def test_recipe_repo_get_by_categories_basic(database: AllRepositories, unique_u
# Get all recipes by category
for category in created_categories:
repo: RepositoryRecipes = database.recipes.by_group(unique_user.group_id) # type: ignore
repo: RepositoryRecipes = database.recipes
recipes = repo.get_by_categories([cast(RecipeCategory, category)])
assert len(recipes) == 5
for recipe in recipes:
assert recipe.recipe_category is not None
found_cat = recipe.recipe_category[0]
assert found_cat.name == category.name
@ -162,7 +198,8 @@ def test_recipe_repo_get_by_categories_basic(database: AllRepositories, unique_u
assert found_cat.id == category.id
def test_recipe_repo_get_by_categories_multi(database: AllRepositories, unique_user: TestUser):
def test_recipe_repo_get_by_categories_multi(unique_user: TestUser):
database = unique_user.repos
slug1, slug2 = (random_string(10) for _ in range(2))
categories = [
@ -204,16 +241,18 @@ def test_recipe_repo_get_by_categories_multi(database: AllRepositories, unique_u
database.recipes.create(recipe)
# Get all recipes by both categories
repo: RepositoryRecipes = database.recipes.by_group(unique_local_group_id) # type: ignore
repo: RepositoryRecipes = database.recipes
by_category = repo.get_by_categories(cast(list[RecipeCategory], created_categories))
assert len(by_category) == 10
for recipe_summary in by_category:
assert recipe_summary.recipe_category is not None
for recipe_category in recipe_summary.recipe_category:
assert recipe_category.id in known_category_ids
def test_recipe_repo_pagination_by_categories(database: AllRepositories, unique_user: TestUser):
def test_recipe_repo_pagination_by_categories(unique_user: TestUser):
database = unique_user.repos
slug1, slug2 = (random_string(10) for _ in range(2))
categories = [
@ -270,6 +309,7 @@ def test_recipe_repo_pagination_by_categories(database: AllRepositories, unique_
assert len(recipes_with_one_category) == 15
for recipe_summary in recipes_with_one_category:
assert recipe_summary.recipe_category is not None
category_ids = [category.id for category in recipe_summary.recipe_category]
assert category_id in category_ids
@ -279,6 +319,7 @@ def test_recipe_repo_pagination_by_categories(database: AllRepositories, unique_
assert len(recipes_with_one_category) == 15
for recipe_summary in recipes_with_one_category:
assert recipe_summary.recipe_category is not None
category_slugs = [category.slug for category in recipe_summary.recipe_category]
assert category_slug in category_slugs
@ -289,6 +330,7 @@ def test_recipe_repo_pagination_by_categories(database: AllRepositories, unique_
assert len(recipes_with_both_categories) == 10
for recipe_summary in recipes_with_both_categories:
assert recipe_summary.recipe_category is not None
category_ids = [category.id for category in recipe_summary.recipe_category]
for category in created_categories:
assert category.id in category_ids
@ -308,7 +350,8 @@ def test_recipe_repo_pagination_by_categories(database: AllRepositories, unique_
assert not all(i == random_ordered[0] for i in random_ordered)
def test_recipe_repo_pagination_by_tags(database: AllRepositories, unique_user: TestUser):
def test_recipe_repo_pagination_by_tags(unique_user: TestUser):
database = unique_user.repos
slug1, slug2 = (random_string(10) for _ in range(2))
tags = [
@ -365,6 +408,7 @@ def test_recipe_repo_pagination_by_tags(database: AllRepositories, unique_user:
assert len(recipes_with_one_tag) == 15
for recipe_summary in recipes_with_one_tag:
assert recipe_summary.tags is not None
tag_ids = [tag.id for tag in recipe_summary.tags]
assert tag_id in tag_ids
@ -374,6 +418,7 @@ def test_recipe_repo_pagination_by_tags(database: AllRepositories, unique_user:
assert len(recipes_with_one_tag) == 15
for recipe_summary in recipes_with_one_tag:
assert recipe_summary.tags is not None
tag_slugs = [tag.slug for tag in recipe_summary.tags]
assert tag_slug in tag_slugs
@ -382,6 +427,7 @@ def test_recipe_repo_pagination_by_tags(database: AllRepositories, unique_user:
assert len(recipes_with_both_tags) == 10
for recipe_summary in recipes_with_both_tags:
assert recipe_summary.tags is not None
tag_ids = [tag.id for tag in recipe_summary.tags]
for tag in created_tags:
assert tag.id in tag_ids
@ -402,7 +448,8 @@ def test_recipe_repo_pagination_by_tags(database: AllRepositories, unique_user:
assert not all(i == random_ordered[0] for i in random_ordered)
def test_recipe_repo_pagination_by_tools(database: AllRepositories, unique_user: TestUser):
def test_recipe_repo_pagination_by_tools(unique_user: TestUser):
database = unique_user.repos
slug1, slug2 = (random_string(10) for _ in range(2))
tools = [
@ -498,7 +545,8 @@ def test_recipe_repo_pagination_by_tools(database: AllRepositories, unique_user:
assert not all(i == random_ordered[0] for i in random_ordered)
def test_recipe_repo_pagination_by_foods(database: AllRepositories, unique_user: TestUser):
def test_recipe_repo_pagination_by_foods(unique_user: TestUser):
database = unique_user.repos
slug1, slug2 = (random_string(10) for _ in range(2))
foods = [
@ -606,13 +654,12 @@ def test_recipe_repo_pagination_by_foods(database: AllRepositories, unique_user:
],
)
def test_basic_recipe_search(
unique_db: AllRepositories,
search: str,
expected_names: list[str],
database: AllRepositories,
search_recipes: list[Recipe], # required so database is populated
unique_local_group_id: str,
):
repo = database.recipes.by_group(unique_local_group_id) # type: ignore
repo = unique_db.recipes
pagination = PaginationQuery(page=1, per_page=-1, order_by="created_at", order_direction=OrderDirection.asc)
results = repo.page_all(pagination, search=search).items
@ -626,15 +673,14 @@ def test_basic_recipe_search(
def test_fuzzy_recipe_search(
database: AllRepositories,
unique_db: AllRepositories,
search_recipes: list[Recipe], # required so database is populated
unique_local_group_id: str,
):
# this only works on postgres
if database.session.get_bind().name != "postgresql":
if unique_db.session.get_bind().name != "postgresql":
return
repo = database.recipes.by_group(unique_local_group_id) # type: ignore
repo = unique_db.recipes
pagination = PaginationQuery(page=1, per_page=-1, order_by="created_at", order_direction=OrderDirection.asc)
results = repo.page_all(pagination, search="Steinbuck").items
@ -642,11 +688,10 @@ def test_fuzzy_recipe_search(
def test_random_order_recipe_search(
database: AllRepositories,
unique_db: AllRepositories,
search_recipes: list[Recipe], # required so database is populated
unique_local_group_id: str,
):
repo = database.recipes.by_group(unique_local_group_id) # type: ignore
repo = unique_db.recipes
pagination = PaginationQuery(
page=1,
per_page=-1,
@ -661,15 +706,16 @@ def test_random_order_recipe_search(
assert not all(i == random_ordered[0] for i in random_ordered)
def test_order_by_rating(database: AllRepositories, user_tuple: tuple[TestUser, TestUser]):
def test_order_by_rating(user_tuple: tuple[TestUser, TestUser]):
user_1, user_2 = user_tuple
repo = database.recipes.by_group(UUID(user_1.group_id))
database = user_1.repos
repo = database.recipes
recipes: list[Recipe] = []
for i in range(3):
slug = f"recipe-{i+1}-{random_string(5)}"
recipes.append(
database.recipes.create(
repo.create(
Recipe(
user_id=user_1.user_id,
group_id=user_1.group_id,
@ -768,7 +814,7 @@ def test_order_by_rating(database: AllRepositories, user_tuple: tuple[TestUser,
)
pq = PaginationQuery(page=1, per_page=-1, order_by="rating", order_direction=OrderDirection.desc)
data = database.recipes.by_group(UUID(user_1.group_id)).page_all(pq).items
data = database.recipes.page_all(pq).items
assert len(data) == 3
assert data[0].slug == recipe_1.slug # global rating == 4.25 (avg of 5 and 3.5)
@ -776,7 +822,7 @@ def test_order_by_rating(database: AllRepositories, user_tuple: tuple[TestUser,
assert data[2].slug == recipe_2.slug # global rating == 2.5 (avg of 4 and 1)
pq = PaginationQuery(page=1, per_page=-1, order_by="rating", order_direction=OrderDirection.asc)
data = database.recipes.by_group(UUID(user_1.group_id)).page_all(pq).items
data = database.recipes.page_all(pq).items
assert len(data) == 3
assert data[0].slug == recipe_2.slug # global rating == 2.5 (avg of 4 and 1)

View file

@ -1,7 +1,9 @@
from datetime import datetime, timezone
import pytest
from sqlalchemy.orm import Session
from mealie.repos.all_repositories import get_repositories
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe_ingredient import IngredientUnit, SaveIngredientUnit
from mealie.schema.response.pagination import OrderDirection, PaginationQuery
@ -10,12 +12,17 @@ from tests.utils.factories import random_int, random_string
@pytest.fixture()
def unique_local_group_id(database: AllRepositories) -> str:
return str(database.groups.create(GroupBase(name=random_string())).id)
def unique_local_group_id(unfiltered_database: AllRepositories) -> str:
return str(unfiltered_database.groups.create(GroupBase(name=random_string())).id)
@pytest.fixture()
def search_units(database: AllRepositories, unique_local_group_id: str) -> list[IngredientUnit]:
def unique_db(session: Session, unique_local_group_id: str):
return get_repositories(session, group_id=unique_local_group_id)
@pytest.fixture()
def search_units(unique_db: AllRepositories, unique_local_group_id: str) -> list[IngredientUnit]:
units = [
SaveIngredientUnit(
group_id=unique_local_group_id,
@ -57,7 +64,7 @@ def search_units(database: AllRepositories, unique_local_group_id: str) -> list[
]
)
return database.ingredient_units.create_many(units)
return unique_db.ingredient_units.create_many(units)
@pytest.mark.parametrize(
@ -82,11 +89,10 @@ def search_units(database: AllRepositories, unique_local_group_id: str) -> list[
def test_basic_search(
search: str,
expected_names: list[str],
database: AllRepositories,
unique_db: AllRepositories,
search_units: list[IngredientUnit], # required so database is populated
unique_local_group_id: str,
):
repo = database.ingredient_units.by_group(unique_local_group_id)
repo = unique_db.ingredient_units
pagination = PaginationQuery(page=1, per_page=-1, order_by="created_at", order_direction=OrderDirection.asc)
results = repo.page_all(pagination, search=search).items
@ -100,15 +106,14 @@ def test_basic_search(
def test_fuzzy_search(
database: AllRepositories,
unique_db: AllRepositories,
search_units: list[IngredientUnit], # required so database is populated
unique_local_group_id: str,
):
# this only works on postgres
if database.session.get_bind().name != "postgresql":
if unique_db.session.get_bind().name != "postgresql":
return
repo = database.ingredient_units.by_group(unique_local_group_id)
repo = unique_db.ingredient_units
pagination = PaginationQuery(page=1, per_page=-1, order_by="created_at", order_direction=OrderDirection.asc)
results = repo.page_all(pagination, search="tabel spoone").items
@ -116,11 +121,10 @@ def test_fuzzy_search(
def test_random_order_search(
database: AllRepositories,
unique_db: AllRepositories,
search_units: list[IngredientUnit], # required so database is populated
unique_local_group_id: str,
):
repo = database.ingredient_units.by_group(unique_local_group_id)
repo = unique_db.ingredient_units
pagination = PaginationQuery(
page=1,
per_page=-1,

View file

@ -1,11 +1,14 @@
from mealie.repos.repository_factory import AllRepositories
from uuid import UUID
from mealie.schema.recipe.recipe import Recipe
from mealie.schema.recipe.recipe_ingredient import RecipeIngredient, SaveIngredientUnit
from tests.utils.factories import random_string
from tests.utils.fixture_schemas import TestUser
def test_unit_merger(database: AllRepositories, unique_user: TestUser):
def test_unit_merger(unique_user: TestUser):
database = unique_user.repos
recipe: Recipe | None = None
slug1 = random_string(10)
unit_1 = database.ingredient_units.create(
@ -25,8 +28,8 @@ def test_unit_merger(database: AllRepositories, unique_user: TestUser):
recipe = database.recipes.create(
Recipe(
name=slug1,
user_id=unique_user.group_id,
group_id=unique_user.group_id,
user_id=unique_user.user_id,
group_id=UUID(unique_user.group_id),
recipe_ingredient=[
RecipeIngredient(note="", unit=unit_1), # type: ignore
RecipeIngredient(note="", unit=unit_2), # type: ignore
@ -35,6 +38,7 @@ def test_unit_merger(database: AllRepositories, unique_user: TestUser):
)
# Santiy check make sure recipe got created
assert recipe.id is not None
for ing in recipe.recipe_ingredient:
@ -43,6 +47,7 @@ def test_unit_merger(database: AllRepositories, unique_user: TestUser):
database.ingredient_units.merge(unit_2.id, unit_1.id)
recipe = database.recipes.get_one(recipe.slug)
assert recipe
for ingredient in recipe.recipe_ingredient:
assert ingredient.unit.id == unit_1.id # type: ignore

View file

@ -1,9 +1,9 @@
from mealie.repos.all_repositories import AllRepositories
from mealie.schema.user import PrivateUser
from tests.utils.fixture_schemas import TestUser
def test_user_directory_deleted_on_delete(database: AllRepositories, unique_user: TestUser) -> None:
def test_user_directory_deleted_on_delete(unique_user: TestUser) -> None:
database = unique_user.repos
user_dir = PrivateUser.get_directory(unique_user.user_id)
assert user_dir.exists()
database.users.delete(unique_user.user_id)

View file

@ -1,4 +1,6 @@
from mealie.schema.group.group_shopping_list import ShoppingListItemOut
import uuid
from mealie.schema.household.group_shopping_list import ShoppingListItemOut
def test_shopping_list_ingredient_validation():
@ -15,7 +17,7 @@ def test_shopping_list_ingredient_validation():
"aliases": [],
"label": None,
"createdAt": "2024-02-26T18:29:46.190754",
"updateAt": "2024-02-26T18:29:46.190758",
"updatedAt": "2024-02-26T18:29:46.190758",
},
"note": "",
"isFood": True,
@ -30,8 +32,10 @@ def test_shopping_list_ingredient_validation():
"id": "80f4df25-6139-4d30-be0c-4100f50e5396",
"label": None,
"recipeReferences": [],
"groupId": uuid.uuid4(),
"householdId": uuid.uuid4(),
"createdAt": "2024-02-27T10:18:19.274677",
"updateAt": "2024-02-27T11:26:32.643392",
"updatedAt": "2024-02-27T11:26:32.643392",
}
out = ShoppingListItemOut.model_validate(db_obj)
assert out.display == "8 bell peppers"

View file

@ -11,7 +11,7 @@ from mealie.core.config import get_app_settings
from mealie.db.db_setup import session_context
from mealie.db.models._model_utils.guid import GUID
from mealie.db.models.group import Group
from mealie.db.models.group.shopping_list import ShoppingList
from mealie.db.models.household.shopping_list import ShoppingList
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
from mealie.db.models.recipe.recipe import RecipeModel

View file

@ -40,7 +40,7 @@ def test_new_mealplan_event(api_client: TestClient, unique_user: TestUser):
new_plan["date"] = datetime.now(timezone.utc).date().isoformat()
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)
assert response.status_code == 201
# run the task and check to make sure a new event was created from the mealplan
@ -70,7 +70,7 @@ def test_new_mealplan_event(api_client: TestClient, unique_user: TestUser):
# make sure nothing else was updated
for data in [original_recipe_data, new_recipe_data]:
data.pop("dateUpdated")
data.pop("updateAt")
data.pop("updatedAt")
data.pop("lastMade")
# instructions ids are generated randomly and aren't consistent between get requests
@ -107,7 +107,7 @@ def test_new_mealplan_event_duplicates(api_client: TestClient, unique_user: Test
new_plan["date"] = datetime.now(timezone.utc).date().isoformat()
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)
assert response.status_code == 201
# run the task multiple times and make sure we only create one event
@ -153,7 +153,7 @@ def test_new_mealplan_events_with_multiple_recipes(api_client: TestClient, uniqu
new_plan["date"] = datetime.now(timezone.utc).date().isoformat()
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)
assert response.status_code == 201
mealplan_count_by_recipe_id[recipe.id] += 1 # type: ignore
@ -213,7 +213,7 @@ def test_preserve_future_made_date(api_client: TestClient, unique_user: TestUser
new_plan["date"] = datetime.now(timezone.utc).date().isoformat()
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)
assert response.status_code == 201
# run the task and make sure the recipe's last made date was not updated

View file

@ -1,7 +1,6 @@
from datetime import datetime, timezone
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.group.group_shopping_list import ShoppingListItemCreate, ShoppingListItemOut, ShoppingListSave
from mealie.schema.household.group_shopping_list import ShoppingListItemCreate, ShoppingListItemOut, ShoppingListSave
from mealie.services.scheduler.tasks.delete_old_checked_shopping_list_items import (
MAX_CHECKED_ITEMS,
delete_old_checked_list_items,
@ -10,12 +9,17 @@ from tests.utils.factories import random_int, random_string
from tests.utils.fixture_schemas import TestUser
def test_cleanup(database: AllRepositories, unique_user: TestUser):
list_repo = database.group_shopping_lists.by_group(unique_user.group_id)
def test_cleanup(unique_user: TestUser):
database = unique_user.repos
list_repo = database.group_shopping_lists
list_item_repo = database.group_shopping_list_item
shopping_list = list_repo.create(
ShoppingListSave(name=random_string(), group_id=unique_user.group_id, user_id=unique_user.user_id)
ShoppingListSave(
name=random_string(),
group_id=unique_user.group_id,
user_id=unique_user.user_id,
)
)
unchecked_items = list_item_repo.create_many(
[
@ -40,12 +44,13 @@ def test_cleanup(database: AllRepositories, unique_user: TestUser):
for item in unchecked_items + checked_items:
assert item in shopping_list.list_items
checked_items.sort(key=lambda x: x.update_at or datetime.now(timezone.utc), reverse=True)
checked_items.sort(key=lambda x: x.updated_at or datetime.now(timezone.utc), reverse=True)
expected_kept_items = unchecked_items + checked_items[:MAX_CHECKED_ITEMS]
expected_deleted_items = checked_items[MAX_CHECKED_ITEMS:]
# make sure we only see the expected items
delete_old_checked_list_items()
database.session.commit()
shopping_list = list_repo.get_one(shopping_list.id) # type: ignore
assert shopping_list
assert len(shopping_list.list_items) == len(expected_kept_items)
@ -55,12 +60,17 @@ def test_cleanup(database: AllRepositories, unique_user: TestUser):
assert item not in shopping_list.list_items
def test_no_cleanup(database: AllRepositories, unique_user: TestUser):
list_repo = database.group_shopping_lists.by_group(unique_user.group_id)
def test_no_cleanup(unique_user: TestUser):
database = unique_user.repos
list_repo = database.group_shopping_lists
list_item_repo = database.group_shopping_list_item
shopping_list = list_repo.create(
ShoppingListSave(name=random_string(), group_id=unique_user.group_id, user_id=unique_user.user_id)
ShoppingListSave(
name=random_string(),
group_id=unique_user.group_id,
user_id=unique_user.user_id,
)
)
unchecked_items = list_item_repo.create_many(
[
@ -87,6 +97,7 @@ def test_no_cleanup(database: AllRepositories, unique_user: TestUser):
# make sure we still see all items
delete_old_checked_list_items()
database.session.commit()
shopping_list = list_repo.get_one(shopping_list.id) # type: ignore
assert shopping_list
assert len(shopping_list.list_items) == len(unchecked_items) + len(checked_items)

View file

@ -1,9 +1,9 @@
from datetime import datetime, timedelta, timezone
from uuid import UUID
from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.group.webhook import SaveWebhook, WebhookType
from mealie.schema.household.webhook import SaveWebhook, WebhookType
from mealie.services.event_bus_service.event_bus_listeners import WebhookEventListener
from tests.utils import random_string
from tests.utils.factories import random_bool
@ -12,6 +12,7 @@ from tests.utils.fixture_schemas import TestUser
def webhook_factory(
group_id: str | UUID4,
household_id: str | UUID4,
enabled: bool = True,
name: str = "",
url: str = "",
@ -25,22 +26,27 @@ def webhook_factory(
webhook_type=webhook_type,
scheduled_time=scheduled_time.time() if scheduled_time else datetime.now(timezone.utc).time(),
group_id=group_id,
household_id=household_id,
)
def test_get_scheduled_webhooks_filter_query(database: AllRepositories, unique_user: TestUser):
def test_get_scheduled_webhooks_filter_query(unique_user: TestUser):
"""
get_scheduled_webhooks_test tests the get_scheduled_webhooks function on the webhook event bus listener.
"""
database = unique_user.repos
expected: list[SaveWebhook] = []
start = datetime.now(timezone.utc)
for _ in range(5):
new_item = webhook_factory(group_id=unique_user.group_id, enabled=random_bool())
new_item = webhook_factory(
group_id=unique_user.group_id, household_id=unique_user.household_id, enabled=random_bool()
)
out_of_range_item = webhook_factory(
group_id=unique_user.group_id,
household_id=unique_user.household_id,
enabled=random_bool(),
scheduled_time=(start - timedelta(minutes=20)),
)
@ -51,7 +57,7 @@ def test_get_scheduled_webhooks_filter_query(database: AllRepositories, unique_u
if new_item.enabled:
expected.append(new_item)
event_bus_listener = WebhookEventListener(unique_user.group_id) # type: ignore
event_bus_listener = WebhookEventListener(UUID(unique_user.group_id), UUID(unique_user.household_id))
results = event_bus_listener.get_scheduled_webhooks(start, datetime.now(timezone.utc) + timedelta(minutes=5))
assert len(results) == len(expected)

View file

@ -1,7 +1,7 @@
import tempfile
from pathlib import Path
from uuid import UUID
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe import Recipe
from mealie.services.recipe.recipe_bulk_service import RecipeBulkActionsService
from mealie.services.scheduler.tasks.purge_group_exports import purge_group_data_exports
@ -9,7 +9,9 @@ from tests.utils.factories import random_int, random_string
from tests.utils.fixture_schemas import TestUser
def test_purge_group_exports(database: AllRepositories, unique_user: TestUser):
def test_purge_group_exports(unique_user: TestUser):
database = unique_user.repos
# create the export
group = database.groups.get_one(unique_user.group_id)
assert group
@ -17,7 +19,14 @@ def test_purge_group_exports(database: AllRepositories, unique_user: TestUser):
assert user
recipe_exporter = RecipeBulkActionsService(database, user, group)
recipes = [
database.recipes.create(Recipe(name=random_string(), group_id=group.id)) for _ in range(random_int(2, 5))
database.recipes.create(
Recipe(
name=random_string(),
group_id=UUID(unique_user.group_id),
user_id=unique_user.user_id,
)
)
for _ in range(random_int(2, 5))
]
with tempfile.NamedTemporaryFile() as tmpfile:

View file

@ -5,14 +5,16 @@ from mealie.services.user_services.user_service import UserService
from tests.utils.fixture_schemas import TestUser
def test_get_locked_users(database: AllRepositories, user_tuple: list[TestUser]) -> None:
def test_get_locked_users(user_tuple: list[TestUser]) -> None:
usr_1, usr_2 = user_tuple
database = usr_1.repos
# Setup
user_service = UserService(database)
user_1 = database.users.get_one(usr_1.user_id)
user_2 = database.users.get_one(usr_2.user_id)
assert user_1 and user_2
locked_users = user_service.get_locked_users()
assert len(locked_users) == 0
@ -41,11 +43,13 @@ def test_get_locked_users(database: AllRepositories, user_tuple: list[TestUser])
user_service.unlock_user(user_2)
def test_lock_unlocker_user(database: AllRepositories, unique_user: TestUser) -> None:
def test_lock_unlocker_user(unique_user: TestUser) -> None:
database = unique_user.repos
user_service = UserService(database)
# Test that the user is unlocked
user = database.users.get_one(unique_user.user_id)
assert user
assert not user.locked_at
# Test that the user is locked
@ -63,11 +67,13 @@ def test_lock_unlocker_user(database: AllRepositories, unique_user: TestUser) ->
assert not user.is_locked
def test_reset_locked_users(database: AllRepositories, unique_user: TestUser) -> None:
def test_reset_locked_users(unique_user: TestUser) -> None:
database = unique_user.repos
user_service = UserService(database)
# Test that the user is unlocked
user = database.users.get_one(unique_user.user_id)
assert user
assert not user.is_locked
assert not user.locked_at
@ -80,6 +86,7 @@ def test_reset_locked_users(database: AllRepositories, unique_user: TestUser) ->
# Test that the locked user is not unlocked by reset
unlocked = user_service.reset_locked_users()
user = database.users.get_one(unique_user.user_id)
assert user
assert unlocked == 0
assert user.is_locked
assert user.login_attemps == 5
@ -89,6 +96,7 @@ def test_reset_locked_users(database: AllRepositories, unique_user: TestUser) ->
database.users.update(user.id, user)
unlocked = user_service.reset_locked_users()
user = database.users.get_one(unique_user.user_id)
assert user
assert unlocked == 1
assert not user.is_locked
assert user.login_attemps == 0

View file

@ -6,8 +6,10 @@ from fractions import Fraction
import pytest
from pydantic import UUID4
from sqlalchemy.orm import Session
from mealie.db.db_setup import session_context
from mealie.repos.all_repositories import get_repositories
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.openai.recipe_ingredient import OpenAIIngredient, OpenAIIngredients
from mealie.schema.recipe.recipe_ingredient import (
@ -56,15 +58,20 @@ def build_parsed_ing(food: str | None, unit: str | None) -> ParsedIngredient:
@pytest.fixture()
def unique_local_group_id(database: AllRepositories) -> UUID4:
return str(database.groups.create(GroupBase(name=random_string())).id)
def unique_local_group_id(unfiltered_database: AllRepositories) -> UUID4:
return str(unfiltered_database.groups.create(GroupBase(name=random_string())).id)
@pytest.fixture()
def unique_db(session: Session, unique_local_group_id: str):
return get_repositories(session, group_id=unique_local_group_id)
@pytest.fixture()
def parsed_ingredient_data(
database: AllRepositories, unique_local_group_id: UUID4
unique_db: AllRepositories, unique_local_group_id: UUID4
) -> tuple[list[IngredientFood], list[IngredientUnit]]:
foods = database.ingredient_foods.create_many(
foods = unique_db.ingredient_foods.create_many(
[
SaveIngredientFood(name="potatoes", group_id=unique_local_group_id),
SaveIngredientFood(name="onion", group_id=unique_local_group_id),
@ -85,7 +92,7 @@ def parsed_ingredient_data(
)
foods.extend(
database.ingredient_foods.create_many(
unique_db.ingredient_foods.create_many(
[
SaveIngredientFood(name=f"{random_string()} food", group_id=unique_local_group_id)
for _ in range(random_int(10, 15))
@ -93,7 +100,7 @@ def parsed_ingredient_data(
)
)
units = database.ingredient_units.create_many(
units = unique_db.ingredient_units.create_many(
[
SaveIngredientUnit(name="Cups", group_id=unique_local_group_id),
SaveIngredientUnit(name="Tablespoon", group_id=unique_local_group_id),
@ -116,7 +123,7 @@ def parsed_ingredient_data(
)
units.extend(
database.ingredient_foods.create_many(
unique_db.ingredient_foods.create_many(
[
SaveIngredientUnit(name=f"{random_string()} unit", group_id=unique_local_group_id)
for _ in range(random_int(10, 15))

View file

@ -6,6 +6,7 @@ from mealie.schema.user.registration import CreateUserRegistration
def test_create_user_registration() -> None:
CreateUserRegistration(
group="Home",
household="Family",
group_token=None,
email="SomeValidEmail@example.com",
username="SomeValidUsername",
@ -18,6 +19,7 @@ def test_create_user_registration() -> None:
CreateUserRegistration(
group=None,
household=None,
group_token="asdfadsfasdfasdfasdf",
email="SomeValidEmail@example.com",
username="SomeValidUsername",
@ -29,11 +31,14 @@ def test_create_user_registration() -> None:
)
@pytest.mark.parametrize("group, group_token", [(None, None), ("", None), (None, "")])
def test_group_or_token_validator(group, group_token) -> None:
@pytest.mark.parametrize("group", [None, ""])
@pytest.mark.parametrize("household", [None, ""])
@pytest.mark.parametrize("group_token", [None, ""])
def test_group_or_token_validator(group, household, group_token) -> None:
with pytest.raises(ValueError):
CreateUserRegistration(
group=group,
household=household,
group_token=group_token,
email="SomeValidEmail@example.com",
username="SomeValidUsername",
@ -62,6 +67,7 @@ def test_password_validator() -> None:
with pytest.raises(ValueError):
CreateUserRegistration(
group=None,
household=None,
group_token="asdfadsfasdfasdfasdf",
email="SomeValidEmail@example.com",
username="SomeValidUsername",
@ -71,6 +77,3 @@ def test_password_validator() -> None:
advanced=False,
private=True,
)
test_create_user_registration()

View file

@ -7,8 +7,6 @@ admin_about_check = "/api/admin/about/check"
"""`/api/admin/about/check`"""
admin_about_statistics = "/api/admin/about/statistics"
"""`/api/admin/about/statistics`"""
admin_analytics = "/api/admin/analytics"
"""`/api/admin/analytics`"""
admin_backups = "/api/admin/backups"
"""`/api/admin/backups`"""
admin_backups_upload = "/api/admin/backups/upload"
@ -17,6 +15,8 @@ admin_email = "/api/admin/email"
"""`/api/admin/email`"""
admin_groups = "/api/admin/groups"
"""`/api/admin/groups`"""
admin_households = "/api/admin/households"
"""`/api/admin/households`"""
admin_maintenance = "/api/admin/maintenance"
"""`/api/admin/maintenance`"""
admin_maintenance_clean_images = "/api/admin/maintenance/clean/images"
@ -53,36 +53,16 @@ foods = "/api/foods"
"""`/api/foods`"""
foods_merge = "/api/foods/merge"
"""`/api/foods/merge`"""
groups_categories = "/api/groups/categories"
"""`/api/groups/categories`"""
groups_cookbooks = "/api/groups/cookbooks"
"""`/api/groups/cookbooks`"""
groups_events_notifications = "/api/groups/events/notifications"
"""`/api/groups/events/notifications`"""
groups_invitations = "/api/groups/invitations"
"""`/api/groups/invitations`"""
groups_invitations_email = "/api/groups/invitations/email"
"""`/api/groups/invitations/email`"""
groups_households = "/api/groups/households"
"""`/api/groups/households`"""
groups_labels = "/api/groups/labels"
"""`/api/groups/labels`"""
groups_mealplans = "/api/groups/mealplans"
"""`/api/groups/mealplans`"""
groups_mealplans_random = "/api/groups/mealplans/random"
"""`/api/groups/mealplans/random`"""
groups_mealplans_rules = "/api/groups/mealplans/rules"
"""`/api/groups/mealplans/rules`"""
groups_mealplans_today = "/api/groups/mealplans/today"
"""`/api/groups/mealplans/today`"""
groups_members = "/api/groups/members"
"""`/api/groups/members`"""
groups_migrations = "/api/groups/migrations"
"""`/api/groups/migrations`"""
groups_permissions = "/api/groups/permissions"
"""`/api/groups/permissions`"""
groups_preferences = "/api/groups/preferences"
"""`/api/groups/preferences`"""
groups_recipe_actions = "/api/groups/recipe-actions"
"""`/api/groups/recipe-actions`"""
groups_reports = "/api/groups/reports"
"""`/api/groups/reports`"""
groups_seeders_foods = "/api/groups/seeders/foods"
@ -93,20 +73,46 @@ groups_seeders_units = "/api/groups/seeders/units"
"""`/api/groups/seeders/units`"""
groups_self = "/api/groups/self"
"""`/api/groups/self`"""
groups_shopping_items = "/api/groups/shopping/items"
"""`/api/groups/shopping/items`"""
groups_shopping_items_create_bulk = "/api/groups/shopping/items/create-bulk"
"""`/api/groups/shopping/items/create-bulk`"""
groups_shopping_lists = "/api/groups/shopping/lists"
"""`/api/groups/shopping/lists`"""
groups_statistics = "/api/groups/statistics"
"""`/api/groups/statistics`"""
groups_storage = "/api/groups/storage"
"""`/api/groups/storage`"""
groups_webhooks = "/api/groups/webhooks"
"""`/api/groups/webhooks`"""
groups_webhooks_rerun = "/api/groups/webhooks/rerun"
"""`/api/groups/webhooks/rerun`"""
households_cookbooks = "/api/households/cookbooks"
"""`/api/households/cookbooks`"""
households_events_notifications = "/api/households/events/notifications"
"""`/api/households/events/notifications`"""
households_invitations = "/api/households/invitations"
"""`/api/households/invitations`"""
households_invitations_email = "/api/households/invitations/email"
"""`/api/households/invitations/email`"""
households_mealplans = "/api/households/mealplans"
"""`/api/households/mealplans`"""
households_mealplans_random = "/api/households/mealplans/random"
"""`/api/households/mealplans/random`"""
households_mealplans_rules = "/api/households/mealplans/rules"
"""`/api/households/mealplans/rules`"""
households_mealplans_today = "/api/households/mealplans/today"
"""`/api/households/mealplans/today`"""
households_members = "/api/households/members"
"""`/api/households/members`"""
households_permissions = "/api/households/permissions"
"""`/api/households/permissions`"""
households_preferences = "/api/households/preferences"
"""`/api/households/preferences`"""
households_recipe_actions = "/api/households/recipe-actions"
"""`/api/households/recipe-actions`"""
households_self = "/api/households/self"
"""`/api/households/self`"""
households_shopping_items = "/api/households/shopping/items"
"""`/api/households/shopping/items`"""
households_shopping_items_create_bulk = "/api/households/shopping/items/create-bulk"
"""`/api/households/shopping/items/create-bulk`"""
households_shopping_lists = "/api/households/shopping/lists"
"""`/api/households/shopping/lists`"""
households_statistics = "/api/households/statistics"
"""`/api/households/statistics`"""
households_webhooks = "/api/households/webhooks"
"""`/api/households/webhooks`"""
households_webhooks_rerun = "/api/households/webhooks/rerun"
"""`/api/households/webhooks/rerun`"""
media_docker_validate_txt = "/api/media/docker/validate.txt"
"""`/api/media/docker/validate.txt`"""
organizers_categories = "/api/organizers/categories"
@ -149,10 +155,6 @@ recipes_create_url_bulk = "/api/recipes/create-url/bulk"
"""`/api/recipes/create-url/bulk`"""
recipes_exports = "/api/recipes/exports"
"""`/api/recipes/exports`"""
recipes_summary_uncategorized = "/api/recipes/summary/uncategorized"
"""`/api/recipes/summary/uncategorized`"""
recipes_summary_untagged = "/api/recipes/summary/untagged"
"""`/api/recipes/summary/untagged`"""
recipes_test_scrape_url = "/api/recipes/test-scrape-url"
"""`/api/recipes/test-scrape-url`"""
recipes_timeline_events = "/api/recipes/timeline/events"
@ -169,8 +171,6 @@ users_api_tokens = "/api/users/api-tokens"
"""`/api/users/api-tokens`"""
users_forgot_password = "/api/users/forgot-password"
"""`/api/users/forgot-password`"""
users_group_users = "/api/users/group-users"
"""`/api/users/group-users`"""
users_password = "/api/users/password"
"""`/api/users/password`"""
users_register = "/api/users/register"
@ -187,6 +187,8 @@ utils_download = "/api/utils/download"
"""`/api/utils/download`"""
validators_group = "/api/validators/group"
"""`/api/validators/group`"""
validators_household = "/api/validators/household"
"""`/api/validators/household`"""
validators_recipe = "/api/validators/recipe"
"""`/api/validators/recipe`"""
validators_user_email = "/api/validators/user/email"
@ -210,6 +212,11 @@ def admin_groups_item_id(item_id):
return f"{prefix}/admin/groups/{item_id}"
def admin_households_item_id(item_id):
"""`/api/admin/households/{item_id}`"""
return f"{prefix}/admin/households/{item_id}"
def admin_users_item_id(item_id):
"""`/api/admin/users/{item_id}`"""
return f"{prefix}/admin/users/{item_id}"
@ -220,64 +227,64 @@ def comments_item_id(item_id):
return f"{prefix}/comments/{item_id}"
def explore_cookbooks_group_slug(group_slug):
"""`/api/explore/cookbooks/{group_slug}`"""
return f"{prefix}/explore/cookbooks/{group_slug}"
def explore_groups_group_slug_cookbooks(group_slug):
"""`/api/explore/groups/{group_slug}/cookbooks`"""
return f"{prefix}/explore/groups/{group_slug}/cookbooks"
def explore_cookbooks_group_slug_item_id(group_slug, item_id):
"""`/api/explore/cookbooks/{group_slug}/{item_id}`"""
return f"{prefix}/explore/cookbooks/{group_slug}/{item_id}"
def explore_groups_group_slug_cookbooks_item_id(group_slug, item_id):
"""`/api/explore/groups/{group_slug}/cookbooks/{item_id}`"""
return f"{prefix}/explore/groups/{group_slug}/cookbooks/{item_id}"
def explore_foods_group_slug(group_slug):
"""`/api/explore/foods/{group_slug}`"""
return f"{prefix}/explore/foods/{group_slug}"
def explore_groups_group_slug_foods(group_slug):
"""`/api/explore/groups/{group_slug}/foods`"""
return f"{prefix}/explore/groups/{group_slug}/foods"
def explore_foods_group_slug_item_id(group_slug, item_id):
"""`/api/explore/foods/{group_slug}/{item_id}`"""
return f"{prefix}/explore/foods/{group_slug}/{item_id}"
def explore_groups_group_slug_foods_item_id(group_slug, item_id):
"""`/api/explore/groups/{group_slug}/foods/{item_id}`"""
return f"{prefix}/explore/groups/{group_slug}/foods/{item_id}"
def explore_organizers_group_slug_categories(group_slug):
"""`/api/explore/organizers/{group_slug}/categories`"""
return f"{prefix}/explore/organizers/{group_slug}/categories"
def explore_groups_group_slug_organizers_categories(group_slug):
"""`/api/explore/groups/{group_slug}/organizers/categories`"""
return f"{prefix}/explore/groups/{group_slug}/organizers/categories"
def explore_organizers_group_slug_categories_item_id(group_slug, item_id):
"""`/api/explore/organizers/{group_slug}/categories/{item_id}`"""
return f"{prefix}/explore/organizers/{group_slug}/categories/{item_id}"
def explore_groups_group_slug_organizers_categories_item_id(group_slug, item_id):
"""`/api/explore/groups/{group_slug}/organizers/categories/{item_id}`"""
return f"{prefix}/explore/groups/{group_slug}/organizers/categories/{item_id}"
def explore_organizers_group_slug_tags(group_slug):
"""`/api/explore/organizers/{group_slug}/tags`"""
return f"{prefix}/explore/organizers/{group_slug}/tags"
def explore_groups_group_slug_organizers_tags(group_slug):
"""`/api/explore/groups/{group_slug}/organizers/tags`"""
return f"{prefix}/explore/groups/{group_slug}/organizers/tags"
def explore_organizers_group_slug_tags_item_id(group_slug, item_id):
"""`/api/explore/organizers/{group_slug}/tags/{item_id}`"""
return f"{prefix}/explore/organizers/{group_slug}/tags/{item_id}"
def explore_groups_group_slug_organizers_tags_item_id(group_slug, item_id):
"""`/api/explore/groups/{group_slug}/organizers/tags/{item_id}`"""
return f"{prefix}/explore/groups/{group_slug}/organizers/tags/{item_id}"
def explore_organizers_group_slug_tools(group_slug):
"""`/api/explore/organizers/{group_slug}/tools`"""
return f"{prefix}/explore/organizers/{group_slug}/tools"
def explore_groups_group_slug_organizers_tools(group_slug):
"""`/api/explore/groups/{group_slug}/organizers/tools`"""
return f"{prefix}/explore/groups/{group_slug}/organizers/tools"
def explore_organizers_group_slug_tools_item_id(group_slug, item_id):
"""`/api/explore/organizers/{group_slug}/tools/{item_id}`"""
return f"{prefix}/explore/organizers/{group_slug}/tools/{item_id}"
def explore_groups_group_slug_organizers_tools_item_id(group_slug, item_id):
"""`/api/explore/groups/{group_slug}/organizers/tools/{item_id}`"""
return f"{prefix}/explore/groups/{group_slug}/organizers/tools/{item_id}"
def explore_recipes_group_slug(group_slug):
"""`/api/explore/recipes/{group_slug}`"""
return f"{prefix}/explore/recipes/{group_slug}"
def explore_groups_group_slug_recipes(group_slug):
"""`/api/explore/groups/{group_slug}/recipes`"""
return f"{prefix}/explore/groups/{group_slug}/recipes"
def explore_recipes_group_slug_recipe_slug(group_slug, recipe_slug):
"""`/api/explore/recipes/{group_slug}/{recipe_slug}`"""
return f"{prefix}/explore/recipes/{group_slug}/{recipe_slug}"
def explore_groups_group_slug_recipes_recipe_slug(group_slug, recipe_slug):
"""`/api/explore/groups/{group_slug}/recipes/{recipe_slug}`"""
return f"{prefix}/explore/groups/{group_slug}/recipes/{recipe_slug}"
def foods_item_id(item_id):
@ -285,79 +292,79 @@ def foods_item_id(item_id):
return f"{prefix}/foods/{item_id}"
def groups_cookbooks_item_id(item_id):
"""`/api/groups/cookbooks/{item_id}`"""
return f"{prefix}/groups/cookbooks/{item_id}"
def groups_events_notifications_item_id(item_id):
"""`/api/groups/events/notifications/{item_id}`"""
return f"{prefix}/groups/events/notifications/{item_id}"
def groups_events_notifications_item_id_test(item_id):
"""`/api/groups/events/notifications/{item_id}/test`"""
return f"{prefix}/groups/events/notifications/{item_id}/test"
def groups_labels_item_id(item_id):
"""`/api/groups/labels/{item_id}`"""
return f"{prefix}/groups/labels/{item_id}"
def groups_mealplans_item_id(item_id):
"""`/api/groups/mealplans/{item_id}`"""
return f"{prefix}/groups/mealplans/{item_id}"
def groups_mealplans_rules_item_id(item_id):
"""`/api/groups/mealplans/rules/{item_id}`"""
return f"{prefix}/groups/mealplans/rules/{item_id}"
def groups_recipe_actions_item_id(item_id):
"""`/api/groups/recipe-actions/{item_id}`"""
return f"{prefix}/groups/recipe-actions/{item_id}"
def groups_reports_item_id(item_id):
"""`/api/groups/reports/{item_id}`"""
return f"{prefix}/groups/reports/{item_id}"
def groups_shopping_items_item_id(item_id):
"""`/api/groups/shopping/items/{item_id}`"""
return f"{prefix}/groups/shopping/items/{item_id}"
def households_cookbooks_item_id(item_id):
"""`/api/households/cookbooks/{item_id}`"""
return f"{prefix}/households/cookbooks/{item_id}"
def groups_shopping_lists_item_id(item_id):
"""`/api/groups/shopping/lists/{item_id}`"""
return f"{prefix}/groups/shopping/lists/{item_id}"
def households_events_notifications_item_id(item_id):
"""`/api/households/events/notifications/{item_id}`"""
return f"{prefix}/households/events/notifications/{item_id}"
def groups_shopping_lists_item_id_label_settings(item_id):
"""`/api/groups/shopping/lists/{item_id}/label-settings`"""
return f"{prefix}/groups/shopping/lists/{item_id}/label-settings"
def households_events_notifications_item_id_test(item_id):
"""`/api/households/events/notifications/{item_id}/test`"""
return f"{prefix}/households/events/notifications/{item_id}/test"
def groups_shopping_lists_item_id_recipe_recipe_id(item_id, recipe_id):
"""`/api/groups/shopping/lists/{item_id}/recipe/{recipe_id}`"""
return f"{prefix}/groups/shopping/lists/{item_id}/recipe/{recipe_id}"
def households_mealplans_item_id(item_id):
"""`/api/households/mealplans/{item_id}`"""
return f"{prefix}/households/mealplans/{item_id}"
def groups_shopping_lists_item_id_recipe_recipe_id_delete(item_id, recipe_id):
"""`/api/groups/shopping/lists/{item_id}/recipe/{recipe_id}/delete`"""
return f"{prefix}/groups/shopping/lists/{item_id}/recipe/{recipe_id}/delete"
def households_mealplans_rules_item_id(item_id):
"""`/api/households/mealplans/rules/{item_id}`"""
return f"{prefix}/households/mealplans/rules/{item_id}"
def groups_webhooks_item_id(item_id):
"""`/api/groups/webhooks/{item_id}`"""
return f"{prefix}/groups/webhooks/{item_id}"
def households_recipe_actions_item_id(item_id):
"""`/api/households/recipe-actions/{item_id}`"""
return f"{prefix}/households/recipe-actions/{item_id}"
def groups_webhooks_item_id_test(item_id):
"""`/api/groups/webhooks/{item_id}/test`"""
return f"{prefix}/groups/webhooks/{item_id}/test"
def households_shopping_items_item_id(item_id):
"""`/api/households/shopping/items/{item_id}`"""
return f"{prefix}/households/shopping/items/{item_id}"
def households_shopping_lists_item_id(item_id):
"""`/api/households/shopping/lists/{item_id}`"""
return f"{prefix}/households/shopping/lists/{item_id}"
def households_shopping_lists_item_id_label_settings(item_id):
"""`/api/households/shopping/lists/{item_id}/label-settings`"""
return f"{prefix}/households/shopping/lists/{item_id}/label-settings"
def households_shopping_lists_item_id_recipe_recipe_id(item_id, recipe_id):
"""`/api/households/shopping/lists/{item_id}/recipe/{recipe_id}`"""
return f"{prefix}/households/shopping/lists/{item_id}/recipe/{recipe_id}"
def households_shopping_lists_item_id_recipe_recipe_id_delete(item_id, recipe_id):
"""`/api/households/shopping/lists/{item_id}/recipe/{recipe_id}/delete`"""
return f"{prefix}/households/shopping/lists/{item_id}/recipe/{recipe_id}/delete"
def households_webhooks_item_id(item_id):
"""`/api/households/webhooks/{item_id}`"""
return f"{prefix}/households/webhooks/{item_id}"
def households_webhooks_item_id_test(item_id):
"""`/api/households/webhooks/{item_id}/test`"""
return f"{prefix}/households/webhooks/{item_id}/test"
def media_recipes_recipe_id_assets_file_name(recipe_id, file_name):

View file

@ -23,6 +23,7 @@ def random_int(min=-4294967296, max=4294967296) -> int:
def user_registration_factory(advanced=None, private=None) -> CreateUserRegistration:
return CreateUserRegistration(
group=random_string(),
household=random_string(),
email=random_email(),
username=random_string(),
full_name=random_string(),

View file

@ -3,6 +3,7 @@ from typing import Any
from uuid import UUID
from mealie.db.models.users.users import AuthMethod
from mealie.repos.repository_factory import AllRepositories
@dataclass
@ -12,9 +13,15 @@ class TestUser:
username: str
password: str
_group_id: UUID
_household_id: UUID
token: Any
auth_method = AuthMethod.MEALIE
repos: AllRepositories
@property
def group_id(self) -> str:
return str(self._group_id)
@property
def household_id(self) -> str:
return str(self._household_id)