mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-21 14:03:32 -07:00
feat: User-specific Recipe Ratings (#3345)
This commit is contained in:
parent
8ab09cf03b
commit
2a541f081a
50 changed files with 1497 additions and 443 deletions
|
@ -1,64 +0,0 @@
|
|||
from typing import Generator
|
||||
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.factories import random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def ten_slugs(
|
||||
api_client: TestClient, unique_user: TestUser, database: AllRepositories
|
||||
) -> Generator[list[str], None, None]:
|
||||
slugs = []
|
||||
|
||||
for _ in range(10):
|
||||
payload = {"name": random_string(length=20)}
|
||||
response = api_client.post(api_routes.recipes, json=payload, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
response_data = response.json()
|
||||
slugs.append(response_data)
|
||||
|
||||
yield slugs
|
||||
|
||||
for slug in slugs:
|
||||
try:
|
||||
database.recipes.delete(slug)
|
||||
except sqlalchemy.exc.NoResultFound:
|
||||
pass
|
||||
|
||||
|
||||
def test_recipe_favorites(api_client: TestClient, unique_user: TestUser, ten_slugs: list[str]):
|
||||
# Check that the user has no favorites
|
||||
response = api_client.get(api_routes.users_id_favorites(unique_user.user_id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["favoriteRecipes"] == []
|
||||
|
||||
# Add a few recipes to the user's favorites
|
||||
for slug in ten_slugs:
|
||||
response = api_client.post(
|
||||
api_routes.users_id_favorites_slug(unique_user.user_id, slug), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Check that the user has the recipes in their favorites
|
||||
response = api_client.get(api_routes.users_id_favorites(unique_user.user_id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()["favoriteRecipes"]) == 10
|
||||
|
||||
# Remove a few recipes from the user's favorites
|
||||
for slug in ten_slugs[:5]:
|
||||
response = api_client.delete(
|
||||
api_routes.users_id_favorites_slug(unique_user.user_id, slug), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Check that the user has the recipes in their favorites
|
||||
response = api_client.get(api_routes.users_id_favorites(unique_user.user_id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()["favoriteRecipes"]) == 5
|
364
tests/integration_tests/user_recipe_tests/test_recipe_ratings.py
Normal file
364
tests/integration_tests/user_recipe_tests/test_recipe_ratings.py
Normal file
|
@ -0,0 +1,364 @@
|
|||
import random
|
||||
from typing import Generator
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.recipe.recipe import Recipe
|
||||
from mealie.schema.user.user import UserRatingUpdate
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.factories import random_bool, random_int, random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def recipes(database: AllRepositories, user_tuple: tuple[TestUser, TestUser]) -> Generator[list[Recipe], None, None]:
|
||||
unique_user = random.choice(user_tuple)
|
||||
recipes_repo = database.recipes.by_group(UUID(unique_user.group_id))
|
||||
|
||||
recipes: list[Recipe] = []
|
||||
for _ in range(random_int(10, 20)):
|
||||
slug = random_string()
|
||||
recipes.append(
|
||||
recipes_repo.create(
|
||||
Recipe(
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=slug,
|
||||
slug=slug,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
yield recipes
|
||||
for recipe in recipes:
|
||||
try:
|
||||
recipes_repo.delete(recipe.id, match_key="id")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize("use_self_route", [True, False])
|
||||
def test_user_recipe_favorites(
|
||||
api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe], use_self_route: bool
|
||||
):
|
||||
# we use two different users because pytest doesn't support function-scopes within parametrized tests
|
||||
if use_self_route:
|
||||
unique_user = user_tuple[0]
|
||||
else:
|
||||
unique_user = user_tuple[1]
|
||||
|
||||
response = api_client.get(api_routes.users_id_favorites(unique_user.user_id), headers=unique_user.token)
|
||||
assert response.json()["ratings"] == []
|
||||
|
||||
recipes_to_favorite = random.sample(recipes, random_int(5, len(recipes)))
|
||||
|
||||
# add favorites
|
||||
for recipe in recipes_to_favorite:
|
||||
response = api_client.post(
|
||||
api_routes.users_id_favorites_slug(unique_user.user_id, recipe.slug), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
if use_self_route:
|
||||
get_url = api_routes.users_self_favorites
|
||||
else:
|
||||
get_url = api_routes.users_id_favorites(unique_user.user_id)
|
||||
|
||||
response = api_client.get(get_url, headers=unique_user.token)
|
||||
ratings = response.json()["ratings"]
|
||||
|
||||
assert len(ratings) == len(recipes_to_favorite)
|
||||
fetched_recipe_ids = set(rating["recipeId"] for rating in ratings)
|
||||
favorited_recipe_ids = set(str(recipe.id) for recipe in recipes_to_favorite)
|
||||
assert fetched_recipe_ids == favorited_recipe_ids
|
||||
|
||||
# remove favorites
|
||||
recipe_favorites_to_remove = random.sample(recipes_to_favorite, 3)
|
||||
for recipe in recipe_favorites_to_remove:
|
||||
response = api_client.delete(
|
||||
api_routes.users_id_favorites_slug(unique_user.user_id, recipe.slug), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(get_url, headers=unique_user.token)
|
||||
ratings = response.json()["ratings"]
|
||||
|
||||
assert len(ratings) == len(recipes_to_favorite) - len(recipe_favorites_to_remove)
|
||||
fetched_recipe_ids = set(rating["recipeId"] for rating in ratings)
|
||||
removed_recipe_ids = set(str(recipe.id) for recipe in recipe_favorites_to_remove)
|
||||
assert fetched_recipe_ids == favorited_recipe_ids - removed_recipe_ids
|
||||
|
||||
|
||||
@pytest.mark.parametrize("add_favorite", [True, False])
|
||||
def test_set_user_favorite_invalid_recipe_404(
|
||||
api_client: TestClient, user_tuple: tuple[TestUser, TestUser], add_favorite: bool
|
||||
):
|
||||
unique_user = random.choice(user_tuple)
|
||||
if add_favorite:
|
||||
response = api_client.post(
|
||||
api_routes.users_id_favorites_slug(unique_user.user_id, random_string()), headers=unique_user.token
|
||||
)
|
||||
else:
|
||||
response = api_client.delete(
|
||||
api_routes.users_id_favorites_slug(unique_user.user_id, random_string()), headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.parametrize("use_self_route", [True, False])
|
||||
def test_set_user_recipe_ratings(
|
||||
api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe], use_self_route: bool
|
||||
):
|
||||
# we use two different users because pytest doesn't support function-scopes within parametrized tests
|
||||
if use_self_route:
|
||||
unique_user = user_tuple[0]
|
||||
else:
|
||||
unique_user = user_tuple[1]
|
||||
|
||||
response = api_client.get(api_routes.users_id_ratings(unique_user.user_id), headers=unique_user.token)
|
||||
assert response.json()["ratings"] == []
|
||||
|
||||
recipes_to_rate = random.sample(recipes, random_int(8, len(recipes)))
|
||||
|
||||
expected_ratings_by_recipe_id: dict[str, UserRatingUpdate] = {}
|
||||
for recipe in recipes_to_rate:
|
||||
new_rating = UserRatingUpdate(
|
||||
rating=random.uniform(1, 5),
|
||||
)
|
||||
expected_ratings_by_recipe_id[str(recipe.id)] = new_rating
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, recipe.slug),
|
||||
json=new_rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
if use_self_route:
|
||||
get_url = api_routes.users_self_ratings
|
||||
else:
|
||||
get_url = api_routes.users_id_ratings(unique_user.user_id)
|
||||
|
||||
response = api_client.get(get_url, headers=unique_user.token)
|
||||
ratings = response.json()["ratings"]
|
||||
|
||||
assert len(ratings) == len(recipes_to_rate)
|
||||
for rating in ratings:
|
||||
recipe_id = rating["recipeId"]
|
||||
assert rating["rating"] == expected_ratings_by_recipe_id[recipe_id].rating
|
||||
assert not rating["isFavorite"]
|
||||
|
||||
|
||||
def test_set_user_rating_invalid_recipe_404(api_client: TestClient, user_tuple: tuple[TestUser, TestUser]):
|
||||
unique_user = random.choice(user_tuple)
|
||||
rating = UserRatingUpdate(rating=random.uniform(1, 5))
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, random_string()),
|
||||
json=rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_set_rating_and_favorite(api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe]):
|
||||
unique_user = random.choice(user_tuple)
|
||||
recipe = random.choice(recipes)
|
||||
|
||||
rating = UserRatingUpdate(rating=random.uniform(1, 5), is_favorite=True)
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, recipe.slug),
|
||||
json=rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
|
||||
data = response.json()
|
||||
assert data["recipeId"] == str(recipe.id)
|
||||
assert data["rating"] == rating.rating
|
||||
assert data["isFavorite"] is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("favorite_value", [True, False])
|
||||
def test_set_rating_preserve_favorite(
|
||||
api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe], favorite_value: bool
|
||||
):
|
||||
initial_rating_value = 1
|
||||
updated_rating_value = 5
|
||||
|
||||
unique_user = random.choice(user_tuple)
|
||||
recipe = random.choice(recipes)
|
||||
rating = UserRatingUpdate(rating=initial_rating_value, is_favorite=favorite_value)
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, recipe.slug),
|
||||
json=rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
|
||||
data = response.json()
|
||||
assert data["recipeId"] == str(recipe.id)
|
||||
assert data["rating"] == initial_rating_value
|
||||
assert data["isFavorite"] == favorite_value
|
||||
|
||||
rating.rating = updated_rating_value
|
||||
rating.is_favorite = None # this should be ignored and the favorite value should be preserved
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, recipe.slug),
|
||||
json=rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
|
||||
data = response.json()
|
||||
assert data["recipeId"] == str(recipe.id)
|
||||
assert data["rating"] == updated_rating_value
|
||||
assert data["isFavorite"] == favorite_value
|
||||
|
||||
|
||||
def test_set_favorite_preserve_rating(
|
||||
api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe]
|
||||
):
|
||||
rating_value = random.uniform(1, 5)
|
||||
initial_favorite_value = random_bool()
|
||||
|
||||
unique_user = random.choice(user_tuple)
|
||||
recipe = random.choice(recipes)
|
||||
rating = UserRatingUpdate(rating=rating_value, is_favorite=initial_favorite_value)
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, recipe.slug),
|
||||
json=rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
|
||||
data = response.json()
|
||||
assert data["recipeId"] == str(recipe.id)
|
||||
assert data["rating"] == rating_value
|
||||
assert data["isFavorite"] is initial_favorite_value
|
||||
|
||||
rating.is_favorite = not initial_favorite_value
|
||||
rating.rating = None # this should be ignored and the rating value should be preserved
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, recipe.slug),
|
||||
json=rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
|
||||
data = response.json()
|
||||
assert data["recipeId"] == str(recipe.id)
|
||||
assert data["rating"] == rating_value
|
||||
assert data["isFavorite"] is not initial_favorite_value
|
||||
|
||||
|
||||
def test_set_rating_to_zero(api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe]):
|
||||
unique_user = random.choice(user_tuple)
|
||||
recipe = random.choice(recipes)
|
||||
|
||||
rating_value = random.uniform(1, 5)
|
||||
rating = UserRatingUpdate(rating=rating_value)
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, recipe.slug),
|
||||
json=rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
|
||||
data = response.json()
|
||||
assert data["rating"] == rating_value
|
||||
|
||||
rating.rating = 0
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, recipe.slug),
|
||||
json=rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
|
||||
data = response.json()
|
||||
assert data["rating"] == 0
|
||||
|
||||
|
||||
def test_delete_recipe_deletes_ratings(
|
||||
database: AllRepositories, api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe]
|
||||
):
|
||||
unique_user = random.choice(user_tuple)
|
||||
recipe = random.choice(recipes)
|
||||
rating = UserRatingUpdate(rating=random.uniform(1, 5), is_favorite=random.choice([True, False, None]))
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, recipe.slug),
|
||||
json=rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
assert response.json()
|
||||
|
||||
database.recipes.delete(recipe.id, match_key="id")
|
||||
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_recipe_rating_is_average_user_rating(
|
||||
api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe]
|
||||
):
|
||||
recipe = random.choice(recipes)
|
||||
user_ratings = (UserRatingUpdate(rating=5), UserRatingUpdate(rating=2))
|
||||
|
||||
for i, user in enumerate(user_tuple):
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(user.user_id, recipe.slug),
|
||||
json=user_ratings[i].model_dump(),
|
||||
headers=user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.recipes_slug(recipe.slug), headers=user_tuple[0].token)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["rating"] == 3.5
|
||||
|
||||
|
||||
def test_recipe_rating_is_readonly(
|
||||
api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe]
|
||||
):
|
||||
unique_user = random.choice(user_tuple)
|
||||
recipe = random.choice(recipes)
|
||||
|
||||
rating = UserRatingUpdate(rating=random.uniform(1, 5), is_favorite=random.choice([True, False, None]))
|
||||
response = api_client.post(
|
||||
api_routes.users_id_ratings_slug(unique_user.user_id, recipe.slug),
|
||||
json=rating.model_dump(),
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(api_routes.recipes_slug(recipe.slug), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["rating"] == rating.rating
|
||||
|
||||
# try to update the rating manually and verify it didn't change
|
||||
new_rating = random.uniform(1, 5)
|
||||
assert new_rating != rating.rating
|
||||
response = api_client.patch(
|
||||
api_routes.recipes_slug(recipe.slug), json={"rating": new_rating}, headers=unique_user.token
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["rating"] == rating.rating
|
||||
|
||||
response = api_client.get(api_routes.recipes_slug(recipe.slug), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["rating"] == rating.rating
|
|
@ -1,5 +1,6 @@
|
|||
from datetime import datetime
|
||||
from typing import cast
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -10,7 +11,7 @@ from mealie.schema.recipe.recipe import Recipe, RecipeCategory, RecipeSummary
|
|||
from mealie.schema.recipe.recipe_category import CategoryOut, CategorySave, TagSave
|
||||
from mealie.schema.recipe.recipe_tool import RecipeToolSave
|
||||
from mealie.schema.response import OrderDirection, PaginationQuery
|
||||
from mealie.schema.user.user import GroupBase
|
||||
from mealie.schema.user.user import GroupBase, UserRatingCreate
|
||||
from tests.utils.factories import random_email, random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
@ -658,3 +659,126 @@ def test_random_order_recipe_search(
|
|||
pagination.pagination_seed = str(datetime.now())
|
||||
random_ordered.append(repo.page_all(pagination, search="soup").items)
|
||||
assert not all(i == random_ordered[0] for i in random_ordered)
|
||||
|
||||
|
||||
def test_order_by_rating(database: AllRepositories, user_tuple: tuple[TestUser, TestUser]):
|
||||
user_1, user_2 = user_tuple
|
||||
repo = database.recipes.by_group(UUID(user_1.group_id))
|
||||
|
||||
recipes: list[Recipe] = []
|
||||
for i in range(3):
|
||||
slug = f"recipe-{i+1}-{random_string(5)}"
|
||||
recipes.append(
|
||||
database.recipes.create(
|
||||
Recipe(
|
||||
user_id=user_1.user_id,
|
||||
group_id=user_1.group_id,
|
||||
name=slug,
|
||||
slug=slug,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# set the rating for user_1 and confirm both users see the same ordering
|
||||
recipe_1, recipe_2, recipe_3 = recipes
|
||||
database.user_ratings.create(
|
||||
UserRatingCreate(
|
||||
user_id=user_1.user_id,
|
||||
recipe_id=recipe_1.id,
|
||||
rating=5,
|
||||
)
|
||||
)
|
||||
database.user_ratings.create(
|
||||
UserRatingCreate(
|
||||
user_id=user_1.user_id,
|
||||
recipe_id=recipe_2.id,
|
||||
rating=4,
|
||||
)
|
||||
)
|
||||
database.user_ratings.create(
|
||||
UserRatingCreate(
|
||||
user_id=user_1.user_id,
|
||||
recipe_id=recipe_3.id,
|
||||
rating=3,
|
||||
)
|
||||
)
|
||||
|
||||
pq = PaginationQuery(page=1, per_page=-1, order_by="rating", order_direction=OrderDirection.desc)
|
||||
data_1 = repo.by_user(user_1.user_id).page_all(pq).items
|
||||
data_2 = repo.by_user(user_2.user_id).page_all(pq).items
|
||||
for data in [data_1, data_2]:
|
||||
assert len(data) == 3
|
||||
assert data[0].slug == recipe_1.slug # global and user rating == 5
|
||||
assert data[1].slug == recipe_2.slug # global and user rating == 4
|
||||
assert data[2].slug == recipe_3.slug # global and user rating == 3
|
||||
|
||||
pq = PaginationQuery(page=1, per_page=-1, order_by="rating", order_direction=OrderDirection.asc)
|
||||
data_1 = repo.by_user(user_1.user_id).page_all(pq).items
|
||||
data_2 = repo.by_user(user_2.user_id).page_all(pq).items
|
||||
for data in [data_1, data_2]:
|
||||
assert len(data) == 3
|
||||
assert data[0].slug == recipe_3.slug # global and user rating == 3
|
||||
assert data[1].slug == recipe_2.slug # global and user rating == 4
|
||||
assert data[2].slug == recipe_1.slug # global and user rating == 5
|
||||
|
||||
# set rating for one recipe for user_2 and confirm user_2 sees the correct order and user_1's order is unchanged
|
||||
database.user_ratings.create(
|
||||
UserRatingCreate(
|
||||
user_id=user_2.user_id,
|
||||
recipe_id=recipe_1.id,
|
||||
rating=3.5,
|
||||
)
|
||||
)
|
||||
|
||||
pq = PaginationQuery(page=1, per_page=-1, order_by="rating", order_direction=OrderDirection.desc)
|
||||
data_1 = repo.by_user(user_1.user_id).page_all(pq).items
|
||||
data_2 = repo.by_user(user_2.user_id).page_all(pq).items
|
||||
|
||||
assert len(data_1) == 3
|
||||
assert data_1[0].slug == recipe_1.slug # user rating == 5
|
||||
assert data_1[1].slug == recipe_2.slug # user rating == 4
|
||||
assert data_1[2].slug == recipe_3.slug # user rating == 3
|
||||
|
||||
assert len(data_2) == 3
|
||||
assert data_2[0].slug == recipe_2.slug # global rating == 4
|
||||
assert data_2[1].slug == recipe_1.slug # user rating == 3.5
|
||||
assert data_2[2].slug == recipe_3.slug # user rating == 3
|
||||
|
||||
pq = PaginationQuery(page=1, per_page=-1, order_by="rating", order_direction=OrderDirection.asc)
|
||||
data_1 = repo.by_user(user_1.user_id).page_all(pq).items
|
||||
data_2 = repo.by_user(user_2.user_id).page_all(pq).items
|
||||
|
||||
assert len(data_1) == 3
|
||||
assert data_1[0].slug == recipe_3.slug # global and user rating == 3
|
||||
assert data_1[1].slug == recipe_2.slug # global and user rating == 4
|
||||
assert data_1[2].slug == recipe_1.slug # global and user rating == 5
|
||||
|
||||
assert len(data_2) == 3
|
||||
assert data_2[0].slug == recipe_3.slug # user rating == 3
|
||||
assert data_2[1].slug == recipe_1.slug # user rating == 3.5
|
||||
assert data_2[2].slug == recipe_2.slug # global rating == 4
|
||||
|
||||
# verify public users see only global ratings
|
||||
database.user_ratings.create(
|
||||
UserRatingCreate(
|
||||
user_id=user_2.user_id,
|
||||
recipe_id=recipe_2.id,
|
||||
rating=1,
|
||||
)
|
||||
)
|
||||
|
||||
pq = PaginationQuery(page=1, per_page=-1, order_by="rating", order_direction=OrderDirection.desc)
|
||||
data = database.recipes.by_group(UUID(user_1.group_id)).page_all(pq).items
|
||||
|
||||
assert len(data) == 3
|
||||
assert data[0].slug == recipe_1.slug # global rating == 4.25 (avg of 5 and 3.5)
|
||||
assert data[1].slug == recipe_3.slug # global rating == 3
|
||||
assert data[2].slug == recipe_2.slug # global rating == 2.5 (avg of 4 and 1)
|
||||
|
||||
pq = PaginationQuery(page=1, per_page=-1, order_by="rating", order_direction=OrderDirection.asc)
|
||||
data = database.recipes.by_group(UUID(user_1.group_id)).page_all(pq).items
|
||||
|
||||
assert len(data) == 3
|
||||
assert data[0].slug == recipe_2.slug # global rating == 2.5 (avg of 4 and 1)
|
||||
assert data[1].slug == recipe_3.slug # global rating == 3
|
||||
assert data[2].slug == recipe_1.slug # global rating == 4.25 (avg of 5 and 3.5)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import filecmp
|
||||
import statistics
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
|
@ -8,11 +9,14 @@ from sqlalchemy.orm import Session
|
|||
import tests.data as test_data
|
||||
from mealie.core.config import get_app_settings
|
||||
from mealie.db.db_setup import session_context
|
||||
from mealie.db.models._model_utils import GUID
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.group.shopping_list import ShoppingList
|
||||
from mealie.db.models.labels import MultiPurposeLabel
|
||||
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.db.models.users.user_to_recipe import UserToRecipe
|
||||
from mealie.db.models.users.users import User
|
||||
from mealie.services.backups_v2.alchemy_exporter import AlchemyExporter
|
||||
from mealie.services.backups_v2.backup_file import BackupFile
|
||||
from mealie.services.backups_v2.backup_v2 import BackupV2
|
||||
|
@ -155,5 +159,18 @@ def test_database_restore_data(backup_path: Path):
|
|||
assert unit.name_normalized
|
||||
if unit.abbreviation:
|
||||
assert unit.abbreviation_normalized
|
||||
|
||||
# 2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_ratings
|
||||
users_by_group_id: dict[GUID, list[User]] = {}
|
||||
for recipe in recipes:
|
||||
users = users_by_group_id.get(recipe.group_id)
|
||||
if users is None:
|
||||
users = session.query(User).filter(User.group_id == recipe.group_id).all()
|
||||
users_by_group_id[recipe.group_id] = users
|
||||
|
||||
user_to_recipes = session.query(UserToRecipe).filter(UserToRecipe.recipe_id == recipe.id).all()
|
||||
user_ratings = [x.rating for x in user_to_recipes if x.rating]
|
||||
assert recipe.rating == (statistics.mean(user_ratings) if user_ratings else None)
|
||||
|
||||
finally:
|
||||
backup_v2.restore(original_data_backup)
|
||||
|
|
|
@ -181,8 +181,12 @@ users_reset_password = "/api/users/reset-password"
|
|||
"""`/api/users/reset-password`"""
|
||||
users_self = "/api/users/self"
|
||||
"""`/api/users/self`"""
|
||||
users_self_favorites = "/api/users/self/favorites"
|
||||
"""`/api/users/self/favorites`"""
|
||||
users_self_group = "/api/users/self/group"
|
||||
"""`/api/users/self/group`"""
|
||||
users_self_ratings = "/api/users/self/ratings"
|
||||
"""`/api/users/self/ratings`"""
|
||||
utils_download = "/api/utils/download"
|
||||
"""`/api/utils/download`"""
|
||||
validators_group = "/api/validators/group"
|
||||
|
@ -490,6 +494,21 @@ def users_id_image(id):
|
|||
return f"{prefix}/users/{id}/image"
|
||||
|
||||
|
||||
def users_id_ratings(id):
|
||||
"""`/api/users/{id}/ratings`"""
|
||||
return f"{prefix}/users/{id}/ratings"
|
||||
|
||||
|
||||
def users_id_ratings_slug(id, slug):
|
||||
"""`/api/users/{id}/ratings/{slug}`"""
|
||||
return f"{prefix}/users/{id}/ratings/{slug}"
|
||||
|
||||
|
||||
def users_item_id(item_id):
|
||||
"""`/api/users/{item_id}`"""
|
||||
return f"{prefix}/users/{item_id}"
|
||||
|
||||
|
||||
def users_self_ratings_recipe_id(recipe_id):
|
||||
"""`/api/users/self/ratings/{recipe_id}`"""
|
||||
return f"{prefix}/users/self/ratings/{recipe_id}"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue