diff --git a/frappe/tests/test_query.py b/frappe/tests/test_query.py index e20f50921f..00fb90f139 100644 --- a/frappe/tests/test_query.py +++ b/frappe/tests/test_query.py @@ -5,9 +5,17 @@ from frappe.core.doctype.doctype.test_doctype import new_doctype from frappe.query_builder import Field from frappe.query_builder.functions import Abs, Count, Ifnull, Max, Now, Timestamp from frappe.tests import IntegrationTestCase +from frappe.tests.test_db_query import ( + create_nested_doctype, + create_nested_doctype_records, + setup_patched_blog_post, + setup_test_user, +) from frappe.tests.test_query_builder import db_type_is, run_only_if from frappe.utils.nestedset import get_ancestors_of, get_descendants_of +EXTRA_TEST_RECORD_DEPENDENCIES = ["User", "Blog Post", "Blog Category", "Blogger"] + def create_tree_docs(): records = [ @@ -252,7 +260,7 @@ class TestQuery(IntegrationTestCase): ) self.assertRaisesRegex( - frappe.ValidationError, + frappe.PermissionError, "Invalid filter", lambda: frappe.qb.get_query( "DocType", @@ -455,3 +463,135 @@ class TestQuery(IntegrationTestCase): note1.delete() note2.delete() + + def test_build_match_conditions(self): + from frappe.permissions import add_user_permission, clear_user_permissions_for_doctype + + clear_user_permissions_for_doctype("Blog Post", "test2@example.com") + + test2user = frappe.get_doc("User", "test2@example.com") + test2user.add_roles("Blogger") + frappe.set_user("test2@example.com") + + # Before any user permission is applied, there should be no conditions + query = frappe.qb.get_query("Blog Post", ignore_permissions=False) + self.assertNotIn("(`tabBlog Post`.`name` in (", str(query)) + + # Add user permissions + add_user_permission("Blog Post", "-test-blog-post", "test2@example.com", True) + add_user_permission("Blog Post", "-test-blog-post-1", "test2@example.com", True) + + # After applying user permission, condition should be in query + query = str(frappe.qb.get_query("Blog Post", ignore_permissions=False)) + + # Check for user permission condition in the query string + if frappe.db.db_type == "mariadb": + self.assertIn("`name` IS NULL OR `name` IN ('-test-blog-post-1','-test-blog-post')", query) + elif frappe.db.db_type == "postgres": + self.assertIn("\"name\" IS NULL OR \"name\" IN ('-test-blog-post-1','-test-blog-post')", query) + + frappe.set_user("Administrator") + clear_user_permissions_for_doctype("Blog Post", "test2@example.com") + test2user.remove_roles("Blogger") + + def test_ignore_permissions_for_query(self): + frappe.set_user("test2@example.com") + + with self.assertRaises(frappe.PermissionError): + frappe.qb.get_query("DocType", filters={"istable": 1}, ignore_permissions=False) + + result = frappe.qb.get_query("DocType", filters={"istable": 1}, ignore_permissions=True).run() + self.assertTrue(len(result) > 0) + + frappe.set_user("Administrator") + + def test_permlevel_fields(self): + """Test permission level check when querying fields""" + with setup_patched_blog_post(), setup_test_user(set_user=True): + # Create a test blog post + test_post = frappe.get_doc( + { + "doctype": "Blog Post", + "title": "Test Permission Post", + "content": "Test Content", + "published": 1, + } + ).insert(ignore_permissions=True) + + # Without proper permission, published field should be filtered out + data = frappe.qb.get_query( + "Blog Post", + filters={"name": test_post.name}, + fields=["name", "published", "title"], + ignore_permissions=False, + ).run(as_dict=1) + + field_list = [field for d in data for field in d.keys()] + self.assertIn("title", field_list) + self.assertIn("name", field_list) + self.assertNotIn("published", field_list) + + # With Administrator, all fields should be accessible + frappe.set_user("Administrator") + data = frappe.qb.get_query( + "Blog Post", + filters={"name": test_post.name}, + fields=["name", "published", "title"], + ignore_permissions=False, + ).run(as_dict=1) + + field_list = [field for d in data for field in d.keys()] + self.assertIn("published", field_list) + + test_post.delete() + + def test_nested_permission(self): + """Test permission on nested doctypes""" + frappe.set_user("Administrator") + create_nested_doctype() + create_nested_doctype_records() + + from frappe.permissions import add_user_permission, clear_user_permissions_for_doctype + + clear_user_permissions_for_doctype("Nested DocType") + + # Add user permission for only one root folder + add_user_permission("Nested DocType", "Level 1 A", "test2@example.com") + + from frappe.core.page.permission_manager.permission_manager import update + + # To avoid if_owner filter + update("Nested DocType", "All", 0, "if_owner", 0) + + test2user = frappe.get_doc("User", "test2@example.com") + test2user.add_roles("Blogger") + frappe.set_user("test2@example.com") + data = frappe.qb.get_query("Nested DocType", ignore_permissions=False).run(as_dict=1) + + # Children of the permitted node should be accessible + self.assertTrue(any(d.name == "Level 2 A" for d in data)) + + # Other nodes should not be accessible + self.assertFalse(any(d.name == "Level 1 B" for d in data)) + self.assertFalse(any(d.name == "Level 2 B" for d in data)) + + update("Nested DocType", "All", 0, "if_owner", 1) # Reset to default + frappe.set_user("Administrator") + + def test_is_set_is_not_set(self): + """Test is set and is not set filters""" + result = frappe.qb.get_query("DocType", filters={"autoname": ["is", "not set"]}).run(as_dict=1) + self.assertTrue({"name": "Integration Request"} in result) + self.assertTrue({"name": "User"} in result) + self.assertFalse({"name": "Blogger"} in result) + + result = frappe.qb.get_query("DocType", filters={"autoname": ["is", "set"]}).run(as_dict=1) + self.assertTrue({"name": "DocField"} in result) + self.assertTrue({"name": "Prepared Report"} in result) + self.assertFalse({"name": "Property Setter"} in result) + + # Test with updating value to NULL + frappe.db.set_value("DocType", "Property Setter", "autoname", None, update_modified=False) + + result = frappe.qb.get_query("DocType", filters={"autoname": ["is", "set"]}).run(as_dict=1) + self.assertFalse(any(d.name == "Property Setter" for d in result))