From 140bd75412f1f21e4471759dfe8e36a1d0c5270c Mon Sep 17 00:00:00 2001 From: Michael Genson Date: Thu, 14 May 2026 19:15:36 +0000 Subject: [PATCH] add tests --- .../test_query_filter_builder.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/unit_tests/repository_tests/test_query_filter_builder.py b/tests/unit_tests/repository_tests/test_query_filter_builder.py index 9015a4a52..b83b055d8 100644 --- a/tests/unit_tests/repository_tests/test_query_filter_builder.py +++ b/tests/unit_tests/repository_tests/test_query_filter_builder.py @@ -1,3 +1,8 @@ +import pytest +import sqlalchemy as sa + +from mealie.db.models.recipe.recipe import RecipeModel +from mealie.db.models.users.users import LongLiveToken, User from mealie.services.query_filter.builder import ( LogicalOperator, QueryFilterBuilder, @@ -6,6 +11,7 @@ from mealie.services.query_filter.builder import ( RelationalKeyword, RelationalOperator, ) +from mealie.services.query_filter.context import allow_filter_restricted def test_query_filter_builder_json(): @@ -74,3 +80,76 @@ def test_query_filter_builder_json_uses_raw_value(): ), ] ) + + +# --------------------------------------------------------------------------- +# PrivateColumn tests +# --------------------------------------------------------------------------- + + +def test_private_field_user_password_raises(): + """Filtering on User.password (PrivateColumn) should raise ValueError.""" + with pytest.raises(ValueError, match="private field"): + QueryFilterBuilder.get_model_and_model_attr_from_attr_string("password", User) + + +def test_private_field_long_live_token_raises(): + """Filtering on LongLiveToken.token (PrivateColumn) should raise ValueError.""" + with pytest.raises(ValueError, match="private field"): + QueryFilterBuilder.get_model_and_model_attr_from_attr_string("token", LongLiveToken) + + +def test_non_private_field_does_not_raise(): + """Filtering on a normal field should not raise.""" + model, attr, _ = QueryFilterBuilder.get_model_and_model_attr_from_attr_string("full_name", User) + assert model is User + assert attr is User.full_name + + +# --------------------------------------------------------------------------- +# __filter_restricted__ traversal tests +# --------------------------------------------------------------------------- + + +def test_restricted_traversal_blocked_when_disallowed(): + """Traversing into User (restricted) via RecipeModel.user should raise when allow_restricted=False.""" + with pytest.raises(ValueError, match="restricted model"): + QueryFilterBuilder.get_model_and_model_attr_from_attr_string("user.email", RecipeModel, allow_restricted=False) + + +def test_restricted_traversal_allowed_by_default(): + """Traversing into User via RecipeModel.user should succeed when allow_restricted=True (default).""" + model, attr, _ = QueryFilterBuilder.get_model_and_model_attr_from_attr_string("user.email", RecipeModel) + assert model is User + assert attr is User.email + + +# --------------------------------------------------------------------------- +# ContextVar tests +# --------------------------------------------------------------------------- + + +def test_allow_filter_restricted_default_is_true(): + """The ContextVar default must be True so authenticated requests are unrestricted.""" + assert allow_filter_restricted.get() is True + + +def test_filter_query_respects_context_var_false(monkeypatch): + """filter_query should block restricted traversal when the ContextVar is False.""" + allow_filter_restricted.set(False) + try: + query = sa.select(RecipeModel) + builder = QueryFilterBuilder("user.email = 'test@example.com'") + with pytest.raises(ValueError, match="restricted model"): + builder.filter_query(query, RecipeModel) + finally: + allow_filter_restricted.set(True) + + +def test_filter_query_respects_context_var_true(): + """filter_query should allow restricted traversal when the ContextVar is True (default).""" + allow_filter_restricted.set(True) + query = sa.select(RecipeModel) + builder = QueryFilterBuilder("user.email = 'test@example.com'") + # Should not raise + builder.filter_query(query, RecipeModel)