mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-06 05:55: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
|
@ -0,0 +1,127 @@
|
|||
import random
|
||||
from dataclasses import dataclass
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import UUID4
|
||||
|
||||
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, household_id: UUID4 | 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),
|
||||
"household_id": str(household_id),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestCookbook:
|
||||
id: UUID4
|
||||
slug: str
|
||||
name: str
|
||||
data: dict
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def cookbooks(unique_user: TestUser) -> list[TestCookbook]:
|
||||
database = unique_user.repos
|
||||
|
||||
data: list[ReadCookBook] = []
|
||||
yield_data: list[TestCookbook] = []
|
||||
for _ in range(3):
|
||||
cb = database.cookbooks.create(SaveCookBook(**get_page_data(unique_user.group_id, unique_user.household_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, unique_user.household_id)
|
||||
response = api_client.post(api_routes.households_cookbooks, json=page_data, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
def test_read_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
|
||||
sample = random.choice(cookbooks)
|
||||
response = api_client.get(api_routes.households_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, unique_user.household_id)
|
||||
|
||||
update_data["name"] = random_string(10)
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.households_cookbooks_item_id(cookbook.id), json=update_data, headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.households_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.households_cookbooks, json=utils.jsonify(reverse_order), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.households_cookbooks, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
known_ids = [x.id for x in cookbooks]
|
||||
|
||||
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.households_cookbooks_item_id(sample.id), headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.households_cookbooks_item_id(sample.slug), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
|
@ -0,0 +1,76 @@
|
|||
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.households_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.households_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["householdId"] == unique_user.household_id
|
||||
assert item["token"] == invite
|
||||
|
||||
|
||||
def register_user(api_client: TestClient, invite: str):
|
||||
# Test User can Join Group
|
||||
registration = user_registration_factory()
|
||||
registration.group = ""
|
||||
registration.household = ""
|
||||
registration.group_token = invite
|
||||
|
||||
response = api_client.post(api_routes.users_register, json=registration.model_dump(by_alias=True))
|
||||
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 and Household match
|
||||
r = api_client.get(api_routes.users_self, headers={"Authorization": f"Bearer {token}"})
|
||||
|
||||
assert r.status_code == 200
|
||||
assert r.json()["groupId"] == unique_user.group_id
|
||||
assert r.json()["householdId"] == unique_user.household_id
|
||||
|
||||
|
||||
def test_group_invitation_delete_after_uses(api_client: TestClient, invite: str) -> None:
|
||||
# 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
|
|
@ -0,0 +1,169 @@
|
|||
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.households_mealplans}?page={page}&perPage={perPage}&start_date={start_date}&end_date={end_date}"
|
||||
)
|
||||
|
||||
|
||||
def test_create_mealplan_no_recipe(api_client: TestClient, unique_user: TestUser):
|
||||
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.households_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.households_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.households_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.households_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.households_mealplans_item_id(plan_id), headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.households_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.households_mealplans, json=new_plan, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.households_mealplans, headers=unique_user.token, params={"page": 1, "perPage": -1}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()["items"]) >= 3
|
||||
|
||||
|
||||
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.households_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.households_mealplans, json=meal_plan, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
# Get meal plan for today
|
||||
response = api_client.get(api_routes.households_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")
|
|
@ -0,0 +1,133 @@
|
|||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.meal_plan.plan_rules import PlanRulesOut
|
||||
from mealie.schema.recipe.recipe import RecipeCategory
|
||||
from mealie.schema.recipe.recipe_category import CategorySave
|
||||
from tests import utils
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def category(unique_user: TestUser):
|
||||
database = unique_user.repos
|
||||
slug = utils.random_string(length=10)
|
||||
model = database.categories.create(CategorySave(group_id=unique_user.group_id, slug=slug, name=slug))
|
||||
|
||||
yield model
|
||||
|
||||
try:
|
||||
database.categories.delete(model.slug)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def plan_rule(api_client: TestClient, unique_user: TestUser):
|
||||
payload = {
|
||||
"groupId": unique_user.group_id,
|
||||
"householdId": unique_user.household_id,
|
||||
"day": "monday",
|
||||
"entryType": "breakfast",
|
||||
"categories": [],
|
||||
}
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.households_mealplans_rules, json=utils.jsonify(payload), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 201
|
||||
plan_rule = PlanRulesOut.model_validate(response.json())
|
||||
yield plan_rule
|
||||
|
||||
# cleanup
|
||||
api_client.delete(api_routes.households_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
|
||||
|
||||
|
||||
def test_group_mealplan_rules_create(api_client: TestClient, unique_user: TestUser, category: RecipeCategory):
|
||||
database = unique_user.repos
|
||||
payload = {
|
||||
"groupId": unique_user.group_id,
|
||||
"householdId": unique_user.household_id,
|
||||
"day": "monday",
|
||||
"entryType": "breakfast",
|
||||
"categories": [category.model_dump()],
|
||||
}
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.households_mealplans_rules, json=utils.jsonify(payload), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
||||
# Validate the response data
|
||||
response_data = response.json()
|
||||
assert response_data["groupId"] == str(unique_user.group_id)
|
||||
assert response_data["householdId"] == str(unique_user.household_id)
|
||||
assert response_data["day"] == "monday"
|
||||
assert response_data["entryType"] == "breakfast"
|
||||
assert len(response_data["categories"]) == 1
|
||||
assert response_data["categories"][0]["slug"] == category.slug
|
||||
|
||||
# Validate database entry
|
||||
rule = database.group_meal_plan_rules.get_one(UUID(response_data["id"]))
|
||||
assert rule
|
||||
|
||||
assert str(rule.group_id) == unique_user.group_id
|
||||
assert str(rule.household_id) == unique_user.household_id
|
||||
assert rule.day == "monday"
|
||||
assert rule.entry_type == "breakfast"
|
||||
assert len(rule.categories) == 1
|
||||
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.households_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Validate the response data
|
||||
response_data = response.json()
|
||||
assert response_data["id"] == str(plan_rule.id)
|
||||
assert response_data["groupId"] == str(unique_user.group_id)
|
||||
assert response_data["householdId"] == str(unique_user.household_id)
|
||||
assert response_data["day"] == "monday"
|
||||
assert response_data["entryType"] == "breakfast"
|
||||
assert len(response_data["categories"]) == 0
|
||||
|
||||
|
||||
def test_group_mealplan_rules_update(api_client: TestClient, unique_user: TestUser, plan_rule: PlanRulesOut):
|
||||
payload = {
|
||||
"groupId": unique_user.group_id,
|
||||
"householdId": unique_user.household_id,
|
||||
"day": "tuesday",
|
||||
"entryType": "lunch",
|
||||
}
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.households_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["householdId"] == str(unique_user.household_id)
|
||||
assert response_data["day"] == "tuesday"
|
||||
assert response_data["entryType"] == "lunch"
|
||||
assert len(response_data["categories"]) == 0
|
||||
|
||||
|
||||
def test_group_mealplan_rules_delete(api_client: TestClient, unique_user: TestUser, plan_rule: PlanRulesOut):
|
||||
response = api_client.get(api_routes.households_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.delete(api_routes.households_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.households_mealplans_rules_item_id(plan_rule.id), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
|
@ -0,0 +1,178 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.household.group_events import GroupEventNotifierCreate, GroupEventNotifierOptions
|
||||
from mealie.services.event_bus_service.event_bus_listeners import AppriseEventListener
|
||||
from mealie.services.event_bus_service.event_bus_service import Event
|
||||
from mealie.services.event_bus_service.event_types import (
|
||||
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.households_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.households_events_notifications_item_id(payload_as_dict["id"]), headers=unique_user.token
|
||||
)
|
||||
|
||||
|
||||
def test_ensure_apprise_url_is_secret(api_client: TestClient, unique_user: TestUser):
|
||||
payload = notifier_generator()
|
||||
response = api_client.post(api_routes.households_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.households_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.households_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.households_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.households_events_notifications_item_id(update_payload["id"]), headers=unique_user.token
|
||||
)
|
||||
|
||||
|
||||
def test_delete_apprise_notification(api_client: TestClient, unique_user: TestUser):
|
||||
payload = notifier_generator()
|
||||
response = api_client.post(api_routes.households_events_notifications, json=payload, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
payload_as_dict = response.json()
|
||||
|
||||
response = api_client.delete(
|
||||
api_routes.households_events_notifications_item_id(payload_as_dict["id"]), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 204
|
||||
|
||||
response = api_client.get(
|
||||
api_routes.households_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)
|
|
@ -0,0 +1,126 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.household.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.households_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 str(action_out.household_id) == unique_user.household_id
|
||||
assert action_out.action_type == action_in.action_type
|
||||
assert action_out.title == action_in.title
|
||||
assert action_out.url == action_in.url
|
||||
|
||||
|
||||
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.households_recipe_actions,
|
||||
json=new_link_action().model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
data = assert_deserialize(response, 201)
|
||||
expected_ids.add(data["id"])
|
||||
|
||||
response = api_client.get(api_routes.households_recipe_actions, headers=unique_user.token)
|
||||
data = assert_deserialize(response, 200)
|
||||
fetched_ids = {item["id"] for item in data["items"]}
|
||||
for expected_id in expected_ids:
|
||||
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.households_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.households_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.households_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.households_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.households_recipe_actions,
|
||||
json=action_in.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
data = assert_deserialize(response, 201)
|
||||
action_id = data["id"]
|
||||
|
||||
response = api_client.delete(api_routes.households_recipe_actions_item_id(action_id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.households_recipe_actions_item_id(action_id), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
|
@ -0,0 +1,683 @@
|
|||
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.household.group_shopping_list import ShoppingListItemOut, ShoppingListOut
|
||||
from mealie.schema.recipe.recipe_ingredient import SaveIngredientFood
|
||||
from tests import utils
|
||||
from tests.utils import api_routes
|
||||
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.households_shopping_items, json=item, headers=unique_user.token)
|
||||
as_json = utils.assert_deserialize(response, 201)
|
||||
assert len(as_json["createdItems"]) == 1
|
||||
|
||||
# Test Item is Getable
|
||||
created_item_id = as_json["createdItems"][0]["id"]
|
||||
response = api_client.get(
|
||||
api_routes.households_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.households_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.households_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.households_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 = unique_user.repos
|
||||
food = database.ingredient_foods.create(SaveIngredientFood(name=random_string(10), group_id=unique_user.group_id))
|
||||
|
||||
item = create_item(shopping_list.id, food_id=str(food.id))
|
||||
response = api_client.post(api_routes.households_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 = unique_user.repos
|
||||
label = database.group_multi_purpose_labels.create({"name": random_string(10), "group_id": unique_user.group_id})
|
||||
food = database.ingredient_foods.create(
|
||||
SaveIngredientFood(name=random_string(10), group_id=unique_user.group_id, label_id=label.id)
|
||||
)
|
||||
|
||||
item = create_item(shopping_list.id, food_id=str(food.id))
|
||||
response = api_client.post(api_routes.households_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,
|
||||
use_fuzzy_name: bool,
|
||||
):
|
||||
database = unique_user.repos
|
||||
label = database.group_multi_purpose_labels.create({"name": random_string(10), "group_id": unique_user.group_id})
|
||||
food = database.ingredient_foods.create(
|
||||
SaveIngredientFood(name=random_string(20), group_id=unique_user.group_id, label_id=label.id)
|
||||
)
|
||||
|
||||
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.households_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.households_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.households_shopping_items, params=params, headers=unique_user.token)
|
||||
pagination_json = utils.assert_deserialize(response, 200)
|
||||
assert len(pagination_json["items"]) == len(list_with_items.list_items)
|
||||
|
||||
|
||||
def test_shopping_list_items_get_one_404(api_client: TestClient, unique_user: TestUser) -> None:
|
||||
response = api_client.get(api_routes.households_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.households_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.households_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.households_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.households_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.households_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.households_shopping_items, json=as_dict, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
# retrieve list and check positions against list
|
||||
response = api_client.get(
|
||||
api_routes.households_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.households_shopping_items_item_id(item.id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Validate Get Item Returns 404
|
||||
response = api_client.get(api_routes.households_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.households_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.households_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.households_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.households_shopping_items, json=new_item, headers=unique_user.token)
|
||||
item_json = utils.assert_deserialize(response, 201)
|
||||
|
||||
# we should have received an updated item, not a created item
|
||||
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.households_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.households_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.households_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.households_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.households_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.households_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.households_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.households_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.households_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.households_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.households_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.households_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.households_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.households_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.households_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.households_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
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,86 @@
|
|||
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.households_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.households_webhooks,
|
||||
json=jsonify(webhook_data),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
item_id = response.json()["id"]
|
||||
|
||||
response = api_client.get(api_routes.households_webhooks_item_id(item_id), headers=unique_user.token)
|
||||
webhook = assert_deserialize(response, 200)
|
||||
|
||||
assert webhook["id"] == item_id
|
||||
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.households_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.households_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.households_webhooks,
|
||||
json=jsonify(webhook_data),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
item_dict = assert_deserialize(response, 201)
|
||||
item_id = item_dict["id"]
|
||||
|
||||
response = api_client.delete(api_routes.households_webhooks_item_id(item_id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.households_webhooks_item_id(item_id), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
|
@ -0,0 +1,47 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.household.household_preferences import UpdateHouseholdPreferences
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.assertion_helpers import assert_ignore_keys
|
||||
from tests.utils.factories import random_bool
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
def test_get_preferences(api_client: TestClient, unique_user: TestUser) -> None:
|
||||
response = api_client.get(api_routes.households_preferences, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
preferences = response.json()
|
||||
|
||||
assert preferences["recipePublic"] in {True, False}
|
||||
assert preferences["recipeShowNutrition"] in {True, False}
|
||||
|
||||
|
||||
def test_preferences_in_household(api_client: TestClient, unique_user: TestUser) -> None:
|
||||
response = api_client.get(api_routes.households_self, headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
household = response.json()
|
||||
|
||||
assert household["preferences"] is not None
|
||||
|
||||
# Spot Check
|
||||
assert household["preferences"]["recipePublic"] in {True, False}
|
||||
assert household["preferences"]["recipeShowNutrition"] in {True, False}
|
||||
|
||||
|
||||
def test_update_preferences(api_client: TestClient, unique_user: TestUser) -> None:
|
||||
new_data = UpdateHouseholdPreferences(recipe_public=random_bool(), recipe_show_nutrition=random_bool())
|
||||
|
||||
response = api_client.put(api_routes.households_preferences, json=new_data.model_dump(), headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
preferences = response.json()
|
||||
|
||||
assert preferences is not None
|
||||
assert preferences["recipePublic"] == new_data.recipe_public
|
||||
assert preferences["recipeShowNutrition"] == new_data.recipe_show_nutrition
|
||||
|
||||
assert_ignore_keys(new_data.model_dump(by_alias=True), preferences, ["id", "householdId"])
|
|
@ -0,0 +1,87 @@
|
|||
from uuid import uuid4
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
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_set_member_permissions(api_client: TestClient, user_tuple: list[TestUser]):
|
||||
usr_1, usr_2 = user_tuple
|
||||
|
||||
# Set Acting User
|
||||
acting_user = usr_1.repos.users.get_one(usr_1.user_id)
|
||||
assert acting_user
|
||||
acting_user.can_manage = True
|
||||
usr_1.repos.users.update(acting_user.id, acting_user)
|
||||
|
||||
payload = get_permissions_payload(str(usr_2.user_id))
|
||||
|
||||
# Test
|
||||
response = api_client.put(api_routes.households_permissions, json=payload, headers=usr_1.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_set_member_permissions_unauthorized(api_client: TestClient, unique_user: TestUser):
|
||||
database = unique_user.repos
|
||||
|
||||
# Setup
|
||||
user = database.users.get_one(unique_user.user_id)
|
||||
assert user
|
||||
user.can_manage = False
|
||||
database.users.update(user.id, user)
|
||||
|
||||
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.households_permissions, json=payload, headers=unique_user.token)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_set_member_permissions_other_household(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
h2_user: TestUser,
|
||||
):
|
||||
database = unique_user.repos
|
||||
|
||||
user = database.users.get_one(unique_user.user_id)
|
||||
assert user
|
||||
user.can_manage = True
|
||||
database.users.update(user.id, user)
|
||||
|
||||
payload = get_permissions_payload(str(h2_user.user_id))
|
||||
response = api_client.put(api_routes.households_permissions, json=payload, headers=unique_user.token)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_set_member_permissions_no_user(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
):
|
||||
database = unique_user.repos
|
||||
|
||||
user = database.users.get_one(unique_user.user_id)
|
||||
assert user
|
||||
user.can_manage = True
|
||||
database.users.update(user.id, user)
|
||||
|
||||
payload = get_permissions_payload(str(uuid4()))
|
||||
response = api_client.put(api_routes.households_permissions, json=payload, headers=unique_user.token)
|
||||
assert response.status_code == 404
|
|
@ -0,0 +1,20 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
def test_get_household_members(api_client: TestClient, user_tuple: list[TestUser], h2_user: TestUser):
|
||||
usr_1, usr_2 = user_tuple
|
||||
|
||||
response = api_client.get(api_routes.households_members, headers=usr_1.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
members = response.json()
|
||||
assert len(members) >= 2
|
||||
|
||||
all_ids = [x["id"] for x in members]
|
||||
|
||||
assert str(usr_1.user_id) in all_ids
|
||||
assert str(usr_2.user_id) in all_ids
|
||||
assert str(h2_user.user_id) not in all_ids
|
|
@ -0,0 +1,234 @@
|
|||
import random
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.household.group_shopping_list import ShoppingListOut
|
||||
from mealie.schema.labels.multi_purpose_label import MultiPurposeLabelOut
|
||||
from mealie.services.seeder.seeder_service import SeederService
|
||||
from tests.utils import api_routes, jsonify
|
||||
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.households_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.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
|
||||
)
|
||||
new_list = ShoppingListOut.model_validate(response.json())
|
||||
existing_label_settings = new_list.label_settings
|
||||
|
||||
# create more labels and make sure they were added to the list's label settings
|
||||
new_labels = create_labels(api_client, unique_user)
|
||||
response = api_client.get(api_routes.households_shopping_lists_item_id(new_list.id), headers=unique_user.token)
|
||||
updated_list = ShoppingListOut.model_validate(response.json())
|
||||
updated_label_settings = updated_list.label_settings
|
||||
assert len(updated_label_settings) == len(existing_label_settings) + len(new_labels)
|
||||
|
||||
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_new_label_creates_list_labels_in_all_households(
|
||||
api_client: TestClient, unique_user: TestUser, h2_user: TestUser
|
||||
):
|
||||
# unique_user and h2_user are in the same group, so these labels should be for both of them
|
||||
create_labels(api_client, unique_user)
|
||||
|
||||
# create a list with some labels for each user
|
||||
response = api_client.post(
|
||||
api_routes.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
|
||||
)
|
||||
new_list_h1 = ShoppingListOut.model_validate(response.json())
|
||||
existing_label_settings_h1 = new_list_h1.label_settings
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.households_shopping_lists, json={"name": random_string()}, headers=h2_user.token
|
||||
)
|
||||
new_list_h2 = ShoppingListOut.model_validate(response.json())
|
||||
existing_label_settings_h2 = new_list_h2.label_settings
|
||||
|
||||
# create more labels and make sure they were added to both lists' label settings
|
||||
new_labels = create_labels(api_client, unique_user)
|
||||
|
||||
for user, new_list, existing_label_settings in [
|
||||
(unique_user, new_list_h1, existing_label_settings_h1),
|
||||
(h2_user, new_list_h2, existing_label_settings_h2),
|
||||
]:
|
||||
response = api_client.get(api_routes.households_shopping_lists_item_id(new_list.id), headers=user.token)
|
||||
updated_list = ShoppingListOut.model_validate(response.json())
|
||||
updated_label_settings = updated_list.label_settings
|
||||
assert len(updated_label_settings) == len(existing_label_settings) + len(new_labels)
|
||||
|
||||
label_settings_ids = [setting.id for setting in updated_list.label_settings]
|
||||
for label_setting in existing_label_settings:
|
||||
assert label_setting.id in label_settings_ids
|
||||
|
||||
label_settings_label_ids = [setting.label_id for setting in updated_list.label_settings]
|
||||
for label in new_labels:
|
||||
assert label.id in label_settings_label_ids
|
||||
|
||||
|
||||
def test_seed_label_creates_list_labels(api_client: TestClient, unique_user: TestUser):
|
||||
CREATED_LABELS = 21
|
||||
database = unique_user.repos
|
||||
|
||||
# create a list with some labels
|
||||
create_labels(api_client, unique_user)
|
||||
response = api_client.post(
|
||||
api_routes.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
|
||||
)
|
||||
new_list = ShoppingListOut.model_validate(response.json())
|
||||
existing_label_settings = new_list.label_settings
|
||||
|
||||
# seed labels and make sure they were added to the list's label settings
|
||||
group = database.groups.get_one(unique_user.group_id)
|
||||
assert group
|
||||
database = AllRepositories(database.session, group_id=group.id)
|
||||
seeder = SeederService(database)
|
||||
seeder.seed_labels("en-US")
|
||||
|
||||
response = api_client.get(api_routes.households_shopping_lists_item_id(new_list.id), headers=unique_user.token)
|
||||
updated_list = ShoppingListOut.model_validate(response.json())
|
||||
updated_label_settings = updated_list.label_settings
|
||||
assert len(updated_label_settings) == len(existing_label_settings) + CREATED_LABELS
|
||||
|
||||
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.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
|
||||
)
|
||||
new_list = ShoppingListOut.model_validate(response.json())
|
||||
|
||||
existing_label_settings = new_list.label_settings
|
||||
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.households_shopping_lists_item_id(new_list.id), headers=unique_user.token)
|
||||
updated_list = ShoppingListOut.model_validate(response.json())
|
||||
assert len(updated_list.label_settings) == len(existing_label_settings) - 1
|
||||
|
||||
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.households_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("updated_at", None)
|
||||
|
||||
updated_list_data["name"] = updated_name
|
||||
updated_list_data["label_settings"][0]["position"] = random_int(999, 9999)
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.households_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.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
|
||||
)
|
||||
new_list = ShoppingListOut.model_validate(response.json())
|
||||
changed_setting = random.choice(new_list.label_settings)
|
||||
changed_setting.position = random_int(999, 9999)
|
||||
|
||||
response = api_client.put(
|
||||
api_routes.households_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.households_shopping_lists, json={"name": random_string()}, headers=unique_user.token
|
||||
)
|
||||
new_list = ShoppingListOut.model_validate(response.json())
|
||||
for i, setting in enumerate(new_list.label_settings):
|
||||
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.households_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