From dfadf42d877e70c45746db45b0c43d822a1a933d Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 12 Apr 2022 19:05:06 +0530 Subject: [PATCH] test: Added tests for nestedset --- frappe/tests/test_nestedset.py | 235 +++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 frappe/tests/test_nestedset.py diff --git a/frappe/tests/test_nestedset.py b/frappe/tests/test_nestedset.py new file mode 100644 index 0000000000..2d601ec185 --- /dev/null +++ b/frappe/tests/test_nestedset.py @@ -0,0 +1,235 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE + +import frappe +from frappe.core.doctype.doctype.test_doctype import new_doctype +from frappe.query_builder import Field +from frappe.query_builder.functions import Max +from frappe.tests.utils import FrappeTestCase +from frappe.utils.nestedset import ( + NestedSetChildExistsError, + NestedSetInvalidMergeError, + NestedSetRecursionError, + get_descendants_of, + rebuild_tree, +) + +records = [ + { + "some_fieldname": "Root Node", + "parent_test_tree_doctype": None, + "is_group": 1, + }, + { + "some_fieldname": "Parent 1", + "parent_test_tree_doctype": "Root Node", + "is_group": 1, + }, + { + "some_fieldname": "Parent 2", + "parent_test_tree_doctype": "Root Node", + "is_group": 1, + }, + { + "some_fieldname": "Child 1", + "parent_test_tree_doctype": "Parent 1", + "is_group": 0, + }, + { + "some_fieldname": "Child 2", + "parent_test_tree_doctype": "Parent 1", + "is_group": 0, + }, + { + "some_fieldname": "Child 3", + "parent_test_tree_doctype": "Parent 2", + "is_group": 0, + }, +] + + +class NestedSetTestUtil: + def setup_test_doctype(self): + frappe.db.sql("delete from `tabDocType` where `name` = 'Test Tree DocType'") + frappe.db.sql_ddl("drop table if exists `tabTest Tree DocType`") + + self.tree_doctype = new_doctype( + "Test Tree DocType", is_tree=True, autoname="field:some_fieldname" + ) + self.tree_doctype.insert() + + for record in records: + d = frappe.new_doc("Test Tree DocType") + d.update(record) + d.insert() + + def teardown_test_doctype(self): + self.tree_doctype.delete() + frappe.db.sql_ddl("drop table if exists `tabTest Tree DocType`") + + def move_it_back(self): + parent_1 = frappe.get_doc("Test Tree DocType", "Parent 1") + parent_1.parent_test_tree_doctype = "Root Node" + parent_1.save() + + def get_no_of_children(self, record_name: str) -> int: + if not record_name: + return frappe.db.count("Test Tree DocType") + return len(get_descendants_of("Test Tree DocType", record_name, ignore_permissions=True)) + + +class TestNestedSet(FrappeTestCase): + @classmethod + def setUpClass(cls) -> None: + cls.nsu = NestedSetTestUtil() + cls.nsu.setup_test_doctype() + super().setUpClass() + + @classmethod + def tearDownClass(cls) -> None: + cls.nsu.teardown_test_doctype() + super().tearDownClass() + + def setUp(self) -> None: + frappe.db.rollback() + + def test_basic_tree(self): + global records + + min_lft = 1 + max_rgt = frappe.qb.from_("Test Tree DocType").select(Max(Field("rgt"))).run(pluck=True)[0] + + for record in records: + lft, rgt, parent_test_tree_doctype = frappe.db.get_value( + "Test Tree DocType", + record["some_fieldname"], + ["lft", "rgt", "parent_test_tree_doctype"], + ) + + if parent_test_tree_doctype: + parent_lft, parent_rgt = frappe.db.get_value( + "Test Tree DocType", parent_test_tree_doctype, ["lft", "rgt"] + ) + else: + # root + parent_lft = min_lft - 1 + parent_rgt = max_rgt + 1 + + self.assertTrue(lft) + self.assertTrue(rgt) + self.assertTrue(lft < rgt) + self.assertTrue(parent_lft < parent_rgt) + self.assertTrue(lft > parent_lft) + self.assertTrue(rgt < parent_rgt) + self.assertTrue(lft >= min_lft) + self.assertTrue(rgt <= max_rgt) + + no_of_children = self.nsu.get_no_of_children(record["some_fieldname"]) + self.assertTrue( + rgt == (lft + 1 + (2 * no_of_children)), + msg=(record, no_of_children, self.nsu.get_no_of_children(record["some_fieldname"])), + ) + + no_of_children = self.nsu.get_no_of_children(parent_test_tree_doctype) + self.assertTrue(parent_rgt == (parent_lft + 1 + (2 * no_of_children))) + + def test_recursion(self): + leaf_node = frappe.get_doc("Test Tree DocType", {"some_fieldname": "Parent 2"}) + leaf_node.parent_test_tree_doctype = "Child 3" + self.assertRaises(NestedSetRecursionError, leaf_node.save) + leaf_node.reload() + + def test_rebuild_tree(self): + rebuild_tree("Test Tree DocType", "parent_test_tree_doctype") + self.test_basic_tree() + + def test_move_group_into_another(self): + old_lft, old_rgt = frappe.db.get_value("Test Tree DocType", "Parent 2", ["lft", "rgt"]) + + parent_1 = frappe.get_doc("Test Tree DocType", "Parent 1") + lft, rgt = parent_1.lft, parent_1.rgt + + parent_1.parent_test_tree_doctype = "Parent 2" + parent_1.save() + self.test_basic_tree() + + # after move + new_lft, new_rgt = frappe.db.get_value("Test Tree DocType", "Parent 2", ["lft", "rgt"]) + + # lft should reduce + self.assertEqual(old_lft - new_lft, rgt - lft + 1) + + # adjacent siblings, hence rgt diff will be 0 + self.assertEqual(new_rgt - old_rgt, 0) + + self.nsu.move_it_back() + self.test_basic_tree() + + def test_move_leaf_into_another_group(self): + child_2 = frappe.get_doc("Test Tree DocType", "Child 2") + + # assert that child 2 is not already under parent 1 + parent_lft_old, parent_rgt_old = frappe.db.get_value( + "Test Tree DocType", "Parent 2", ["lft", "rgt"] + ) + self.assertTrue((parent_lft_old > child_2.lft) and (parent_rgt_old > child_2.rgt)) + + child_2.parent_test_tree_doctype = "Parent 2" + child_2.save() + self.test_basic_tree() + + # assert that child 2 is under parent 1 + parent_lft_new, parent_rgt_new = frappe.db.get_value( + "Test Tree DocType", "Parent 2", ["lft", "rgt"] + ) + self.assertFalse((parent_lft_new > child_2.lft) and (parent_rgt_new > child_2.rgt)) + + def test_delete_leaf(self): + global records + el = {"some_fieldname": "Child 1", "parent_test_tree_doctype": "Parent 1", "is_group": 0} + + child_1 = frappe.get_doc("Test Tree DocType", "Child 1") + child_1.delete() + records.remove(el) + + self.test_basic_tree() + + n = frappe.new_doc("Test Tree DocType") + n.update(el) + n.insert() + records.append(el) + + self.test_basic_tree() + + def test_delete_group(self): + # cannot delete group with child, but can delete leaf + with self.assertRaises(NestedSetChildExistsError): + frappe.delete_doc("Test Tree DocType", "Parent 1") + + def test_merge_groups(self): + global records + el = {"some_fieldname": "Parent 2", "parent_test_tree_doctype": "Root Node", "is_group": 1} + frappe.rename_doc("Test Tree DocType", "Parent 2", "Parent 1", merge=True) + records.remove(el) + self.test_basic_tree() + + def test_merge_leaves(self): + global records + el = {"some_fieldname": "Child 3", "parent_test_tree_doctype": "Parent 2", "is_group": 0} + + frappe.rename_doc( + "Test Tree DocType", + "Child 3", + "Child 2", + merge=True, + ) + records.remove(el) + self.test_basic_tree() + + def test_merge_leaf_into_group(self): + with self.assertRaises(NestedSetInvalidMergeError): + frappe.rename_doc("Test Tree DocType", "Child 1", "Parent 1", merge=True) + + def test_merge_group_into_leaf(self): + with self.assertRaises(NestedSetInvalidMergeError): + frappe.rename_doc("Test Tree DocType", "Parent 1", "Child 1", merge=True)