You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

340 rivejä
9.7 KiB

  1. # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. import unittest
  4. from contextlib import contextmanager
  5. from datetime import timedelta
  6. from unittest.mock import patch
  7. import frappe
  8. from frappe.desk.doctype.note.note import Note
  9. from frappe.model.naming import make_autoname, parse_naming_series, revert_series_if_last
  10. from frappe.utils import cint, now_datetime
  11. class CustomTestNote(Note):
  12. @property
  13. def age(self):
  14. return now_datetime() - self.creation
  15. class TestDocument(unittest.TestCase):
  16. def test_get_return_empty_list_for_table_field_if_none(self):
  17. d = frappe.get_doc({"doctype":"User"})
  18. self.assertEqual(d.get("roles"), [])
  19. def test_load(self):
  20. d = frappe.get_doc("DocType", "User")
  21. self.assertEqual(d.doctype, "DocType")
  22. self.assertEqual(d.name, "User")
  23. self.assertEqual(d.allow_rename, 1)
  24. self.assertTrue(isinstance(d.fields, list))
  25. self.assertTrue(isinstance(d.permissions, list))
  26. self.assertTrue(filter(lambda d: d.fieldname=="email", d.fields))
  27. def test_load_single(self):
  28. d = frappe.get_doc("Website Settings", "Website Settings")
  29. self.assertEqual(d.name, "Website Settings")
  30. self.assertEqual(d.doctype, "Website Settings")
  31. self.assertTrue(d.disable_signup in (0, 1))
  32. def test_insert(self):
  33. d = frappe.get_doc({
  34. "doctype":"Event",
  35. "subject":"test-doc-test-event 1",
  36. "starts_on": "2014-01-01",
  37. "event_type": "Public"
  38. })
  39. d.insert()
  40. self.assertTrue(d.name.startswith("EV"))
  41. self.assertEqual(frappe.db.get_value("Event", d.name, "subject"),
  42. "test-doc-test-event 1")
  43. # test if default values are added
  44. self.assertEqual(d.send_reminder, 1)
  45. return d
  46. def test_insert_with_child(self):
  47. d = frappe.get_doc({
  48. "doctype":"Event",
  49. "subject":"test-doc-test-event 2",
  50. "starts_on": "2014-01-01",
  51. "event_type": "Public"
  52. })
  53. d.insert()
  54. self.assertTrue(d.name.startswith("EV"))
  55. self.assertEqual(frappe.db.get_value("Event", d.name, "subject"),
  56. "test-doc-test-event 2")
  57. def test_update(self):
  58. d = self.test_insert()
  59. d.subject = "subject changed"
  60. d.save()
  61. self.assertEqual(frappe.db.get_value(d.doctype, d.name, "subject"), "subject changed")
  62. def test_value_changed(self):
  63. d = self.test_insert()
  64. d.subject = "subject changed again"
  65. d.save()
  66. self.assertTrue(d.has_value_changed('subject'))
  67. self.assertFalse(d.has_value_changed('event_type'))
  68. def test_mandatory(self):
  69. # TODO: recheck if it is OK to force delete
  70. frappe.delete_doc_if_exists("User", "test_mandatory@example.com", 1)
  71. d = frappe.get_doc({
  72. "doctype": "User",
  73. "email": "test_mandatory@example.com",
  74. })
  75. self.assertRaises(frappe.MandatoryError, d.insert)
  76. d.set("first_name", "Test Mandatory")
  77. d.insert()
  78. self.assertEqual(frappe.db.get_value("User", d.name), d.name)
  79. def test_conflict_validation(self):
  80. d1 = self.test_insert()
  81. d2 = frappe.get_doc(d1.doctype, d1.name)
  82. d1.save()
  83. self.assertRaises(frappe.TimestampMismatchError, d2.save)
  84. def test_conflict_validation_single(self):
  85. d1 = frappe.get_doc("Website Settings", "Website Settings")
  86. d1.home_page = "test-web-page-1"
  87. d2 = frappe.get_doc("Website Settings", "Website Settings")
  88. d2.home_page = "test-web-page-1"
  89. d1.save()
  90. self.assertRaises(frappe.TimestampMismatchError, d2.save)
  91. def test_permission(self):
  92. frappe.set_user("Guest")
  93. self.assertRaises(frappe.PermissionError, self.test_insert)
  94. frappe.set_user("Administrator")
  95. def test_permission_single(self):
  96. frappe.set_user("Guest")
  97. d = frappe.get_doc("Website Settings", "Website Settings")
  98. self.assertRaises(frappe.PermissionError, d.save)
  99. frappe.set_user("Administrator")
  100. def test_link_validation(self):
  101. frappe.delete_doc_if_exists("User", "test_link_validation@example.com", 1)
  102. d = frappe.get_doc({
  103. "doctype": "User",
  104. "email": "test_link_validation@example.com",
  105. "first_name": "Link Validation",
  106. "roles": [
  107. {
  108. "role": "ABC"
  109. }
  110. ]
  111. })
  112. self.assertRaises(frappe.LinkValidationError, d.insert)
  113. d.roles = []
  114. d.append("roles", {
  115. "role": "System Manager"
  116. })
  117. d.insert()
  118. self.assertEqual(frappe.db.get_value("User", d.name), d.name)
  119. def test_validate(self):
  120. d = self.test_insert()
  121. d.starts_on = "2014-01-01"
  122. d.ends_on = "2013-01-01"
  123. self.assertRaises(frappe.ValidationError, d.validate)
  124. self.assertRaises(frappe.ValidationError, d.run_method, "validate")
  125. self.assertRaises(frappe.ValidationError, d.save)
  126. def test_update_after_submit(self):
  127. d = self.test_insert()
  128. d.starts_on = "2014-09-09"
  129. self.assertRaises(frappe.UpdateAfterSubmitError, d.validate_update_after_submit)
  130. d.meta.get_field("starts_on").allow_on_submit = 1
  131. d.validate_update_after_submit()
  132. d.meta.get_field("starts_on").allow_on_submit = 0
  133. # when comparing date(2014, 1, 1) and "2014-01-01"
  134. d.reload()
  135. d.starts_on = "2014-01-01"
  136. d.validate_update_after_submit()
  137. def test_varchar_length(self):
  138. d = self.test_insert()
  139. d.sender = "abcde"*100 + "@user.com"
  140. self.assertRaises(frappe.CharacterLengthExceededError, d.save)
  141. def test_xss_filter(self):
  142. d = self.test_insert()
  143. # script
  144. xss = '<script>alert("XSS")</script>'
  145. escaped_xss = xss.replace('<', '&lt;').replace('>', '&gt;')
  146. d.subject += xss
  147. d.save()
  148. d.reload()
  149. self.assertTrue(xss not in d.subject)
  150. self.assertTrue(escaped_xss in d.subject)
  151. # onload
  152. xss = '<div onload="alert("XSS")">Test</div>'
  153. escaped_xss = '<div>Test</div>'
  154. d.subject += xss
  155. d.save()
  156. d.reload()
  157. self.assertTrue(xss not in d.subject)
  158. self.assertTrue(escaped_xss in d.subject)
  159. # css attributes
  160. xss = '<div style="something: doesn\'t work; color: red;">Test</div>'
  161. escaped_xss = '<div style="">Test</div>'
  162. d.subject += xss
  163. d.save()
  164. d.reload()
  165. self.assertTrue(xss not in d.subject)
  166. self.assertTrue(escaped_xss in d.subject)
  167. def test_naming_series(self):
  168. data = ["TEST-", "TEST/17-18/.test_data./.####", "TEST.YYYY.MM.####"]
  169. for series in data:
  170. name = make_autoname(series)
  171. prefix = series
  172. if ".#" in series:
  173. prefix = series.rsplit('.',1)[0]
  174. prefix = parse_naming_series(prefix)
  175. old_current = frappe.db.get_value('Series', prefix, "current", order_by="name")
  176. revert_series_if_last(series, name)
  177. new_current = cint(frappe.db.get_value('Series', prefix, "current", order_by="name"))
  178. self.assertEqual(cint(old_current) - 1, new_current)
  179. def test_non_negative_check(self):
  180. frappe.delete_doc_if_exists("Currency", "Frappe Coin", 1)
  181. d = frappe.get_doc({
  182. 'doctype': 'Currency',
  183. 'currency_name': 'Frappe Coin',
  184. 'smallest_currency_fraction_value': -1
  185. })
  186. self.assertRaises(frappe.NonNegativeError, d.insert)
  187. d.set('smallest_currency_fraction_value', 1)
  188. d.insert()
  189. self.assertEqual(frappe.db.get_value("Currency", d.name), d.name)
  190. frappe.delete_doc_if_exists("Currency", "Frappe Coin", 1)
  191. def test_get_formatted(self):
  192. frappe.get_doc({
  193. 'doctype': 'DocType',
  194. 'name': 'Test Formatted',
  195. 'module': 'Custom',
  196. 'custom': 1,
  197. 'fields': [
  198. {'label': 'Currency', 'fieldname': 'currency', 'reqd': 1, 'fieldtype': 'Currency'},
  199. ]
  200. }).insert()
  201. frappe.delete_doc_if_exists("Currency", "INR", 1)
  202. d = frappe.get_doc({
  203. 'doctype': 'Currency',
  204. 'currency_name': 'INR',
  205. 'symbol': '₹',
  206. }).insert()
  207. d = frappe.get_doc({
  208. 'doctype': 'Test Formatted',
  209. 'currency': 100000
  210. })
  211. self.assertEquals(d.get_formatted('currency', currency='INR', format="#,###.##"), '₹ 100,000.00')
  212. def test_limit_for_get(self):
  213. doc = frappe.get_doc("DocType", "DocType")
  214. # assuming DocType has more than 3 Data fields
  215. self.assertEquals(len(doc.get("fields", limit=3)), 3)
  216. # limit with filters
  217. self.assertEquals(len(doc.get("fields", filters={"fieldtype": "Data"}, limit=3)), 3)
  218. def test_virtual_fields(self):
  219. """Virtual fields are accessible via API and Form views, whenever .as_dict is invoked
  220. """
  221. frappe.db.delete("Custom Field", {"dt": "Note", "fieldname":"age"})
  222. note = frappe.new_doc("Note")
  223. note.content = "some content"
  224. note.title = frappe.generate_hash(length=20)
  225. note.insert()
  226. def patch_note():
  227. return patch("frappe.controllers", new={frappe.local.site: {'Note': CustomTestNote}})
  228. @contextmanager
  229. def customize_note(with_options=False):
  230. options = "frappe.utils.now_datetime() - doc.creation" if with_options else ""
  231. custom_field = frappe.get_doc({
  232. "doctype": "Custom Field",
  233. "dt": "Note",
  234. "fieldname": "age",
  235. "fieldtype": "Data",
  236. "read_only": True,
  237. "is_virtual": True,
  238. "options": options,
  239. })
  240. try:
  241. yield custom_field.insert(ignore_if_duplicate=True)
  242. finally:
  243. custom_field.delete()
  244. with patch_note():
  245. doc = frappe.get_last_doc("Note")
  246. self.assertIsInstance(doc, CustomTestNote)
  247. self.assertIsInstance(doc.age, timedelta)
  248. self.assertIsNone(doc.as_dict().get("age"))
  249. self.assertIsNone(doc.get_valid_dict().get("age"))
  250. with customize_note(), patch_note():
  251. doc = frappe.get_last_doc("Note")
  252. self.assertIsInstance(doc, CustomTestNote)
  253. self.assertIsInstance(doc.age, timedelta)
  254. self.assertIsInstance(doc.as_dict().get("age"), timedelta)
  255. self.assertIsInstance(doc.get_valid_dict().get("age"), timedelta)
  256. with customize_note(with_options=True):
  257. doc = frappe.get_last_doc("Note")
  258. self.assertIsInstance(doc, Note)
  259. self.assertIsInstance(doc.as_dict().get("age"), timedelta)
  260. self.assertIsInstance(doc.get_valid_dict().get("age"), timedelta)
  261. def test_run_method(self):
  262. doc = frappe.get_last_doc("User")
  263. # Case 1: Override with a string
  264. doc.as_dict = ""
  265. # run_method should throw TypeError
  266. self.assertRaisesRegex(TypeError, "not callable", doc.run_method, "as_dict")
  267. # Case 2: Override with a function
  268. def my_as_dict(*args, **kwargs):
  269. return "success"
  270. doc.as_dict = my_as_dict
  271. # run_method should get overridden
  272. self.assertEqual(doc.run_method("as_dict"), "success")