Add nutrition query filters and tests

This commit is contained in:
Garrett Potter 2025-01-08 03:56:19 +00:00
commit 5907dbf35d
3 changed files with 64 additions and 23 deletions

View file

@ -148,6 +148,27 @@ export default defineComponent({
label: i18n.tc("general.date-updated"), label: i18n.tc("general.date-updated"),
type: "date", type: "date",
}, },
{
name: "nutrition.calories",
label: i18n.tc("recipe.calories"),
type: "number",
},
{
name: "nutrition.carbohydrateContent",
label: i18n.tc("recipe.carbohydrate-content"),
type: "number",
},
{
name: "nutrition.proteinContent",
label: i18n.tc("recipe.protein-content"),
type: "number",
},
{
name: "nutrition.fatContent",
label: i18n.tc("recipe.fat-content"),
type: "number",
},
]; ];
return { return {

View file

@ -140,12 +140,20 @@ def test_group_mealplan_rules_delete(api_client: TestClient, unique_user: TestUs
"qf_string, expected_code", "qf_string, expected_code",
[ [
('tags.name CONTAINS ALL ["tag1","tag2"]', 200), ('tags.name CONTAINS ALL ["tag1","tag2"]', 200),
("nutrition.calories >= 100", 200),
("nutrition.fatContent <= 10", 200),
("nutrition.proteinContent >= 40", 200),
("nutrition.carbohydrateContent = 200", 200),
('badfield = "badvalue"', 422), ('badfield = "badvalue"', 422),
('recipe_category.id IN ["1"]', 422), ('recipe_category.id IN ["1"]', 422),
('created_at >= "not-a-date"', 422), ('created_at >= "not-a-date"', 422),
], ],
ids=[ ids=[
"valid qf", "valid qf",
"valid calorie filter",
"valid fat filter",
"valid protein filter",
"valid carb filter",
"invalid field", "invalid field",
"invalid UUID", "invalid UUID",
"invalid date", "invalid date",

View file

@ -1,7 +1,7 @@
import random import random
import time import time
from collections import defaultdict from collections import defaultdict
from datetime import datetime, timedelta, timezone from datetime import UTC, datetime, timedelta
from random import randint from random import randint
from urllib.parse import parse_qsl, urlsplit from urllib.parse import parse_qsl, urlsplit
@ -166,7 +166,10 @@ def test_pagination_guides(unique_user: TestUser):
next_params: dict = dict(parse_qsl(urlsplit(random_page_of_results.next).query)) # type: ignore next_params: dict = dict(parse_qsl(urlsplit(random_page_of_results.next).query)) # type: ignore
assert int(next_params["page"]) == random_page + 1 assert int(next_params["page"]) == random_page + 1
prev_params: dict = dict(parse_qsl(urlsplit(random_page_of_results.previous).query)) # type: ignore prev_params: dict = dict(
# type: ignore
parse_qsl(urlsplit(random_page_of_results.previous).query)
)
assert int(prev_params["page"]) == random_page - 1 assert int(prev_params["page"]) == random_page - 1
source_params = camelize(query.model_dump()) source_params = camelize(query.model_dump())
@ -238,7 +241,7 @@ def test_pagination_filter_null(unique_user: TestUser):
user_id=unique_user.user_id, user_id=unique_user.user_id,
group_id=unique_user.group_id, group_id=unique_user.group_id,
name=random_string(), name=random_string(),
last_made=datetime.now(timezone.utc), last_made=datetime.now(UTC),
) )
) )
@ -531,7 +534,7 @@ def test_pagination_filter_datetimes(
# units are created in order with increasing createdAt values # units are created in order with increasing createdAt values
units_repo, unit_1, unit_2, unit_3 = query_units units_repo, unit_1, unit_2, unit_3 = query_units
## GT # GT
past_dt: datetime = unit_1.created_at - timedelta(seconds=1) # type: ignore past_dt: datetime = unit_1.created_at - timedelta(seconds=1) # type: ignore
dt = past_dt.isoformat() dt = past_dt.isoformat()
query = PaginationQuery(page=1, per_page=-1, query_filter=f'createdAt>"{dt}"') query = PaginationQuery(page=1, per_page=-1, query_filter=f'createdAt>"{dt}"')
@ -573,7 +576,7 @@ def test_pagination_filter_datetimes(
unit_ids = {unit.id for unit in unit_results} unit_ids = {unit.id for unit in unit_results}
assert len(unit_ids) == 0 assert len(unit_ids) == 0
## GTE # GTE
past_dt = unit_1.created_at - timedelta(seconds=1) # type: ignore past_dt = unit_1.created_at - timedelta(seconds=1) # type: ignore
dt = past_dt.isoformat() dt = past_dt.isoformat()
query = PaginationQuery(page=1, per_page=-1, query_filter=f'createdAt>="{dt}"') query = PaginationQuery(page=1, per_page=-1, query_filter=f'createdAt>="{dt}"')
@ -626,7 +629,7 @@ def test_pagination_filter_datetimes(
) )
def test_pagination_order_by_multiple(unique_user: TestUser, order_direction: OrderDirection): def test_pagination_order_by_multiple(unique_user: TestUser, order_direction: OrderDirection):
database = unique_user.repos database = unique_user.repos
current_time = datetime.now(timezone.utc) current_time = datetime.now(UTC)
alphabet = ["a", "b", "c", "d", "e"] alphabet = ["a", "b", "c", "d", "e"]
abbreviations = alphabet.copy() abbreviations = alphabet.copy()
@ -687,7 +690,7 @@ def test_pagination_order_by_multiple_directions(
unique_user: TestUser, order_by_str: str, order_direction: OrderDirection unique_user: TestUser, order_by_str: str, order_direction: OrderDirection
): ):
database = unique_user.repos database = unique_user.repos
current_time = datetime.now(timezone.utc) current_time = datetime.now(UTC)
alphabet = ["a", "b", "c", "d", "e"] alphabet = ["a", "b", "c", "d", "e"]
abbreviations = alphabet.copy() abbreviations = alphabet.copy()
@ -735,7 +738,7 @@ def test_pagination_order_by_multiple_directions(
) )
def test_pagination_order_by_nested_model(unique_user: TestUser, order_direction: OrderDirection): def test_pagination_order_by_nested_model(unique_user: TestUser, order_direction: OrderDirection):
database = unique_user.repos database = unique_user.repos
current_time = datetime.now(timezone.utc) current_time = datetime.now(UTC)
alphabet = ["a", "b", "c", "d", "e"] alphabet = ["a", "b", "c", "d", "e"]
labels = database.group_multi_purpose_labels.create_many( labels = database.group_multi_purpose_labels.create_many(
@ -766,7 +769,7 @@ def test_pagination_order_by_nested_model(unique_user: TestUser, order_direction
def test_pagination_order_by_doesnt_filter(unique_user: TestUser): def test_pagination_order_by_doesnt_filter(unique_user: TestUser):
database = unique_user.repos database = unique_user.repos
current_time = datetime.now(timezone.utc) current_time = datetime.now(UTC)
label = database.group_multi_purpose_labels.create( label = database.group_multi_purpose_labels.create(
MultiPurposeLabelSave(name=random_string(), group_id=unique_user.group_id) MultiPurposeLabelSave(name=random_string(), group_id=unique_user.group_id)
@ -810,7 +813,7 @@ def test_pagination_order_by_nulls(
unique_user: TestUser, null_position: OrderByNullPosition, order_direction: OrderDirection unique_user: TestUser, null_position: OrderByNullPosition, order_direction: OrderDirection
): ):
database = unique_user.repos database = unique_user.repos
current_time = datetime.now(timezone.utc) current_time = datetime.now(UTC)
label = database.group_multi_purpose_labels.create( label = database.group_multi_purpose_labels.create(
MultiPurposeLabelSave(name=random_string(), group_id=unique_user.group_id) MultiPurposeLabelSave(name=random_string(), group_id=unique_user.group_id)
@ -916,7 +919,7 @@ def test_pagination_shopping_list_items_with_labels(unique_user: TestUser):
def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser): def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
today = datetime.now(timezone.utc).date() today = datetime.now(UTC).date()
yesterday = today - timedelta(days=1) yesterday = today - timedelta(days=1)
tomorrow = today + timedelta(days=1) tomorrow = today + timedelta(days=1)
@ -936,7 +939,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
response = api_client.post(api_routes.households_mealplans, json=data, headers=unique_user.token) response = api_client.post(api_routes.households_mealplans, json=data, headers=unique_user.token)
assert response.status_code == 201 assert response.status_code == 201
## Yesterday # Yesterday
params = { params = {
"page": 1, "page": 1,
"perPage": -1, "perPage": -1,
@ -965,7 +968,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
assert mealplan_today.title in fetched_mealplan_titles assert mealplan_today.title in fetched_mealplan_titles
assert mealplan_tomorrow.title in fetched_mealplan_titles assert mealplan_tomorrow.title in fetched_mealplan_titles
## Today # Today
params = { params = {
"page": 1, "page": 1,
"perPage": -1, "perPage": -1,
@ -994,7 +997,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
assert mealplan_today.title not in fetched_mealplan_titles assert mealplan_today.title not in fetched_mealplan_titles
assert mealplan_tomorrow.title in fetched_mealplan_titles assert mealplan_tomorrow.title in fetched_mealplan_titles
## Tomorrow # Tomorrow
params = { params = {
"page": 1, "page": 1,
"perPage": -1, "perPage": -1,
@ -1020,7 +1023,7 @@ def test_pagination_filter_dates(api_client: TestClient, unique_user: TestUser):
assert len(response_json["items"]) == 0 assert len(response_json["items"]) == 0
## Day After Tomorrow # Day After Tomorrow
params = { params = {
"page": 1, "page": 1,
"perPage": -1, "perPage": -1,
@ -1049,7 +1052,8 @@ def test_pagination_filter_booleans(query_units: tuple[RepositoryUnit, Ingredien
query = PaginationQuery( query = PaginationQuery(
page=1, page=1,
per_page=-1, per_page=-1,
query_filter=f"useAbbreviation=true AND id IN [{', '.join([str(unit.id) for unit in query_units[1:]])}]", query_filter=f"useAbbreviation=true AND id IN [{
', '.join([str(unit.id) for unit in query_units[1:]])}]",
) )
unit_results = units_repo.page_all(query).items unit_results = units_repo.page_all(query).items
assert len(unit_results) == 1 assert len(unit_results) == 1
@ -1060,7 +1064,8 @@ def test_pagination_filter_advanced(query_units: tuple[RepositoryUnit, Ingredien
units_repo, unit_1, unit_2, unit_3 = query_units units_repo, unit_1, unit_2, unit_3 = query_units
dt = str(unit_3.created_at.isoformat()) # type: ignore dt = str(unit_3.created_at.isoformat()) # type: ignore
qf = f'name="test unit 1" OR (useAbbreviation=f AND (name="{unit_2.name}" OR createdAt > "{dt}"))' qf = f'name="test unit 1" OR (useAbbreviation=f AND (name="{
unit_2.name}" OR createdAt > "{dt}"))'
query = PaginationQuery(page=1, per_page=-1, query_filter=qf) query = PaginationQuery(page=1, per_page=-1, query_filter=qf)
unit_results = units_repo.page_all(query).items unit_results = units_repo.page_all(query).items
@ -1069,7 +1074,8 @@ def test_pagination_filter_advanced(query_units: tuple[RepositoryUnit, Ingredien
assert unit_2.id in result_ids assert unit_2.id in result_ids
assert unit_3.id not in result_ids assert unit_3.id not in result_ids
qf = f'(name LIKE %_1 OR name IN ["{unit_2.name}"]) AND createdAt IS NOT NONE' qf = f'(name LIKE %_1 OR name IN ["{
unit_2.name}"]) AND createdAt IS NOT NONE'
query = PaginationQuery(page=1, per_page=-1, query_filter=qf) query = PaginationQuery(page=1, per_page=-1, query_filter=qf)
unit_results = units_repo.page_all(query).items unit_results = units_repo.page_all(query).items
@ -1184,7 +1190,8 @@ def test_pagination_filter_advanced_frontend_sort(unique_user: TestUser):
repo = database.recipes repo = database.recipes
qf = f'recipeCategory.id IN ["{category_1.id}"] AND tools.id IN ["{tool_1.id}"]' qf = f'recipeCategory.id IN ["{
category_1.id}"] AND tools.id IN ["{tool_1.id}"]'
query = PaginationQuery(page=1, per_page=-1, query_filter=qf) query = PaginationQuery(page=1, per_page=-1, query_filter=qf)
recipe_results = repo.page_all(query).items recipe_results = repo.page_all(query).items
assert len(recipe_results) == 1 assert len(recipe_results) == 1
@ -1197,7 +1204,8 @@ def test_pagination_filter_advanced_frontend_sort(unique_user: TestUser):
assert recipe_ct0_tg2_tl2.id not in recipe_ids assert recipe_ct0_tg2_tl2.id not in recipe_ids
assert recipe_ct12_tg12_tl2.id not in recipe_ids assert recipe_ct12_tg12_tl2.id not in recipe_ids
qf = f'recipeCategory.id CONTAINS ALL ["{category_1.id}", "{category_2.id}"] AND tags.id IN ["{tag_1.id}"]' qf = f'recipeCategory.id CONTAINS ALL ["{category_1.id}", "{
category_2.id}"] AND tags.id IN ["{tag_1.id}"]'
query = PaginationQuery(page=1, per_page=-1, query_filter=qf) query = PaginationQuery(page=1, per_page=-1, query_filter=qf)
recipe_results = repo.page_all(query).items recipe_results = repo.page_all(query).items
assert len(recipe_results) == 1 assert len(recipe_results) == 1
@ -1210,7 +1218,8 @@ def test_pagination_filter_advanced_frontend_sort(unique_user: TestUser):
assert recipe_ct0_tg2_tl2.id not in recipe_ids assert recipe_ct0_tg2_tl2.id not in recipe_ids
assert recipe_ct12_tg12_tl2.id in recipe_ids assert recipe_ct12_tg12_tl2.id in recipe_ids
qf = f'tags.id IN ["{tag_1.id}", "{tag_2.id}"] AND tools.id IN ["{tool_2.id}"]' qf = f'tags.id IN ["{tag_1.id}", "{
tag_2.id}"] AND tools.id IN ["{tool_2.id}"]'
query = PaginationQuery(page=1, per_page=-1, query_filter=qf) query = PaginationQuery(page=1, per_page=-1, query_filter=qf)
recipe_results = repo.page_all(query).items recipe_results = repo.page_all(query).items
assert len(recipe_results) == 2 assert len(recipe_results) == 2
@ -1224,8 +1233,10 @@ def test_pagination_filter_advanced_frontend_sort(unique_user: TestUser):
assert recipe_ct12_tg12_tl2.id in recipe_ids assert recipe_ct12_tg12_tl2.id in recipe_ids
qf = ( qf = (
f'recipeCategory.id CONTAINS ALL ["{category_1.id}", "{category_2.id}"]' f'recipeCategory.id CONTAINS ALL ["{
f'AND tags.id IN ["{tag_1.id}", "{tag_2.id}"] AND tools.id IN ["{tool_1.id}", "{tool_2.id}"]' category_1.id}", "{category_2.id}"]'
f'AND tags.id IN ["{tag_1.id}", "{
tag_2.id}"] AND tools.id IN ["{tool_1.id}", "{tool_2.id}"]'
) )
query = PaginationQuery(page=1, per_page=-1, query_filter=qf) query = PaginationQuery(page=1, per_page=-1, query_filter=qf)
recipe_results = repo.page_all(query).items recipe_results = repo.page_all(query).items
@ -1265,6 +1276,7 @@ def test_pagination_filter_advanced_frontend_sort(unique_user: TestUser):
'group.preferences.badAttribute="test value"', 'group.preferences.badAttribute="test value"',
id="bad double nested attribute", id="bad double nested attribute",
), ),
pytest.param('nutrition.calories="test"', id="comparing int to string"),
], ],
) )
def test_malformed_query_filters(api_client: TestClient, unique_user: TestUser, qf: str): def test_malformed_query_filters(api_client: TestClient, unique_user: TestUser, qf: str):