mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-05 13:35:23 +02:00
feat: Add Households to Mealie (#3970)
This commit is contained in:
parent
0c29cef17d
commit
eb170cc7e5
315 changed files with 6975 additions and 3577 deletions
|
@ -1,123 +0,0 @@
|
|||
import random
|
||||
from dataclasses import dataclass
|
||||
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.cookbook.cookbook import ReadCookBook, SaveCookBook
|
||||
from tests import utils
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.factories import random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
def get_page_data(group_id: UUID | str):
|
||||
name_and_slug = random_string(10)
|
||||
return {
|
||||
"name": name_and_slug,
|
||||
"slug": name_and_slug,
|
||||
"description": "",
|
||||
"position": 0,
|
||||
"categories": [],
|
||||
"group_id": str(group_id),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestCookbook:
|
||||
id: UUID4
|
||||
slug: str
|
||||
name: str
|
||||
data: dict
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def cookbooks(database: AllRepositories, unique_user: TestUser) -> list[TestCookbook]:
|
||||
data: list[ReadCookBook] = []
|
||||
yield_data: list[TestCookbook] = []
|
||||
for _ in range(3):
|
||||
cb = database.cookbooks.create(SaveCookBook(**get_page_data(unique_user.group_id)))
|
||||
data.append(cb)
|
||||
yield_data.append(TestCookbook(id=cb.id, slug=cb.slug, name=cb.name, data=cb.model_dump()))
|
||||
|
||||
yield yield_data
|
||||
|
||||
for cb in yield_data:
|
||||
try:
|
||||
database.cookbooks.delete(cb.id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
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)
|
||||
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)
|
||||
assert response.status_code == 200
|
||||
|
||||
page_data = response.json()
|
||||
|
||||
assert page_data["id"] == str(sample.id)
|
||||
assert page_data["slug"] == sample.slug
|
||||
assert page_data["name"] == sample.name
|
||||
assert page_data["groupId"] == str(unique_user.group_id)
|
||||
|
||||
|
||||
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["name"] = random_string(10)
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_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)
|
||||
assert response.status_code == 200
|
||||
|
||||
page_data = response.json()
|
||||
assert page_data["name"] == update_data["name"]
|
||||
assert page_data["slug"] == update_data["name"]
|
||||
|
||||
|
||||
def test_update_cookbooks_many(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
|
||||
pages = [x.data for x in cookbooks]
|
||||
|
||||
reverse_order = sorted(pages, key=lambda x: x["position"], reverse=True)
|
||||
for x, page in enumerate(reverse_order):
|
||||
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)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.groups_cookbooks, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
known_ids = [x.id for x in cookbooks]
|
||||
|
||||
server_ids = [x["id"] for x in response.json()["items"]]
|
||||
|
||||
for know in known_ids: # Hacky check, because other tests don't cleanup after themselves :(
|
||||
assert str(know) in server_ids
|
||||
|
||||
|
||||
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)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.groups_cookbooks_item_id(sample.slug), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
|
@ -1,73 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.factories import user_registration_factory
|
||||
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)
|
||||
assert r.status_code == 201
|
||||
invitation = r.json()
|
||||
return invitation["token"]
|
||||
|
||||
|
||||
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)
|
||||
|
||||
assert r.status_code == 200
|
||||
|
||||
items = r.json()
|
||||
|
||||
assert len(items) == 1
|
||||
|
||||
for item in items:
|
||||
assert item["groupId"] == unique_user.group_id
|
||||
assert item["token"] == invite
|
||||
|
||||
|
||||
def register_user(api_client, invite):
|
||||
# Test User can Join Group
|
||||
registration = user_registration_factory()
|
||||
registration.group = ""
|
||||
registration.group_token = invite
|
||||
|
||||
response = api_client.post(api_routes.users_register, json=registration.model_dump(by_alias=True))
|
||||
return registration, response
|
||||
|
||||
|
||||
def test_group_invitation_link(api_client: TestClient, unique_user: TestUser, invite: str):
|
||||
registration, r = register_user(api_client, invite)
|
||||
assert r.status_code == 201
|
||||
|
||||
# Login as new User
|
||||
form_data = {"username": registration.email, "password": registration.password}
|
||||
|
||||
r = api_client.post(api_routes.auth_token, data=form_data)
|
||||
assert r.status_code == 200
|
||||
token = r.json().get("access_token")
|
||||
assert token is not None
|
||||
|
||||
# Check user Group is Same
|
||||
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
|
||||
|
||||
|
||||
def test_group_invitation_delete_after_uses(api_client: TestClient, invite: str) -> None:
|
||||
# Register First User
|
||||
_, r = register_user(api_client, invite)
|
||||
assert r.status_code == 201
|
||||
|
||||
# Register Second User
|
||||
_, r = register_user(api_client, invite)
|
||||
assert r.status_code == 201
|
||||
|
||||
# Check Group Invitation is Deleted
|
||||
_, r = register_user(api_client, invite)
|
||||
assert r.status_code == 400
|
|
@ -1,165 +0,0 @@
|
|||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.meal_plan.new_meal import CreatePlanEntry
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.factories import random_string
|
||||
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}"
|
||||
|
||||
|
||||
def test_create_mealplan_no_recipe(api_client: TestClient, unique_user: TestUser):
|
||||
title = random_string(length=25)
|
||||
text = random_string(length=25)
|
||||
new_plan = CreatePlanEntry(
|
||||
date=datetime.now(timezone.utc).date(), entry_type="breakfast", title=title, text=text
|
||||
).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)
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
response_json = response.json()
|
||||
assert response_json["title"] == title
|
||||
assert response_json["text"] == text
|
||||
|
||||
|
||||
def test_create_mealplan_with_recipe(api_client: TestClient, unique_user: TestUser):
|
||||
recipe_name = random_string(length=25)
|
||||
response = api_client.post(api_routes.recipes, json={"name": recipe_name}, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
response = api_client.get(api_routes.recipes_slug(recipe_name), headers=unique_user.token)
|
||||
recipe = response.json()
|
||||
recipe_id = recipe["id"]
|
||||
|
||||
new_plan = CreatePlanEntry(
|
||||
date=datetime.now(timezone.utc).date(), entry_type="dinner", recipe_id=recipe_id
|
||||
).model_dump(by_alias=True)
|
||||
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_json = response.json()
|
||||
assert response.status_code == 201
|
||||
|
||||
assert response_json["recipe"]["slug"] == recipe_name
|
||||
|
||||
|
||||
def test_crud_mealplan(api_client: TestClient, unique_user: TestUser):
|
||||
new_plan = CreatePlanEntry(
|
||||
date=datetime.now(timezone.utc).date(),
|
||||
entry_type="breakfast",
|
||||
title=random_string(),
|
||||
text=random_string(),
|
||||
).model_dump()
|
||||
|
||||
# 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_json = response.json()
|
||||
assert response.status_code == 201
|
||||
plan_id = response_json["id"]
|
||||
|
||||
# Update
|
||||
response_json["title"] = random_string()
|
||||
response_json["text"] = random_string()
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_mealplans_item_id(plan_id), headers=unique_user.token, json=response_json
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
assert response.json()["title"] == response_json["title"]
|
||||
assert response.json()["text"] == response_json["text"]
|
||||
|
||||
# Delete
|
||||
response = api_client.delete(api_routes.groups_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)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_get_all_mealplans(api_client: TestClient, unique_user: TestUser):
|
||||
for _ in range(3):
|
||||
new_plan = CreatePlanEntry(
|
||||
date=datetime.now(timezone.utc).date(),
|
||||
entry_type="breakfast",
|
||||
title=random_string(),
|
||||
text=random_string(),
|
||||
).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)
|
||||
assert response.status_code == 201
|
||||
|
||||
response = api_client.get(api_routes.groups_mealplans, headers=unique_user.token, params={"page": 1, "perPage": -1})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()["items"]) >= 3
|
||||
|
||||
|
||||
def test_get_slice_mealplans(api_client: TestClient, unique_user: TestUser):
|
||||
# Make List of 10 dates from now to +10 days
|
||||
dates = [datetime.now(timezone.utc).date() + timedelta(days=x) for x in range(10)]
|
||||
|
||||
# Make a list of 10 meal plans
|
||||
meal_plans = [
|
||||
CreatePlanEntry(date=date, entry_type="breakfast", title=random_string(), text=random_string()).model_dump()
|
||||
for date in dates
|
||||
]
|
||||
|
||||
# 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)
|
||||
assert response.status_code == 201
|
||||
|
||||
# Get meal slice of meal plans from database
|
||||
slices = [dates, dates[1:2], dates[2:3], dates[3:4], dates[4:5]]
|
||||
|
||||
for date_range in slices:
|
||||
start_date = date_range[0].strftime("%Y-%m-%d")
|
||||
end_date = date_range[-1].strftime("%Y-%m-%d")
|
||||
|
||||
response = api_client.get(route_all_slice(1, -1, start_date, end_date), headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
response_json = response.json()
|
||||
|
||||
for meal_plan in response_json["items"]:
|
||||
assert meal_plan["date"] in [date.strftime("%Y-%m-%d") for date in date_range]
|
||||
|
||||
|
||||
def test_get_mealplan_today(api_client: TestClient, unique_user: TestUser):
|
||||
# Create Meal Plans for today
|
||||
test_meal_plans = [
|
||||
CreatePlanEntry(
|
||||
date=datetime.now(timezone.utc).date(), entry_type="breakfast", title=random_string(), text=random_string()
|
||||
).model_dump()
|
||||
for _ in range(3)
|
||||
]
|
||||
|
||||
# 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)
|
||||
assert response.status_code == 201
|
||||
|
||||
# Get meal plan for today
|
||||
response = api_client.get(api_routes.groups_mealplans_today, headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
response_json = response.json()
|
||||
|
||||
for meal_plan in response_json:
|
||||
assert meal_plan["date"] == datetime.now(timezone.utc).date().strftime("%Y-%m-%d")
|
|
@ -1,127 +0,0 @@
|
|||
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.recipe.recipe import RecipeCategory
|
||||
from mealie.schema.recipe.recipe_category import CategorySave
|
||||
from tests import utils
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def category(
|
||||
database: AllRepositories,
|
||||
unique_user: TestUser,
|
||||
):
|
||||
slug = utils.random_string(length=10)
|
||||
model = database.categories.create(CategorySave(group_id=unique_user.group_id, slug=slug, name=slug))
|
||||
|
||||
yield model
|
||||
|
||||
try:
|
||||
database.categories.delete(model.slug)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@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
|
||||
):
|
||||
payload = {
|
||||
"groupId": unique_user.group_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
|
||||
)
|
||||
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["day"] == "monday"
|
||||
assert response_data["entryType"] == "breakfast"
|
||||
assert len(response_data["categories"]) == 1
|
||||
assert response_data["categories"][0]["slug"] == category.slug
|
||||
|
||||
# Validate database entry
|
||||
rule = database.group_meal_plan_rules.get_one(UUID(response_data["id"]))
|
||||
|
||||
assert str(rule.group_id) == unique_user.group_id
|
||||
assert rule.day == "monday"
|
||||
assert rule.entry_type == "breakfast"
|
||||
assert len(rule.categories) == 1
|
||||
assert rule.categories[0].slug == category.slug
|
||||
|
||||
# Cleanup
|
||||
database.group_meal_plan_rules.delete(rule.id)
|
||||
|
||||
|
||||
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)
|
||||
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["day"] == "monday"
|
||||
assert response_data["entryType"] == "breakfast"
|
||||
assert len(response_data["categories"]) == 0
|
||||
|
||||
|
||||
def test_group_mealplan_rules_update(api_client: TestClient, unique_user: TestUser, plan_rule: PlanRulesOut):
|
||||
payload = {
|
||||
"groupId": unique_user.group_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
|
||||
)
|
||||
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["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)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Validate no entry in database
|
||||
assert database.group_meal_plan_rules.get_one(plan_rule.id) is None
|
|
@ -1,178 +0,0 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.group.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 (
|
||||
EventBusMessage,
|
||||
EventDocumentDataBase,
|
||||
EventDocumentType,
|
||||
EventOperation,
|
||||
EventTypes,
|
||||
)
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.assertion_helpers import assert_ignore_keys
|
||||
from tests.utils.factories import random_bool, random_email, random_int, random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
def preferences_generator():
|
||||
return GroupEventNotifierOptions(
|
||||
recipe_created=random_bool(),
|
||||
recipe_updated=random_bool(),
|
||||
recipe_deleted=random_bool(),
|
||||
user_signup=random_bool(),
|
||||
data_migrations=random_bool(),
|
||||
data_export=random_bool(),
|
||||
data_import=random_bool(),
|
||||
mealplan_entry_created=random_bool(),
|
||||
shopping_list_created=random_bool(),
|
||||
shopping_list_updated=random_bool(),
|
||||
shopping_list_deleted=random_bool(),
|
||||
cookbook_created=random_bool(),
|
||||
cookbook_updated=random_bool(),
|
||||
cookbook_deleted=random_bool(),
|
||||
tag_created=random_bool(),
|
||||
tag_updated=random_bool(),
|
||||
tag_deleted=random_bool(),
|
||||
category_created=random_bool(),
|
||||
category_updated=random_bool(),
|
||||
category_deleted=random_bool(),
|
||||
).model_dump(by_alias=True)
|
||||
|
||||
|
||||
def notifier_generator():
|
||||
return GroupEventNotifierCreate(
|
||||
name=random_string(),
|
||||
apprise_url=random_string(),
|
||||
).model_dump(by_alias=True)
|
||||
|
||||
|
||||
def event_generator():
|
||||
return Event(
|
||||
message=EventBusMessage(title=random_string(), body=random_string()),
|
||||
event_type=EventTypes.test_message,
|
||||
integration_id=random_string(),
|
||||
document_data=EventDocumentDataBase(document_type=EventDocumentType.generic, operation=EventOperation.info),
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
assert response.status_code == 201
|
||||
|
||||
payload_as_dict = response.json()
|
||||
|
||||
assert payload_as_dict["name"] == payload["name"]
|
||||
assert payload_as_dict["enabled"] is True
|
||||
|
||||
# Ensure Apprise URL Stays Private
|
||||
assert "apprise_url" not in payload_as_dict
|
||||
|
||||
# Cleanup
|
||||
response = api_client.delete(
|
||||
api_routes.groups_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)
|
||||
assert response.status_code == 201
|
||||
|
||||
payload_as_dict = response.json()
|
||||
|
||||
# Ensure Apprise URL Staysa Private
|
||||
assert "apprise_url" not in payload_as_dict
|
||||
|
||||
|
||||
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)
|
||||
assert response.status_code == 201
|
||||
|
||||
update_payload = response.json()
|
||||
|
||||
# Set Update Values
|
||||
update_payload["name"] = random_string()
|
||||
update_payload["enabled"] = random_bool()
|
||||
update_payload["options"] = preferences_generator()
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_events_notifications_item_id(update_payload["id"]),
|
||||
json=update_payload,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
# Re-Get The Item
|
||||
response = api_client.get(
|
||||
api_routes.groups_events_notifications_item_id(update_payload["id"]), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Validate Updated Values
|
||||
updated_payload = response.json()
|
||||
|
||||
assert updated_payload["name"] == update_payload["name"]
|
||||
assert updated_payload["enabled"] == update_payload["enabled"]
|
||||
assert_ignore_keys(updated_payload["options"], update_payload["options"])
|
||||
|
||||
# Cleanup
|
||||
response = api_client.delete(
|
||||
api_routes.groups_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)
|
||||
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
|
||||
)
|
||||
assert response.status_code == 204
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_events_notifications_item_id(payload_as_dict["id"]), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_apprise_event_bus_listener_functions():
|
||||
test_event = event_generator()
|
||||
|
||||
test_standard_urls = [
|
||||
"a" + random_string(),
|
||||
f"ses://{random_email()}/{random_string()}/{random_string()}/us-east-1/",
|
||||
f"pBUL://{random_string()}/{random_email()}",
|
||||
]
|
||||
|
||||
test_custom_urls = [
|
||||
"JSON://" + random_string(),
|
||||
f"jsons://{random_string()}:my/pass/word@{random_string()}.com/{random_string()}",
|
||||
"form://" + random_string(),
|
||||
"fORMS://" + str(random_int()),
|
||||
"xml:" + str(random_int()),
|
||||
"xmls://" + random_string(),
|
||||
]
|
||||
|
||||
# Validate all standard urls are not considered custom
|
||||
responses = [AppriseEventListener.is_custom_url(url) for url in test_standard_urls]
|
||||
assert not any(responses)
|
||||
|
||||
# Validate all custom urls are actually considered custom
|
||||
responses = [AppriseEventListener.is_custom_url(url) for url in test_custom_urls]
|
||||
assert all(responses)
|
||||
|
||||
updated_standard_urls = AppriseEventListener.update_urls_with_event_data(test_standard_urls, test_event)
|
||||
updated_custom_urls = AppriseEventListener.update_urls_with_event_data(test_custom_urls, test_event)
|
||||
|
||||
# Validate that no URLs are lost when updating them
|
||||
assert len(updated_standard_urls) == len(test_standard_urls)
|
||||
assert len(updated_custom_urls) == len(updated_custom_urls)
|
|
@ -1,95 +0,0 @@
|
|||
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
|
||||
|
||||
|
||||
def get_permissions_payload(user_id: str, can_manage=None) -> dict:
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"can_manage": random_bool() if can_manage is None else can_manage,
|
||||
"can_invite": random_bool(),
|
||||
"can_organize": random_bool(),
|
||||
}
|
||||
|
||||
|
||||
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):
|
||||
usr_1, usr_2 = user_tuple
|
||||
|
||||
# Set Acting User
|
||||
acting_user = database.users.get_one(usr_1.user_id)
|
||||
acting_user.can_manage = True
|
||||
database.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)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_set_memeber_permissions_unauthorized(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
|
||||
# Setup
|
||||
user = database.users.get_one(unique_user.user_id)
|
||||
user.can_manage = False
|
||||
database.users.update(user.id, user)
|
||||
|
||||
payload = get_permissions_payload(str(user.id))
|
||||
payload = {
|
||||
"user_id": str(user.id),
|
||||
"can_manage": True,
|
||||
"can_invite": True,
|
||||
"can_organize": True,
|
||||
}
|
||||
|
||||
# Test
|
||||
response = api_client.put(api_routes.groups_permissions, json=payload, headers=unique_user.token)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_set_memeber_permissions_other_group(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
g2_user: TestUser,
|
||||
database: AllRepositories,
|
||||
):
|
||||
user = database.users.get_one(unique_user.user_id)
|
||||
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)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_set_memeber_permissions_no_user(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
database: AllRepositories,
|
||||
):
|
||||
user = database.users.get_one(unique_user.user_id)
|
||||
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)
|
||||
assert response.status_code == 404
|
|
@ -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"])
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.group.group_recipe_action import (
|
||||
CreateGroupRecipeAction,
|
||||
GroupRecipeActionOut,
|
||||
GroupRecipeActionType,
|
||||
)
|
||||
from tests.utils import api_routes, assert_deserialize
|
||||
from tests.utils.factories import random_int, random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
def new_link_action() -> CreateGroupRecipeAction:
|
||||
return CreateGroupRecipeAction(
|
||||
action_type=GroupRecipeActionType.link,
|
||||
title=random_string(),
|
||||
url=random_string(),
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
json=action_in.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
data = assert_deserialize(response, 201)
|
||||
|
||||
action_out = GroupRecipeActionOut(**data)
|
||||
assert action_out.id
|
||||
assert str(action_out.group_id) == unique_user.group_id
|
||||
assert action_out.action_type == action_in.action_type
|
||||
assert action_out.title == action_in.title
|
||||
assert action_out.url == action_in.url
|
||||
|
||||
|
||||
def test_group_recipe_actions_get_all(api_client: TestClient, unique_user: TestUser):
|
||||
expected_ids: set[str] = set()
|
||||
for _ in range(random_int(3, 5)):
|
||||
response = api_client.post(
|
||||
api_routes.groups_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)
|
||||
data = assert_deserialize(response, 200)
|
||||
fetched_ids = {item["id"] for item in data["items"]}
|
||||
for expected_id in expected_ids:
|
||||
assert expected_id in fetched_ids
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_own_group", [True, False])
|
||||
def test_group_recipe_actions_get_one(
|
||||
api_client: TestClient, unique_user: TestUser, g2_user: TestUser, is_own_group: bool
|
||||
):
|
||||
action_in = new_link_action()
|
||||
response = api_client.post(
|
||||
api_routes.groups_recipe_actions,
|
||||
json=action_in.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
data = assert_deserialize(response, 201)
|
||||
expected_action_out = GroupRecipeActionOut(**data)
|
||||
|
||||
if is_own_group:
|
||||
fetch_user = unique_user
|
||||
else:
|
||||
fetch_user = g2_user
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_recipe_actions_item_id(expected_action_out.id),
|
||||
headers=fetch_user.token,
|
||||
)
|
||||
if not is_own_group:
|
||||
assert response.status_code == 404
|
||||
return
|
||||
|
||||
data = assert_deserialize(response, 200)
|
||||
action_out = GroupRecipeActionOut(**data)
|
||||
assert action_out == expected_action_out
|
||||
|
||||
|
||||
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,
|
||||
json=action_in.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
data = assert_deserialize(response, 201)
|
||||
action_id = data["id"]
|
||||
|
||||
new_title = random_string()
|
||||
data["title"] = new_title
|
||||
response = api_client.put(
|
||||
api_routes.groups_recipe_actions_item_id(action_id),
|
||||
json=data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
data = assert_deserialize(response, 200)
|
||||
updated_action = GroupRecipeActionOut(**data)
|
||||
|
||||
assert updated_action.title == new_title
|
||||
|
||||
|
||||
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,
|
||||
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)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.groups_recipe_actions_item_id(action_id), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -1,687 +0,0 @@
|
|||
import random
|
||||
from math import ceil, floor
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
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.recipe.recipe_ingredient import SaveIngredientFood
|
||||
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
|
||||
|
||||
|
||||
def create_item(list_id: UUID4, **kwargs) -> dict:
|
||||
return {
|
||||
"shopping_list_id": str(list_id),
|
||||
"note": random_string(10),
|
||||
"quantity": random_int(1, 10),
|
||||
**kwargs,
|
||||
}
|
||||
|
||||
|
||||
def serialize_list_items(list_items: list[ShoppingListItemOut]) -> list:
|
||||
as_dict = []
|
||||
for item in list_items:
|
||||
item_dict = item.model_dump(by_alias=True)
|
||||
item_dict["shoppingListId"] = str(item.shopping_list_id)
|
||||
item_dict["id"] = str(item.id)
|
||||
as_dict.append(item_dict)
|
||||
|
||||
# the default serializer fails on certain complex objects, so we use FastAPI's serliazer first
|
||||
as_dict = utils.jsonify(as_dict)
|
||||
return as_dict
|
||||
|
||||
|
||||
def test_shopping_list_items_create_one(
|
||||
api_client: TestClient, unique_user: TestUser, shopping_list: ShoppingListOut
|
||||
) -> None:
|
||||
item = create_item(shopping_list.id)
|
||||
|
||||
response = api_client.post(api_routes.groups_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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
# Ensure List Id is Set
|
||||
assert as_json["shoppingListId"] == str(shopping_list.id)
|
||||
|
||||
# Test Item In List
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(shopping_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
response_list = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(response_list["listItems"]) == 1
|
||||
|
||||
# Check Item Ids
|
||||
assert response_list["listItems"][0]["id"] == created_item_id
|
||||
|
||||
|
||||
def test_shopping_list_items_create_many(
|
||||
api_client: TestClient, unique_user: TestUser, shopping_list: ShoppingListOut
|
||||
) -> None:
|
||||
items = [create_item(shopping_list.id) for _ in range(10)]
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.groups_shopping_items_create_bulk,
|
||||
json=items,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 201)
|
||||
assert len(as_json["createdItems"]) == len(items)
|
||||
assert len(as_json["updatedItems"]) == 0
|
||||
assert len(as_json["deletedItems"]) == 0
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
# make sure the list is the correct size
|
||||
assert len(as_json["listItems"]) == len(items)
|
||||
|
||||
for item in as_json["listItems"]:
|
||||
# Ensure List Id is Set
|
||||
assert item["shoppingListId"] == str(shopping_list.id)
|
||||
assert item["id"] in created_item_ids
|
||||
created_item_ids.remove(item["id"])
|
||||
|
||||
# make sure we found all items
|
||||
assert not created_item_ids
|
||||
|
||||
|
||||
def test_shopping_list_items_auto_assign_label_with_food_without_label(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_list: ShoppingListOut,
|
||||
database: AllRepositories,
|
||||
):
|
||||
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)
|
||||
as_json = utils.assert_deserialize(response, 201)
|
||||
assert len(as_json["createdItems"]) == 1
|
||||
|
||||
item_out = ShoppingListItemOut.model_validate(as_json["createdItems"][0])
|
||||
assert item_out.label_id is None
|
||||
assert item_out.label is None
|
||||
|
||||
|
||||
def test_shopping_list_items_auto_assign_label_with_food_with_label(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_list: ShoppingListOut,
|
||||
database: AllRepositories,
|
||||
):
|
||||
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)
|
||||
as_json = utils.assert_deserialize(response, 201)
|
||||
assert len(as_json["createdItems"]) == 1
|
||||
|
||||
item_out = ShoppingListItemOut.model_validate(as_json["createdItems"][0])
|
||||
assert item_out.label_id == label.id
|
||||
assert item_out.label
|
||||
assert item_out.label.id == label.id
|
||||
|
||||
|
||||
@pytest.mark.parametrize("use_fuzzy_name", [True, False])
|
||||
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,
|
||||
):
|
||||
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)
|
||||
)
|
||||
|
||||
item = create_item(shopping_list.id)
|
||||
name = food.name
|
||||
if use_fuzzy_name:
|
||||
name = name + random_string(2)
|
||||
item["note"] = name
|
||||
|
||||
response = api_client.post(api_routes.groups_shopping_items, json=item, headers=unique_user.token)
|
||||
as_json = utils.assert_deserialize(response, 201)
|
||||
assert len(as_json["createdItems"]) == 1
|
||||
|
||||
item_out = ShoppingListItemOut.model_validate(as_json["createdItems"][0])
|
||||
assert item_out.label_id == label.id
|
||||
assert item_out.label
|
||||
assert item_out.label.id == label.id
|
||||
|
||||
|
||||
def test_shopping_list_items_get_one(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
list_with_items: ShoppingListOut,
|
||||
) -> None:
|
||||
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)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_shopping_list_items_get_all(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
list_with_items: ShoppingListOut,
|
||||
) -> None:
|
||||
params = {
|
||||
"page": 1,
|
||||
"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)
|
||||
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)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_shopping_list_items_update_one(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
list_with_items: ShoppingListOut,
|
||||
) -> None:
|
||||
for _ in range(3):
|
||||
item = random.choice(list_with_items.list_items)
|
||||
|
||||
item.note = random_string(10)
|
||||
|
||||
update_data = create_item(list_with_items.id)
|
||||
update_data["id"] = str(item.id)
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_shopping_items_item_id(item.id),
|
||||
json=update_data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
item_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(item_json["createdItems"]) == 0
|
||||
assert len(item_json["updatedItems"]) == 1
|
||||
assert len(item_json["deletedItems"]) == 0
|
||||
assert item_json["updatedItems"][0]["note"] == update_data["note"]
|
||||
assert item_json["updatedItems"][0]["quantity"] == update_data["quantity"]
|
||||
|
||||
# make sure the list didn't change sizes
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(list_with_items.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["listItems"]) == len(list_with_items.list_items)
|
||||
|
||||
|
||||
def test_shopping_list_items_update_many(
|
||||
api_client: TestClient, unique_user: TestUser, shopping_list: ShoppingListOut
|
||||
) -> None:
|
||||
# create a bunch of items
|
||||
items = [create_item(shopping_list.id) for _ in range(10)]
|
||||
for item in items:
|
||||
item["quantity"] += 10
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.groups_shopping_items_create_bulk,
|
||||
json=items,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 201)
|
||||
assert len(as_json["createdItems"]) == len(items)
|
||||
|
||||
# update the items and compare values
|
||||
item_quantity_map = {}
|
||||
for update_item in as_json["createdItems"]:
|
||||
update_item["quantity"] += random_int(-5, 5)
|
||||
item_quantity_map[update_item["id"]] = update_item["quantity"]
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_shopping_items,
|
||||
json=as_json["createdItems"],
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["updatedItems"]) == len(items)
|
||||
|
||||
for updated_item in as_json["updatedItems"]:
|
||||
assert item_quantity_map[updated_item["id"]] == updated_item["quantity"]
|
||||
|
||||
# make sure the list didn't change sizes
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(shopping_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["listItems"]) == len(items)
|
||||
|
||||
|
||||
def test_shopping_list_items_update_many_reorder(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
list_with_items: ShoppingListOut,
|
||||
) -> None:
|
||||
list_items = list_with_items.list_items
|
||||
|
||||
# reorder list in random order
|
||||
random.shuffle(list_items)
|
||||
|
||||
# update item posiitons and serialize
|
||||
as_dict = []
|
||||
for i, item in enumerate(list_items):
|
||||
item.position = i
|
||||
item_dict = item.model_dump(by_alias=True)
|
||||
item_dict["shoppingListId"] = str(list_with_items.id)
|
||||
item_dict["id"] = str(item.id)
|
||||
as_dict.append(item_dict)
|
||||
|
||||
# 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)
|
||||
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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
response_list = utils.assert_deserialize(response, 200)
|
||||
|
||||
for i, item_data in enumerate(response_list["listItems"]):
|
||||
assert item_data["position"] == i
|
||||
assert item_data["id"] == str(list_items[i].id)
|
||||
|
||||
|
||||
def test_shopping_list_items_delete_one(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
list_with_items: ShoppingListOut,
|
||||
) -> None:
|
||||
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)
|
||||
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)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_shopping_list_items_update_many_consolidates_common_items(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
list_with_items: ShoppingListOut,
|
||||
) -> None:
|
||||
list_items = list_with_items.list_items
|
||||
|
||||
master_note = random_string(10)
|
||||
|
||||
# set quantity and note to trigger consolidation
|
||||
for li in list_items:
|
||||
li.quantity = 1
|
||||
li.note = master_note
|
||||
|
||||
# update list
|
||||
response = api_client.put(
|
||||
api_routes.groups_shopping_items,
|
||||
json=serialize_list_items(list_items),
|
||||
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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
response_list = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(response_list["listItems"]) == 1
|
||||
assert response_list["listItems"][0]["quantity"] == len(list_items)
|
||||
assert response_list["listItems"][0]["note"] == master_note
|
||||
|
||||
|
||||
def test_shopping_list_items_add_mergeable(
|
||||
api_client: TestClient, unique_user: TestUser, shopping_list: ShoppingListOut
|
||||
):
|
||||
# add a bunch of items that can be consolidated
|
||||
items = [create_item(shopping_list.id) for _ in range(5)]
|
||||
|
||||
common_note = random_string()
|
||||
duplicate_items = [create_item(shopping_list.id) for _ in range(5)]
|
||||
for item in duplicate_items:
|
||||
item["note"] = common_note
|
||||
|
||||
merged_qty = sum([item["quantity"] for item in duplicate_items]) # type: ignore
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.groups_shopping_items_create_bulk,
|
||||
json=items + duplicate_items,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 201)
|
||||
assert len(as_json["createdItems"]) == len(items) + 1
|
||||
assert len(as_json["updatedItems"]) == 0
|
||||
assert len(as_json["deletedItems"]) == 0
|
||||
|
||||
found = False
|
||||
for item in as_json["createdItems"]:
|
||||
if item["note"] == common_note:
|
||||
assert item["quantity"] == merged_qty
|
||||
found = True
|
||||
break
|
||||
|
||||
assert found
|
||||
|
||||
# add more items that can be merged into the existing items
|
||||
item_to_merge_into = random.choice(as_json["createdItems"])
|
||||
new_item = create_item(shopping_list.id)
|
||||
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)
|
||||
item_json = utils.assert_deserialize(response, 201)
|
||||
|
||||
# we should have received an updated item, not a created item
|
||||
assert len(item_json["createdItems"]) == 0
|
||||
assert len(item_json["updatedItems"]) == 1
|
||||
assert len(item_json["deletedItems"]) == 0
|
||||
assert item_json["updatedItems"][0]["id"] == item_to_merge_into["id"]
|
||||
assert item_json["updatedItems"][0]["quantity"] == updated_quantity
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
list_json = utils.assert_deserialize(response, 200)
|
||||
assert len(list_json["listItems"]) == len(as_json["createdItems"])
|
||||
|
||||
|
||||
def test_shopping_list_items_update_mergable(
|
||||
api_client: TestClient, unique_user: TestUser, list_with_items: ShoppingListOut
|
||||
):
|
||||
# update every other item so it merges into the previous item
|
||||
for i, item in enumerate(list_with_items.list_items):
|
||||
if not i % 2:
|
||||
continue
|
||||
|
||||
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)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(as_json["createdItems"]) == 0
|
||||
assert len(as_json["updatedItems"]) == ceil(len(list_with_items.list_items) / 2)
|
||||
assert len(as_json["deletedItems"]) == floor(len(list_with_items.list_items) / 2)
|
||||
|
||||
# check that every other item was updated, and its quantity matches the sum of itself and the previous item
|
||||
for i, item in enumerate(list_with_items.list_items):
|
||||
if not i % 2:
|
||||
continue
|
||||
|
||||
assert (
|
||||
as_json["updatedItems"][floor(i / 2)]["quantity"]
|
||||
== item.quantity + list_with_items.list_items[i - 1].quantity
|
||||
)
|
||||
|
||||
# confirm the number of items on the list matches
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(list_with_items.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
updated_list_items = as_json["listItems"]
|
||||
assert len(updated_list_items) == ceil(len(list_with_items.list_items) / 2)
|
||||
|
||||
# update two of the items so they merge into each other
|
||||
new_note = random_string()
|
||||
items_to_merge = random.sample(updated_list_items, 2)
|
||||
for item_data in items_to_merge:
|
||||
item_data["note"] = new_note
|
||||
|
||||
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)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(as_json["createdItems"]) == 0
|
||||
assert len(as_json["updatedItems"]) == 1
|
||||
assert len(as_json["deletedItems"]) == 1
|
||||
assert as_json["deletedItems"][0]["id"] in [item["id"] for item in items_to_merge]
|
||||
|
||||
found = False
|
||||
for item_data in as_json["updatedItems"]:
|
||||
if item_data["id"] not in [item["id"] for item in items_to_merge]:
|
||||
continue
|
||||
|
||||
assert item_data["quantity"] == merged_quantity
|
||||
found = True
|
||||
break
|
||||
|
||||
assert found
|
||||
|
||||
|
||||
def test_shopping_list_items_checked_off(
|
||||
api_client: TestClient, unique_user: TestUser, list_with_items: ShoppingListOut
|
||||
):
|
||||
# rename an item to match another item and check it off, and make sure it does not affect the other item
|
||||
checked_item, reference_item = random.sample(list_with_items.list_items, 2)
|
||||
checked_item.note = reference_item.note
|
||||
checked_item.checked = True
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_shopping_items_item_id(checked_item.id),
|
||||
json=utils.jsonify(checked_item.model_dump()),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["createdItems"]) == 0
|
||||
assert len(as_json["updatedItems"]) == 1
|
||||
assert len(as_json["deletedItems"]) == 0
|
||||
updated_item = as_json["updatedItems"][0]
|
||||
assert updated_item["checked"]
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
reference_item_get = ShoppingListItemOut.model_validate(as_json)
|
||||
|
||||
assert reference_item_get.id == reference_item.id
|
||||
assert reference_item_get.shopping_list_id == reference_item.shopping_list_id
|
||||
assert reference_item_get.note == reference_item.note
|
||||
assert reference_item_get.quantity == reference_item.quantity
|
||||
assert reference_item_get.checked == reference_item.checked
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
updated_list = ShoppingListOut.model_validate(as_json)
|
||||
|
||||
item_1, item_2 = random.sample(updated_list.list_items, 2)
|
||||
item_1.checked = True
|
||||
item_2.checked = True
|
||||
item_2.note = item_1.note
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_shopping_items,
|
||||
json=utils.jsonify([item_1.model_dump(), item_2.model_dump()]),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["createdItems"]) == 0
|
||||
assert len(as_json["updatedItems"]) == 2
|
||||
assert len(as_json["deletedItems"]) == 0
|
||||
|
||||
updated_items_map = {item["id"]: item for item in as_json["updatedItems"]}
|
||||
for item in [item_1, item_2]:
|
||||
updated_item_data = updated_items_map[str(item.id)]
|
||||
assert item.note == updated_item_data["note"]
|
||||
assert item.quantity == updated_item_data["quantity"]
|
||||
assert updated_item_data["checked"]
|
||||
|
||||
|
||||
def test_shopping_list_items_with_zero_quantity(
|
||||
api_client: TestClient, unique_user: TestUser, shopping_list: ShoppingListOut
|
||||
):
|
||||
# add a bunch of items, some with zero quantity, and make sure they persist
|
||||
normal_items = [create_item(shopping_list.id) for _ in range(10)]
|
||||
zero_qty_items = [create_item(shopping_list.id) for _ in range(10)]
|
||||
for item in zero_qty_items:
|
||||
item["quantity"] = 0
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.groups_shopping_items_create_bulk,
|
||||
json=normal_items + zero_qty_items,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 201)
|
||||
assert len(as_json["createdItems"]) == len(normal_items + zero_qty_items)
|
||||
|
||||
# confirm the number of items on the list matches
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(shopping_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
created_items = as_json["listItems"]
|
||||
assert len(created_items) == len(normal_items + zero_qty_items)
|
||||
|
||||
# add another zero quantity item so it merges into the existing item
|
||||
new_item_to_merge = create_item(shopping_list.id)
|
||||
new_item_to_merge["quantity"] = 0
|
||||
target_item = random.choice(created_items)
|
||||
new_item_to_merge["note"] = target_item["note"]
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.groups_shopping_items,
|
||||
json=new_item_to_merge,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 201)
|
||||
assert len(as_json["createdItems"]) == 0
|
||||
assert len(as_json["updatedItems"]) == 1
|
||||
assert len(as_json["deletedItems"]) == 0
|
||||
|
||||
updated_item = as_json["updatedItems"][0]
|
||||
assert updated_item["id"] == target_item["id"]
|
||||
assert updated_item["note"] == target_item["note"]
|
||||
assert updated_item["quantity"] == target_item["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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["listItems"]) == len(normal_items + zero_qty_items)
|
||||
|
||||
# update an existing item to zero quantity and make sure it merges into the existing item
|
||||
update_item_to_merge, target_item = random.sample(as_json["listItems"], 2)
|
||||
update_item_to_merge["note"] = target_item["note"]
|
||||
update_item_to_merge["quantity"] = 0
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_shopping_items_item_id(update_item_to_merge["id"]),
|
||||
json=update_item_to_merge,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["createdItems"]) == 0
|
||||
assert len(as_json["updatedItems"]) == 1
|
||||
assert len(as_json["deletedItems"]) == 1
|
||||
assert as_json["deletedItems"][0]["id"] == update_item_to_merge["id"]
|
||||
|
||||
updated_item = as_json["updatedItems"][0]
|
||||
assert updated_item["id"] == target_item["id"]
|
||||
assert updated_item["note"] == target_item["note"]
|
||||
assert updated_item["quantity"] == target_item["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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["listItems"]) == len(normal_items + zero_qty_items) - 1
|
||||
|
||||
|
||||
def test_shopping_list_item_extras(
|
||||
api_client: TestClient, unique_user: TestUser, shopping_list: ShoppingListOut
|
||||
) -> None:
|
||||
key_str_1 = random_string()
|
||||
val_str_1 = random_string()
|
||||
|
||||
key_str_2 = random_string()
|
||||
val_str_2 = random_string()
|
||||
|
||||
# create an item with 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)
|
||||
collection = utils.assert_deserialize(response, 201)
|
||||
item_as_json = collection["createdItems"][0]
|
||||
|
||||
# make sure the extra persists
|
||||
extras = item_as_json["extras"]
|
||||
assert key_str_1 in extras
|
||||
assert extras[key_str_1] == val_str_1
|
||||
|
||||
# add more extras to the item
|
||||
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"]),
|
||||
json=item_as_json,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
collection = utils.assert_deserialize(response, 200)
|
||||
item_as_json = collection["updatedItems"][0]
|
||||
|
||||
# make sure both the new extra and original extra persist
|
||||
extras = item_as_json["extras"]
|
||||
assert key_str_1 in extras
|
||||
assert key_str_2 in extras
|
||||
assert extras[key_str_1] == val_str_1
|
||||
assert extras[key_str_2] == val_str_2
|
|
@ -1,972 +0,0 @@
|
|||
import random
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.group.group_shopping_list import (
|
||||
ShoppingListItemOut,
|
||||
ShoppingListItemUpdate,
|
||||
ShoppingListItemUpdateBulk,
|
||||
ShoppingListOut,
|
||||
)
|
||||
from mealie.schema.recipe.recipe import Recipe
|
||||
from tests import utils
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.assertion_helpers import assert_deserialize
|
||||
from tests.utils.factories import random_int, random_string
|
||||
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)
|
||||
assert response.status_code == 200
|
||||
all_lists = response.json()["items"]
|
||||
|
||||
assert len(all_lists) == len(shopping_lists)
|
||||
|
||||
known_ids = [str(model.id) for model in shopping_lists]
|
||||
|
||||
for list_ in all_lists:
|
||||
assert list_["id"] in known_ids
|
||||
|
||||
|
||||
def test_shopping_lists_create_one(api_client: TestClient, unique_user: TestUser):
|
||||
payload = {
|
||||
"name": random_string(10),
|
||||
}
|
||||
|
||||
response = api_client.post(api_routes.groups_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["userId"] == str(unique_user.user_id)
|
||||
|
||||
|
||||
def test_shopping_lists_get_one(api_client: TestClient, unique_user: TestUser, shopping_lists: list[ShoppingListOut]):
|
||||
shopping_list = shopping_lists[0]
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(shopping_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response_list = response.json()
|
||||
|
||||
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["userId"] == str(shopping_list.user_id)
|
||||
|
||||
|
||||
def test_shopping_lists_update_one(
|
||||
api_client: TestClient, unique_user: TestUser, shopping_lists: list[ShoppingListOut]
|
||||
):
|
||||
sample_list = random.choice(shopping_lists)
|
||||
|
||||
payload = {
|
||||
"name": random_string(10),
|
||||
"id": str(sample_list.id),
|
||||
"groupId": str(sample_list.group_id),
|
||||
"userId": str(sample_list.user_id),
|
||||
"listItems": [],
|
||||
}
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_shopping_lists_item_id(sample_list.id),
|
||||
json=payload,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response_list = response.json()
|
||||
|
||||
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["userId"] == str(sample_list.user_id)
|
||||
|
||||
|
||||
def test_shopping_lists_delete_one(
|
||||
api_client: TestClient, unique_user: TestUser, shopping_lists: list[ShoppingListOut]
|
||||
):
|
||||
sample_list = random.choice(shopping_lists)
|
||||
|
||||
response = api_client.delete(
|
||||
api_routes.groups_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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_shopping_lists_add_recipe(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
recipe_ingredient_only: Recipe,
|
||||
):
|
||||
sample_list = random.choice(shopping_lists)
|
||||
recipe = recipe_ingredient_only
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.groups_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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["listItems"]) == len(recipe.recipe_ingredient)
|
||||
|
||||
known_ingredients = {ingredient.note: ingredient for ingredient in recipe.recipe_ingredient}
|
||||
for item in as_json["listItems"]:
|
||||
assert item["note"] in known_ingredients
|
||||
|
||||
ingredient = known_ingredients[item["note"]]
|
||||
assert item["quantity"] == (ingredient.quantity or 0)
|
||||
|
||||
# check recipe reference was added with quantity 1
|
||||
refs = as_json["recipeReferences"]
|
||||
assert len(refs) == 1
|
||||
assert refs[0]["recipeId"] == str(recipe.id)
|
||||
assert refs[0]["recipeQuantity"] == 1
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(sample_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["listItems"]) == len(recipe.recipe_ingredient)
|
||||
|
||||
for item in as_json["listItems"]:
|
||||
assert item["note"] in known_ingredients
|
||||
|
||||
ingredient = known_ingredients[item["note"]]
|
||||
assert item["quantity"] == (ingredient.quantity or 0) * 2
|
||||
|
||||
refs = as_json["recipeReferences"]
|
||||
assert len(refs) == 1
|
||||
assert refs[0]["recipeId"] == str(recipe.id)
|
||||
assert refs[0]["recipeQuantity"] == 2
|
||||
|
||||
|
||||
def test_shopping_lists_add_one_with_zero_quantity(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
):
|
||||
shopping_list = random.choice(shopping_lists)
|
||||
|
||||
# build a recipe that has some ingredients with a null quantity
|
||||
response = api_client.post(api_routes.recipes, json={"name": random_string()}, headers=unique_user.token)
|
||||
recipe_slug = utils.assert_deserialize(response, 201)
|
||||
|
||||
response = api_client.get(f"{api_routes.recipes}/{recipe_slug}", headers=unique_user.token)
|
||||
recipe_data = utils.assert_deserialize(response, 200)
|
||||
|
||||
ingredient_1 = {"quantity": random_int(1, 10), "note": random_string()}
|
||||
ingredient_2 = {"quantity": random_int(1, 10), "note": random_string()}
|
||||
ingredient_3_null_qty = {"quantity": None, "note": random_string()}
|
||||
|
||||
recipe_data["recipeIngredient"] = [
|
||||
ingredient_1,
|
||||
ingredient_2,
|
||||
ingredient_3_null_qty,
|
||||
]
|
||||
response = api_client.put(
|
||||
f"{api_routes.recipes}/{recipe_slug}",
|
||||
json=recipe_data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
utils.assert_deserialize(response, 200)
|
||||
|
||||
recipe = Recipe.model_validate_json(
|
||||
api_client.get(f"{api_routes.recipes}/{recipe_slug}", headers=unique_user.token).content
|
||||
)
|
||||
assert recipe.id
|
||||
assert len(recipe.recipe_ingredient) == 3
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(shopping_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
shopping_list_out = ShoppingListOut.model_validate(utils.assert_deserialize(response, 200))
|
||||
|
||||
assert len(shopping_list_out.list_items) == 3
|
||||
|
||||
found = False
|
||||
for item in shopping_list_out.list_items:
|
||||
if item.note != ingredient_3_null_qty["note"]:
|
||||
continue
|
||||
|
||||
found = True
|
||||
assert item.quantity == 0
|
||||
|
||||
assert found
|
||||
|
||||
|
||||
def test_shopping_lists_add_custom_recipe_items(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
recipe_ingredient_only: Recipe,
|
||||
):
|
||||
sample_list = random.choice(shopping_lists)
|
||||
recipe = recipe_ingredient_only
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.groups_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),
|
||||
headers=unique_user.token,
|
||||
json={"recipeIngredients": utils.jsonify(custom_items)},
|
||||
)
|
||||
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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["listItems"]) == len(recipe.recipe_ingredient)
|
||||
|
||||
known_ingredients = {ingredient.note: ingredient for ingredient in recipe.recipe_ingredient}
|
||||
custom_ingredients = [ingredient.note for ingredient in custom_items]
|
||||
for item in as_json["listItems"]:
|
||||
assert item["note"] in known_ingredients
|
||||
|
||||
ingredient = known_ingredients[item["note"]]
|
||||
if item["note"] in custom_ingredients:
|
||||
assert item["quantity"] == (ingredient.quantity * 2 if ingredient.quantity else 0)
|
||||
|
||||
else:
|
||||
assert item["quantity"] == (ingredient.quantity or 0)
|
||||
|
||||
# check recipe reference was added with quantity 2
|
||||
refs = as_json["recipeReferences"]
|
||||
assert len(refs) == 1
|
||||
assert refs[0]["recipeId"] == str(recipe.id)
|
||||
assert refs[0]["recipeQuantity"] == 2
|
||||
|
||||
|
||||
def test_shopping_list_ref_removes_itself(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_list: ShoppingListOut,
|
||||
recipe_ingredient_only: Recipe,
|
||||
):
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
utils.assert_deserialize(response, 200)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(shopping_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
shopping_list_json = utils.assert_deserialize(response, 200)
|
||||
assert len(shopping_list_json["listItems"]) == len(recipe.recipe_ingredient)
|
||||
assert len(shopping_list_json["recipeReferences"]) == 1
|
||||
|
||||
for item in shopping_list_json["listItems"]:
|
||||
item["checked"] = True
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
shopping_list_json = utils.assert_deserialize(response, 200)
|
||||
assert len(shopping_list_json["recipeReferences"]) == 0
|
||||
|
||||
|
||||
def test_shopping_lists_add_recipe_with_merge(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
):
|
||||
shopping_list = random.choice(shopping_lists)
|
||||
|
||||
# build a recipe that has some ingredients more than once
|
||||
response = api_client.post(api_routes.recipes, json={"name": random_string()}, headers=unique_user.token)
|
||||
recipe_slug = utils.assert_deserialize(response, 201)
|
||||
|
||||
response = api_client.get(f"{api_routes.recipes}/{recipe_slug}", headers=unique_user.token)
|
||||
recipe_data = utils.assert_deserialize(response, 200)
|
||||
|
||||
ingredient_1 = {"quantity": random_int(1, 10), "note": random_string()}
|
||||
ingredient_2 = {"quantity": random_int(1, 10), "note": random_string()}
|
||||
ingredient_duplicate_1 = {"quantity": random_int(1, 10), "note": random_string()}
|
||||
ingredient_duplicate_2 = {
|
||||
"quantity": random_int(1, 10),
|
||||
"note": ingredient_duplicate_1["note"],
|
||||
}
|
||||
|
||||
recipe_data["recipeIngredient"] = [
|
||||
ingredient_1,
|
||||
ingredient_2,
|
||||
ingredient_duplicate_1,
|
||||
ingredient_duplicate_2,
|
||||
]
|
||||
response = api_client.put(
|
||||
f"{api_routes.recipes}/{recipe_slug}",
|
||||
json=recipe_data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
utils.assert_deserialize(response, 200)
|
||||
|
||||
recipe = Recipe.model_validate_json(
|
||||
api_client.get(f"{api_routes.recipes}/{recipe_slug}", headers=unique_user.token).content
|
||||
)
|
||||
assert recipe.id
|
||||
assert len(recipe.recipe_ingredient) == 4
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(shopping_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
shopping_list_out = ShoppingListOut.model_validate(utils.assert_deserialize(response, 200))
|
||||
|
||||
assert len(shopping_list_out.list_items) == 3
|
||||
|
||||
found_item_1 = False
|
||||
found_item_2 = False
|
||||
found_duplicate_item = False
|
||||
for list_item in shopping_list_out.list_items:
|
||||
assert len(list_item.recipe_references) == 1
|
||||
|
||||
ref = list_item.recipe_references[0]
|
||||
assert ref.recipe_scale == 1
|
||||
assert ref.recipe_quantity == list_item.quantity
|
||||
|
||||
if list_item.note == ingredient_1["note"]:
|
||||
assert list_item.quantity == ingredient_1["quantity"]
|
||||
found_item_1 = True
|
||||
|
||||
elif list_item.note == ingredient_2["note"]:
|
||||
assert list_item.quantity == ingredient_2["quantity"]
|
||||
found_item_2 = True
|
||||
|
||||
elif list_item.note == ingredient_duplicate_1["note"]:
|
||||
combined_quantity = ingredient_duplicate_1["quantity"] + ingredient_duplicate_2["quantity"] # type: ignore
|
||||
assert list_item.quantity == combined_quantity
|
||||
found_duplicate_item = True
|
||||
|
||||
assert all([found_item_1, found_item_2, found_duplicate_item])
|
||||
|
||||
|
||||
def test_shopping_list_add_recipe_scale(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
recipe_ingredient_only: Recipe,
|
||||
):
|
||||
sample_list = random.choice(shopping_lists)
|
||||
recipe = recipe_ingredient_only
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.groups_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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(as_json["recipeReferences"]) == 1
|
||||
assert as_json["recipeReferences"][0]["recipeQuantity"] == 1
|
||||
|
||||
for item in as_json["listItems"]:
|
||||
assert item["quantity"] == 1
|
||||
refs = item["recipeReferences"]
|
||||
|
||||
# only one reference per item
|
||||
assert len(refs) == 1
|
||||
|
||||
# base recipe quantity is 1
|
||||
assert refs[0]["recipeQuantity"] == 1
|
||||
|
||||
# scale was unspecified, which defaults to 1
|
||||
assert refs[0]["recipeScale"] == 1
|
||||
|
||||
recipe_scale = round(random.uniform(1, 10), 5)
|
||||
payload = {"recipeIncrementQuantity": recipe_scale}
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.groups_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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(as_json["recipeReferences"]) == 1
|
||||
assert as_json["recipeReferences"][0]["recipeQuantity"] == 1 + recipe_scale
|
||||
|
||||
for item in as_json["listItems"]:
|
||||
assert item["quantity"] == 1 + recipe_scale
|
||||
refs = item["recipeReferences"]
|
||||
|
||||
assert len(refs) == 1
|
||||
assert refs[0]["recipeQuantity"] == 1
|
||||
assert refs[0]["recipeScale"] == 1 + recipe_scale
|
||||
|
||||
|
||||
def test_shopping_lists_remove_recipe(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
recipe_ingredient_only: Recipe,
|
||||
):
|
||||
sample_list = random.choice(shopping_lists)
|
||||
recipe = recipe_ingredient_only
|
||||
|
||||
# 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),
|
||||
json=payload,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# 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),
|
||||
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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["listItems"]) == len(recipe.recipe_ingredient)
|
||||
|
||||
known_ingredients = {ingredient.note: ingredient for ingredient in recipe.recipe_ingredient}
|
||||
for item in as_json["listItems"]:
|
||||
assert item["note"] in known_ingredients
|
||||
|
||||
ingredient = known_ingredients[item["note"]]
|
||||
assert item["quantity"] == (ingredient.quantity or 0)
|
||||
|
||||
# check recipe reference was reduced to 1
|
||||
refs = as_json["recipeReferences"]
|
||||
assert len(refs) == 1
|
||||
assert refs[0]["recipeId"] == str(recipe.id)
|
||||
assert refs[0]["recipeQuantity"] == 1
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(sample_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
assert len(as_json["listItems"]) == 0
|
||||
assert len(as_json["recipeReferences"]) == 0
|
||||
|
||||
|
||||
def test_shopping_lists_remove_recipe_multiple_quantity(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
recipe_ingredient_only: Recipe,
|
||||
):
|
||||
sample_list = random.choice(shopping_lists)
|
||||
recipe = recipe_ingredient_only
|
||||
|
||||
for _ in range(3):
|
||||
response = api_client.post(
|
||||
api_routes.groups_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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(as_json["listItems"]) == len(recipe.recipe_ingredient)
|
||||
|
||||
known_ingredients = [ingredient.note for ingredient in recipe.recipe_ingredient]
|
||||
|
||||
for item in as_json["listItems"]:
|
||||
assert item["note"] in known_ingredients
|
||||
|
||||
# Remove Recipe
|
||||
response = api_client.post(
|
||||
api_routes.groups_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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
# All Items Should Still Exists
|
||||
assert len(as_json["listItems"]) == len(recipe.recipe_ingredient)
|
||||
|
||||
# Quantity Should Equal 2 Start with 3 remove 1)
|
||||
for item in as_json["listItems"]:
|
||||
assert item["quantity"] == 2.0
|
||||
|
||||
refs = as_json["recipeReferences"]
|
||||
assert len(refs) == 1
|
||||
assert refs[0]["recipeId"] == str(recipe.id)
|
||||
|
||||
|
||||
def test_shopping_list_remove_recipe_scale(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
recipe_ingredient_only: Recipe,
|
||||
):
|
||||
sample_list = random.choice(shopping_lists)
|
||||
recipe = recipe_ingredient_only
|
||||
|
||||
recipe_initital_scale = 100
|
||||
payload: dict = {"recipeIncrementQuantity": recipe_initital_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),
|
||||
headers=unique_user.token,
|
||||
json=payload,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(sample_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(as_json["recipeReferences"]) == 1
|
||||
assert as_json["recipeReferences"][0]["recipeQuantity"] == recipe_initital_scale
|
||||
|
||||
for item in as_json["listItems"]:
|
||||
assert item["quantity"] == recipe_initital_scale
|
||||
refs = item["recipeReferences"]
|
||||
|
||||
assert len(refs) == 1
|
||||
assert refs[0]["recipeQuantity"] == 1
|
||||
assert refs[0]["recipeScale"] == recipe_initital_scale
|
||||
|
||||
recipe_decrement_scale = round(random.uniform(10, 90), 5)
|
||||
payload = {"recipeDecrementQuantity": recipe_decrement_scale}
|
||||
recipe_expected_scale = recipe_initital_scale - recipe_decrement_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),
|
||||
headers=unique_user.token,
|
||||
json=payload,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(sample_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(as_json["recipeReferences"]) == 1
|
||||
assert as_json["recipeReferences"][0]["recipeQuantity"] == recipe_expected_scale
|
||||
|
||||
for item in as_json["listItems"]:
|
||||
assert item["quantity"] == recipe_expected_scale
|
||||
refs = item["recipeReferences"]
|
||||
|
||||
assert len(refs) == 1
|
||||
assert refs[0]["recipeQuantity"] == 1
|
||||
assert refs[0]["recipeScale"] == recipe_expected_scale
|
||||
|
||||
|
||||
def test_recipe_decrement_max(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
recipe_ingredient_only: Recipe,
|
||||
):
|
||||
sample_list = random.choice(shopping_lists)
|
||||
recipe = recipe_ingredient_only
|
||||
|
||||
recipe_scale = 10
|
||||
payload = {"recipeIncrementQuantity": 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),
|
||||
headers=unique_user.token,
|
||||
json=payload,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(sample_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
assert len(as_json["recipeReferences"]) == 1
|
||||
assert as_json["recipeReferences"][0]["recipeQuantity"] == recipe_scale
|
||||
|
||||
for item in as_json["listItems"]:
|
||||
assert item["quantity"] == recipe_scale
|
||||
refs = item["recipeReferences"]
|
||||
|
||||
assert len(refs) == 1
|
||||
assert refs[0]["recipeQuantity"] == 1
|
||||
assert refs[0]["recipeScale"] == recipe_scale
|
||||
|
||||
# next add a little bit more of one item
|
||||
item_additional_quantity = random_int(1, 10)
|
||||
item_json = random.choice(as_json["listItems"])
|
||||
item_json["quantity"] += item_additional_quantity
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_shopping_items_item_id(item_json["id"]),
|
||||
json=item_json,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
item_json = as_json["updatedItems"][0]
|
||||
assert item_json["quantity"] == recipe_scale + item_additional_quantity
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
json=payload,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(sample_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
# check that only the original recipe quantity and its reference were removed, not the additional quantity
|
||||
assert len(as_json["recipeReferences"]) == 0
|
||||
assert len(as_json["listItems"]) == 1
|
||||
|
||||
item = as_json["listItems"][0]
|
||||
assert item["quantity"] == item_additional_quantity
|
||||
assert len(item["recipeReferences"]) == 0
|
||||
|
||||
|
||||
def test_recipe_manipulation_with_zero_quantities(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
):
|
||||
shopping_list = random.choice(shopping_lists)
|
||||
|
||||
# create a recipe with one item that has a quantity of zero
|
||||
response = api_client.post(api_routes.recipes, json={"name": random_string()}, headers=unique_user.token)
|
||||
recipe_slug = utils.assert_deserialize(response, 201)
|
||||
|
||||
response = api_client.get(f"{api_routes.recipes}/{recipe_slug}", headers=unique_user.token)
|
||||
recipe_data = utils.assert_deserialize(response, 200)
|
||||
|
||||
note_with_zero_quantity = random_string()
|
||||
recipe_data["recipeIngredient"] = [
|
||||
{"quantity": random_int(1, 10), "note": random_string()},
|
||||
{"quantity": random_int(1, 10), "note": random_string()},
|
||||
{"quantity": random_int(1, 10), "note": random_string()},
|
||||
{"quantity": 0, "note": note_with_zero_quantity},
|
||||
]
|
||||
|
||||
response = api_client.put(
|
||||
f"{api_routes.recipes}/{recipe_slug}",
|
||||
json=recipe_data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
utils.assert_deserialize(response, 200)
|
||||
|
||||
recipe = Recipe.model_validate_json(
|
||||
api_client.get(f"{api_routes.recipes}/{recipe_slug}", headers=unique_user.token).content
|
||||
)
|
||||
assert recipe.id
|
||||
assert len(recipe.recipe_ingredient) == 4
|
||||
|
||||
# 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),
|
||||
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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
utils.assert_deserialize(response, 200)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(shopping_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
updated_list = ShoppingListOut.model_validate_json(response.content)
|
||||
assert len(updated_list.list_items) == 4
|
||||
|
||||
found = False
|
||||
for item in updated_list.list_items:
|
||||
if item.note != note_with_zero_quantity:
|
||||
continue
|
||||
|
||||
assert item.quantity == 0
|
||||
|
||||
recipe_ref = item.recipe_references[0]
|
||||
assert recipe_ref.recipe_scale == 2
|
||||
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
raise Exception("Did not find item with no quantity in shopping list")
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(shopping_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
updated_list = ShoppingListOut.model_validate_json(response.content)
|
||||
assert len(updated_list.list_items) == 4
|
||||
|
||||
found = False
|
||||
for item in updated_list.list_items:
|
||||
if item.note != note_with_zero_quantity:
|
||||
continue
|
||||
|
||||
assert item.quantity == 0
|
||||
|
||||
recipe_ref = item.recipe_references[0]
|
||||
assert recipe_ref.recipe_scale == 1
|
||||
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
raise Exception("Did not find item with no quantity in shopping list")
|
||||
|
||||
# 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),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.groups_shopping_lists_item_id(shopping_list.id),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
updated_list = ShoppingListOut.model_validate_json(response.content)
|
||||
assert len(updated_list.list_items) == 0
|
||||
|
||||
|
||||
def test_shopping_list_extras(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
):
|
||||
key_str_1 = random_string()
|
||||
val_str_1 = random_string()
|
||||
|
||||
key_str_2 = random_string()
|
||||
val_str_2 = random_string()
|
||||
|
||||
# create a list with 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)
|
||||
list_as_json = utils.assert_deserialize(response, 201)
|
||||
|
||||
# make sure the extra persists
|
||||
extras = list_as_json["extras"]
|
||||
assert key_str_1 in extras
|
||||
assert extras[key_str_1] == val_str_1
|
||||
|
||||
# add more extras to the list
|
||||
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"]),
|
||||
json=list_as_json,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
list_as_json = utils.assert_deserialize(response, 200)
|
||||
|
||||
# make sure both the new extra and original extra persist
|
||||
extras = list_as_json["extras"]
|
||||
assert key_str_1 in extras
|
||||
assert key_str_2 in extras
|
||||
assert extras[key_str_1] == val_str_1
|
||||
assert extras[key_str_2] == val_str_2
|
||||
|
||||
|
||||
def test_modify_shopping_list_items_updates_shopping_list(
|
||||
database: AllRepositories,
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
):
|
||||
shopping_list = random.choice(shopping_lists)
|
||||
last_update_at = shopping_list.update_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)
|
||||
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
|
||||
|
||||
list_item_id = data["createdItems"][0]["id"]
|
||||
list_item = database.group_shopping_list_item.get_one(list_item_id)
|
||||
assert list_item
|
||||
|
||||
# Update
|
||||
list_item.note = random_string()
|
||||
response = api_client.put(
|
||||
api_routes.groups_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
|
||||
|
||||
# Delete
|
||||
response = api_client.delete(
|
||||
api_routes.groups_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
|
||||
|
||||
|
||||
def test_bulk_modify_shopping_list_items_updates_shopping_list(
|
||||
database: AllRepositories,
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
shopping_lists: list[ShoppingListOut],
|
||||
):
|
||||
shopping_list = random.choice(shopping_lists)
|
||||
last_update_at = shopping_list.update_at
|
||||
assert last_update_at
|
||||
|
||||
# Create
|
||||
new_item_data = [
|
||||
{"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,
|
||||
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
|
||||
|
||||
# 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)
|
||||
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)
|
||||
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
|
||||
|
||||
# Delete
|
||||
response = api_client.delete(
|
||||
api_routes.groups_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
|
|
@ -1,86 +0,0 @@
|
|||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import api_routes, assert_deserialize, jsonify
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def webhook_data():
|
||||
return {
|
||||
"enabled": True,
|
||||
"name": "Test-Name",
|
||||
"url": "https://my-fake-url.com",
|
||||
"time": "00:00",
|
||||
"scheduledTime": datetime.now(timezone.utc),
|
||||
}
|
||||
|
||||
|
||||
def test_create_webhook(api_client: TestClient, unique_user: TestUser, webhook_data):
|
||||
response = api_client.post(
|
||||
api_routes.groups_webhooks,
|
||||
json=jsonify(webhook_data),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
def test_read_webhook(api_client: TestClient, unique_user: TestUser, webhook_data):
|
||||
response = api_client.post(
|
||||
api_routes.groups_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)
|
||||
webhook = assert_deserialize(response, 200)
|
||||
|
||||
assert webhook["id"] == item_id
|
||||
assert webhook["name"] == webhook_data["name"]
|
||||
assert webhook["url"] == webhook_data["url"]
|
||||
assert webhook["scheduledTime"] == str(webhook_data["scheduledTime"].astimezone(timezone.utc).time())
|
||||
assert webhook["enabled"] == webhook_data["enabled"]
|
||||
|
||||
|
||||
def test_update_webhook(api_client: TestClient, webhook_data, unique_user: TestUser):
|
||||
response = api_client.post(
|
||||
api_routes.groups_webhooks,
|
||||
json=jsonify(webhook_data),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
item_dict = assert_deserialize(response, 201)
|
||||
item_id = item_dict["id"]
|
||||
|
||||
webhook_data["name"] = "My New Name"
|
||||
webhook_data["url"] = "https://my-new-fake-url.com"
|
||||
webhook_data["enabled"] = False
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.groups_webhooks_item_id(item_id),
|
||||
json=jsonify(webhook_data),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
updated_webhook = assert_deserialize(response, 200)
|
||||
|
||||
assert updated_webhook["name"] == webhook_data["name"]
|
||||
assert updated_webhook["url"] == webhook_data["url"]
|
||||
assert updated_webhook["enabled"] == webhook_data["enabled"]
|
||||
|
||||
|
||||
def test_delete_webhook(api_client: TestClient, webhook_data, unique_user: TestUser):
|
||||
response = api_client.post(
|
||||
api_routes.groups_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)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.groups_webhooks_item_id(item_id), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
|
@ -1,191 +0,0 @@
|
|||
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.labels.multi_purpose_label import MultiPurposeLabelOut
|
||||
from mealie.services.seeder.seeder_service import SeederService
|
||||
from tests.utils import api_routes, jsonify
|
||||
from tests.utils.factories import random_int, random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
def create_labels(api_client: TestClient, unique_user: TestUser, count: int = 10) -> list[MultiPurposeLabelOut]:
|
||||
labels: list[MultiPurposeLabelOut] = []
|
||||
for _ in range(count):
|
||||
response = api_client.post(api_routes.groups_labels, json={"name": random_string()}, headers=unique_user.token)
|
||||
labels.append(MultiPurposeLabelOut.model_validate(response.json()))
|
||||
|
||||
return labels
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
new_list = ShoppingListOut.model_validate(response.json())
|
||||
|
||||
assert len(new_list.label_settings) == len(labels)
|
||||
label_settings_label_ids = [setting.label_id for setting in new_list.label_settings]
|
||||
for label in labels:
|
||||
assert label.id in label_settings_label_ids
|
||||
|
||||
|
||||
def test_new_label_creates_list_labels(api_client: TestClient, unique_user: TestUser):
|
||||
# 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
|
||||
)
|
||||
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)
|
||||
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(database: AllRepositories, api_client: TestClient, unique_user: TestUser):
|
||||
CREATED_LABELS = 21
|
||||
|
||||
# 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
|
||||
)
|
||||
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
|
||||
seeder.seed_labels("en-US")
|
||||
|
||||
response = api_client.get(api_routes.groups_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
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
new_list = ShoppingListOut.model_validate(response.json())
|
||||
|
||||
existing_label_settings = new_list.label_settings
|
||||
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)
|
||||
updated_list = ShoppingListOut.model_validate(response.json())
|
||||
assert len(updated_list.label_settings) == len(existing_label_settings) - 1
|
||||
|
||||
label_settings_label_ids = [setting.label_id for setting in updated_list.label_settings]
|
||||
for label in new_labels:
|
||||
if label.id == label_to_delete.id:
|
||||
assert label.id not in label_settings_label_ids
|
||||
|
||||
else:
|
||||
assert label.id in label_settings_label_ids
|
||||
|
||||
|
||||
def test_update_list_doesnt_change_list_labels(api_client: TestClient, unique_user: TestUser):
|
||||
create_labels(api_client, unique_user)
|
||||
original_name = random_string()
|
||||
updated_name = random_string()
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.groups_shopping_lists, json={"name": original_name}, headers=unique_user.token
|
||||
)
|
||||
new_list = ShoppingListOut.model_validate(response.json())
|
||||
assert new_list.name == original_name
|
||||
assert new_list.label_settings
|
||||
|
||||
updated_list_data = new_list.model_dump()
|
||||
updated_list_data.pop("created_at", None)
|
||||
updated_list_data.pop("update_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),
|
||||
json=jsonify(updated_list_data),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
updated_list = ShoppingListOut.model_validate(response.json())
|
||||
assert updated_list.name == updated_name
|
||||
assert updated_list.label_settings == new_list.label_settings
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
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),
|
||||
json=jsonify(new_list.label_settings),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
updated_list = ShoppingListOut.model_validate(response.json())
|
||||
|
||||
original_settings_by_id = {setting.id: setting for setting in new_list.label_settings}
|
||||
for setting in updated_list.label_settings:
|
||||
assert setting.id in original_settings_by_id
|
||||
assert original_settings_by_id[setting.id].shopping_list_id == setting.shopping_list_id
|
||||
assert original_settings_by_id[setting.id].label_id == setting.label_id
|
||||
|
||||
if setting.id == changed_setting.id:
|
||||
assert setting.position == changed_setting.position
|
||||
|
||||
else:
|
||||
assert original_settings_by_id[setting.id].position == setting.position
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
new_list = ShoppingListOut.model_validate(response.json())
|
||||
for i, setting in enumerate(new_list.label_settings):
|
||||
if not i:
|
||||
continue
|
||||
|
||||
assert setting.position > new_list.label_settings[i - 1].position
|
||||
|
||||
random.shuffle(new_list.label_settings)
|
||||
response = api_client.put(
|
||||
api_routes.groups_shopping_lists_item_id_label_settings(new_list.id),
|
||||
json=jsonify(new_list.label_settings),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
updated_list = ShoppingListOut.model_validate(response.json())
|
||||
for i, setting in enumerate(updated_list.label_settings):
|
||||
if not i:
|
||||
continue
|
||||
|
||||
assert setting.position > updated_list.label_settings[i - 1].position
|
Loading…
Add table
Add a link
Reference in a new issue