seitime-frappe/frappe/tests/test_base_document.py
2025-09-08 17:02:34 +05:30

143 lines
5.2 KiB
Python

import frappe
from frappe.desk.doctype.todo.todo import ToDo
from frappe.model.base_document import BaseDocument, get_extended_class
from frappe.tests import IntegrationTestCase
class TestExtensionA(BaseDocument):
def extension_method_a(self):
return "method_a"
class TestExtensionB(BaseDocument):
def extension_method_b(self):
return "method_b"
class TestToDoExtension(BaseDocument):
"""Extension class that overrides ToDo's validate method"""
def validate(self):
# Add our custom logic
self.custom_validation_called = True
def extension_method(self):
return "extension_method_called"
class TestBaseDocument(IntegrationTestCase):
def test_docstatus(self):
doc = BaseDocument({"docstatus": 0, "doctype": "ToDo"})
self.assertTrue(doc.docstatus.is_draft())
self.assertEqual(doc.docstatus, 0)
doc.docstatus = 1
self.assertTrue(doc.docstatus.is_submitted())
self.assertEqual(doc.docstatus, 1)
doc.docstatus = 2
self.assertTrue(doc.docstatus.is_cancelled())
self.assertEqual(doc.docstatus, 2)
def test_get_extended_class_with_no_extensions(self):
"""Test that get_extended_class returns the base class when no extensions are provided."""
with self.patch_hooks({"extend_doctype_class": {}}):
result = get_extended_class(ToDo, "ToDo")
self.assertEqual(result, ToDo)
with self.patch_hooks({"extend_doctype_class": {"ToDo": []}}):
result = get_extended_class(ToDo, "ToDo")
self.assertEqual(result, ToDo)
def test_get_extended_class_with_extensions(self):
"""Test that get_extended_class properly combines extension classes with base class."""
# Mock frappe.get_hooks to return extension paths
extensions = [
"frappe.tests.test_base_document.TestExtensionA",
"frappe.tests.test_base_document.TestExtensionB",
]
with self.patch_hooks({"extend_doctype_class": {"ToDo": extensions}}):
extended_class = get_extended_class(ToDo, "ToDo")
# Test that the extended class is different from base class
self.assertNotEqual(extended_class, ToDo)
# Test that the extended class has all methods from extensions and base
instance = extended_class({"doctype": "ToDo"})
self.assertTrue(hasattr(instance, "extension_method_a"))
self.assertTrue(hasattr(instance, "extension_method_b"))
# Test that methods work correctly
self.assertEqual(instance.extension_method_a(), "method_a")
self.assertEqual(instance.extension_method_b(), "method_b")
# Test MRO (Method Resolution Order) - extensions should come first in reverse order
mro_classes = [cls.__name__ for cls in extended_class.__mro__]
self.assertIn("TestExtensionB", mro_classes)
self.assertIn("TestExtensionA", mro_classes)
self.assertIn("ToDo", mro_classes)
# TestExtensionB should come before TestExtensionA (reverse order)
idx_b = mro_classes.index("TestExtensionB")
idx_a = mro_classes.index("TestExtensionA")
idx_base = mro_classes.index("ToDo")
self.assertLess(idx_b, idx_a)
self.assertLess(idx_a, idx_base)
def test_extension_overrides_todo_method(self):
"""Test that an extension can override methods from the actual ToDo class"""
from frappe.desk.doctype.todo.todo import ToDo
# Mock the hooks to include our ToDo extension
extensions = ["frappe.tests.test_base_document.TestToDoExtension"]
with self.patch_hooks({"extend_doctype_class": {"ToDo": extensions}}):
extended_class = get_extended_class(ToDo, "ToDo")
# Test that the extended class is different from base ToDo
self.assertNotEqual(extended_class, ToDo)
# Create an instance of the extended ToDo
instance = extended_class({"doctype": "ToDo"})
# Test that extension method is available
self.assertTrue(hasattr(instance, "extension_method"))
self.assertEqual(instance.extension_method(), "extension_method_called")
# Test that the validate method is overridden
# The extension's validate method should set custom_validation_called = True
instance.validate()
self.assertTrue(getattr(instance, "custom_validation_called", False))
# Test MRO - extension should come before ToDo class
mro_classes = [cls.__name__ for cls in extended_class.__mro__]
self.assertIn("TestToDoExtension", mro_classes)
self.assertIn("ToDo", mro_classes)
# TestToDoExtension should come before ToDo
idx_extension = mro_classes.index("TestToDoExtension")
idx_todo = mro_classes.index("ToDo")
self.assertLess(idx_extension, idx_todo)
def test_extension_invalid_path_raises_exception(self):
"""Test that an invalid extension path raises an appropriate exception"""
from frappe.desk.doctype.todo.todo import ToDo
# Mock the hooks to include an invalid extension path
path_to_invalid_extension = "invalid.module.path.NonExistentClass"
extensions = [
"frappe.tests.test_base_document.TestExtensionA", # valid
path_to_invalid_extension, # invalid
]
with self.patch_hooks({"extend_doctype_class": {"ToDo": extensions}}):
# Test that frappe.ValidationError is raised for invalid extension path
with self.assertRaises(frappe.ValidationError) as context:
get_extended_class(ToDo, "ToDo")
# Check that the error message mentions the invalid path
error_message = str(context.exception)
self.assertIn(path_to_invalid_extension, error_message)