PageRenderTime 27ms CodeModel.GetById 38ms RepoModel.GetById 0ms app.codeStats 0ms

/frappe/tests/test_nestedset.py

https://github.com/frappe/frappe
Python | 235 lines | 182 code | 44 blank | 9 comment | 10 complexity | 7ad58d81bc7e5447730a42ecf48d3e31 MD5 | raw file
  1. # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. import frappe
  4. from frappe.core.doctype.doctype.test_doctype import new_doctype
  5. from frappe.query_builder import Field
  6. from frappe.query_builder.functions import Max
  7. from frappe.tests.utils import FrappeTestCase
  8. from frappe.utils.nestedset import (
  9. NestedSetChildExistsError,
  10. NestedSetInvalidMergeError,
  11. NestedSetRecursionError,
  12. get_descendants_of,
  13. rebuild_tree,
  14. )
  15. records = [
  16. {
  17. "some_fieldname": "Root Node",
  18. "parent_test_tree_doctype": None,
  19. "is_group": 1,
  20. },
  21. {
  22. "some_fieldname": "Parent 1",
  23. "parent_test_tree_doctype": "Root Node",
  24. "is_group": 1,
  25. },
  26. {
  27. "some_fieldname": "Parent 2",
  28. "parent_test_tree_doctype": "Root Node",
  29. "is_group": 1,
  30. },
  31. {
  32. "some_fieldname": "Child 1",
  33. "parent_test_tree_doctype": "Parent 1",
  34. "is_group": 0,
  35. },
  36. {
  37. "some_fieldname": "Child 2",
  38. "parent_test_tree_doctype": "Parent 1",
  39. "is_group": 0,
  40. },
  41. {
  42. "some_fieldname": "Child 3",
  43. "parent_test_tree_doctype": "Parent 2",
  44. "is_group": 0,
  45. },
  46. ]
  47. class NestedSetTestUtil:
  48. def setup_test_doctype(self):
  49. frappe.db.sql("delete from `tabDocType` where `name` = 'Test Tree DocType'")
  50. frappe.db.sql_ddl("drop table if exists `tabTest Tree DocType`")
  51. self.tree_doctype = new_doctype(
  52. "Test Tree DocType", is_tree=True, autoname="field:some_fieldname"
  53. )
  54. self.tree_doctype.insert()
  55. for record in records:
  56. d = frappe.new_doc("Test Tree DocType")
  57. d.update(record)
  58. d.insert()
  59. def teardown_test_doctype(self):
  60. self.tree_doctype.delete()
  61. frappe.db.sql_ddl("drop table if exists `tabTest Tree DocType`")
  62. def move_it_back(self):
  63. parent_1 = frappe.get_doc("Test Tree DocType", "Parent 1")
  64. parent_1.parent_test_tree_doctype = "Root Node"
  65. parent_1.save()
  66. def get_no_of_children(self, record_name: str) -> int:
  67. if not record_name:
  68. return frappe.db.count("Test Tree DocType")
  69. return len(get_descendants_of("Test Tree DocType", record_name, ignore_permissions=True))
  70. class TestNestedSet(FrappeTestCase):
  71. @classmethod
  72. def setUpClass(cls) -> None:
  73. cls.nsu = NestedSetTestUtil()
  74. cls.nsu.setup_test_doctype()
  75. super().setUpClass()
  76. @classmethod
  77. def tearDownClass(cls) -> None:
  78. cls.nsu.teardown_test_doctype()
  79. super().tearDownClass()
  80. def setUp(self) -> None:
  81. frappe.db.rollback()
  82. def test_basic_tree(self):
  83. global records
  84. min_lft = 1
  85. max_rgt = frappe.qb.from_("Test Tree DocType").select(Max(Field("rgt"))).run(pluck=True)[0]
  86. for record in records:
  87. lft, rgt, parent_test_tree_doctype = frappe.db.get_value(
  88. "Test Tree DocType",
  89. record["some_fieldname"],
  90. ["lft", "rgt", "parent_test_tree_doctype"],
  91. )
  92. if parent_test_tree_doctype:
  93. parent_lft, parent_rgt = frappe.db.get_value(
  94. "Test Tree DocType", parent_test_tree_doctype, ["lft", "rgt"]
  95. )
  96. else:
  97. # root
  98. parent_lft = min_lft - 1
  99. parent_rgt = max_rgt + 1
  100. self.assertTrue(lft)
  101. self.assertTrue(rgt)
  102. self.assertTrue(lft < rgt)
  103. self.assertTrue(parent_lft < parent_rgt)
  104. self.assertTrue(lft > parent_lft)
  105. self.assertTrue(rgt < parent_rgt)
  106. self.assertTrue(lft >= min_lft)
  107. self.assertTrue(rgt <= max_rgt)
  108. no_of_children = self.nsu.get_no_of_children(record["some_fieldname"])
  109. self.assertTrue(
  110. rgt == (lft + 1 + (2 * no_of_children)),
  111. msg=(record, no_of_children, self.nsu.get_no_of_children(record["some_fieldname"])),
  112. )
  113. no_of_children = self.nsu.get_no_of_children(parent_test_tree_doctype)
  114. self.assertTrue(parent_rgt == (parent_lft + 1 + (2 * no_of_children)))
  115. def test_recursion(self):
  116. leaf_node = frappe.get_doc("Test Tree DocType", {"some_fieldname": "Parent 2"})
  117. leaf_node.parent_test_tree_doctype = "Child 3"
  118. self.assertRaises(NestedSetRecursionError, leaf_node.save)
  119. leaf_node.reload()
  120. def test_rebuild_tree(self):
  121. rebuild_tree("Test Tree DocType", "parent_test_tree_doctype")
  122. self.test_basic_tree()
  123. def test_move_group_into_another(self):
  124. old_lft, old_rgt = frappe.db.get_value("Test Tree DocType", "Parent 2", ["lft", "rgt"])
  125. parent_1 = frappe.get_doc("Test Tree DocType", "Parent 1")
  126. lft, rgt = parent_1.lft, parent_1.rgt
  127. parent_1.parent_test_tree_doctype = "Parent 2"
  128. parent_1.save()
  129. self.test_basic_tree()
  130. # after move
  131. new_lft, new_rgt = frappe.db.get_value("Test Tree DocType", "Parent 2", ["lft", "rgt"])
  132. # lft should reduce
  133. self.assertEqual(old_lft - new_lft, rgt - lft + 1)
  134. # adjacent siblings, hence rgt diff will be 0
  135. self.assertEqual(new_rgt - old_rgt, 0)
  136. self.nsu.move_it_back()
  137. self.test_basic_tree()
  138. def test_move_leaf_into_another_group(self):
  139. child_2 = frappe.get_doc("Test Tree DocType", "Child 2")
  140. # assert that child 2 is not already under parent 1
  141. parent_lft_old, parent_rgt_old = frappe.db.get_value(
  142. "Test Tree DocType", "Parent 2", ["lft", "rgt"]
  143. )
  144. self.assertTrue((parent_lft_old > child_2.lft) and (parent_rgt_old > child_2.rgt))
  145. child_2.parent_test_tree_doctype = "Parent 2"
  146. child_2.save()
  147. self.test_basic_tree()
  148. # assert that child 2 is under parent 1
  149. parent_lft_new, parent_rgt_new = frappe.db.get_value(
  150. "Test Tree DocType", "Parent 2", ["lft", "rgt"]
  151. )
  152. self.assertFalse((parent_lft_new > child_2.lft) and (parent_rgt_new > child_2.rgt))
  153. def test_delete_leaf(self):
  154. global records
  155. el = {"some_fieldname": "Child 1", "parent_test_tree_doctype": "Parent 1", "is_group": 0}
  156. child_1 = frappe.get_doc("Test Tree DocType", "Child 1")
  157. child_1.delete()
  158. records.remove(el)
  159. self.test_basic_tree()
  160. n = frappe.new_doc("Test Tree DocType")
  161. n.update(el)
  162. n.insert()
  163. records.append(el)
  164. self.test_basic_tree()
  165. def test_delete_group(self):
  166. # cannot delete group with child, but can delete leaf
  167. with self.assertRaises(NestedSetChildExistsError):
  168. frappe.delete_doc("Test Tree DocType", "Parent 1")
  169. def test_merge_groups(self):
  170. global records
  171. el = {"some_fieldname": "Parent 2", "parent_test_tree_doctype": "Root Node", "is_group": 1}
  172. frappe.rename_doc("Test Tree DocType", "Parent 2", "Parent 1", merge=True)
  173. records.remove(el)
  174. self.test_basic_tree()
  175. def test_merge_leaves(self):
  176. global records
  177. el = {"some_fieldname": "Child 3", "parent_test_tree_doctype": "Parent 2", "is_group": 0}
  178. frappe.rename_doc(
  179. "Test Tree DocType",
  180. "Child 3",
  181. "Child 2",
  182. merge=True,
  183. )
  184. records.remove(el)
  185. self.test_basic_tree()
  186. def test_merge_leaf_into_group(self):
  187. with self.assertRaises(NestedSetInvalidMergeError):
  188. frappe.rename_doc("Test Tree DocType", "Child 1", "Parent 1", merge=True)
  189. def test_merge_group_into_leaf(self):
  190. with self.assertRaises(NestedSetInvalidMergeError):
  191. frappe.rename_doc("Test Tree DocType", "Parent 1", "Child 1", merge=True)