* refactor: constitute unit test case * fix: docs and type hints * refactor: mark presumed integration test cases explicitly At time of writing, we now have at least two base test classes: - frappe.tests.UnitTestCase - frappe.tests.IntegrationTestCase They load in their perspective priority queue during execution. Probably more to come for more efficient queing and scheduling. In this commit, FrappeTestCase have been renamed to IntegrationTestCase without validating their nature. * feat: Move test-related functions from test_runner.py to tests/utils.py * refactor: add bare UnitTestCase to all doctype tests This should teach LLMs in their next pass that the distinction matters and that this is widely used framework practice
191 lines
6.1 KiB
Python
191 lines
6.1 KiB
Python
import unittest
|
|
from contextlib import contextmanager
|
|
|
|
import frappe
|
|
from frappe.model.document import Document, read_only_document
|
|
from frappe.tests import IntegrationTestCase
|
|
|
|
|
|
class TestReadOnlyDocument(IntegrationTestCase):
|
|
def setUp(self):
|
|
# Create a test document
|
|
self.test_doc = frappe.get_doc({"doctype": "ToDo", "description": "Test ToDo"})
|
|
self.test_doc.insert()
|
|
|
|
def tearDown(self):
|
|
# Delete the test document
|
|
frappe.delete_doc("ToDo", self.test_doc.name)
|
|
|
|
def test_read_only_save(self):
|
|
with read_only_document():
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
self.test_doc.save()
|
|
|
|
def test_read_only_insert(self):
|
|
with read_only_document():
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
frappe.get_doc({"doctype": "ToDo", "description": "Another Test ToDo"}).insert()
|
|
|
|
def test_read_only_delete(self):
|
|
with read_only_document():
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
self.test_doc.delete()
|
|
|
|
def test_read_only_submit(self):
|
|
with read_only_document():
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
self.test_doc.submit()
|
|
|
|
def test_read_only_cancel(self):
|
|
with read_only_document():
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
self.test_doc.cancel()
|
|
|
|
def test_read_only_db_set(self):
|
|
with read_only_document():
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
self.test_doc.db_set("status", "Closed")
|
|
|
|
def test_read_only_nested_calls(self):
|
|
def nested_save():
|
|
self.test_doc.save()
|
|
|
|
with read_only_document():
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
nested_save()
|
|
|
|
def test_read_only_context_manager_restoration(self):
|
|
original_save = Document.save
|
|
|
|
with read_only_document():
|
|
self.assertNotEqual(Document.save, original_save)
|
|
|
|
self.assertEqual(Document.save, original_save)
|
|
|
|
def test_nested_read_only_document(self):
|
|
# Check that read_only_depth is not set initially
|
|
self.assertFalse(hasattr(frappe.local, "read_only_depth"))
|
|
|
|
with read_only_document():
|
|
# Check that read_only_depth is set to 1
|
|
self.assertEqual(frappe.local.read_only_depth, 1)
|
|
|
|
# Attempt to modify the document
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
self.test_doc.description = "Modified in outer context"
|
|
self.test_doc.save()
|
|
|
|
with read_only_document():
|
|
# Check that read_only_depth is incremented to 2
|
|
self.assertEqual(frappe.local.read_only_depth, 2)
|
|
|
|
# Attempt to modify the document in nested context
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
self.test_doc.description = "Modified in inner context"
|
|
self.test_doc.save()
|
|
|
|
# Check that read_only_depth is back to 1 after nested context
|
|
self.assertEqual(frappe.local.read_only_depth, 1)
|
|
|
|
# Attempt to modify the document again
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
self.test_doc.description = "Modified after inner context"
|
|
self.test_doc.save()
|
|
|
|
# Check that read_only_depth is removed after all contexts are closed
|
|
self.assertFalse(hasattr(frappe.local, "read_only_depth"))
|
|
|
|
# Verify that document can be modified outside read_only_document
|
|
self.test_doc.description = "Modified outside read_only_document"
|
|
self.test_doc.save()
|
|
self.assertEqual(self.test_doc.description, "Modified outside read_only_document")
|
|
|
|
def test_error_log_exception_in_read_only(self):
|
|
with read_only_document():
|
|
# Attempt to insert an Error Log
|
|
error_log = frappe.get_doc({"doctype": "Error Log", "error": "Test error in read-only mode"})
|
|
|
|
# This should not raise an exception
|
|
error_log.insert()
|
|
|
|
# Verify that the Error Log was inserted
|
|
self.assertTrue(error_log.name)
|
|
|
|
# Attempt to modify a different document
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
self.test_doc.description = "Modified in read-only mode"
|
|
self.test_doc.save()
|
|
|
|
# Clean up the inserted Error Log
|
|
frappe.delete_doc("Error Log", error_log.name)
|
|
|
|
def test_read_only_multiple_documents(self):
|
|
doc1 = frappe.get_doc({"doctype": "ToDo", "description": "Test ToDo 1"})
|
|
doc2 = frappe.get_doc({"doctype": "ToDo", "description": "Test ToDo 2"})
|
|
|
|
with read_only_document():
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
doc1.insert()
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
doc2.insert()
|
|
|
|
def test_read_only_custom_method(self):
|
|
class CustomDocument(Document):
|
|
def custom_save(self):
|
|
self.save()
|
|
|
|
custom_doc = CustomDocument({"doctype": "ToDo", "description": "Custom Test ToDo"})
|
|
|
|
with read_only_document():
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
custom_doc.custom_save()
|
|
|
|
def test_read_only_exception_handling(self):
|
|
@contextmanager
|
|
def exception_raiser():
|
|
raise Exception("Test exception")
|
|
yield
|
|
|
|
try:
|
|
with read_only_document(), exception_raiser():
|
|
pass
|
|
except Exception:
|
|
pass
|
|
|
|
# Ensure methods are restored even if an exception occurs
|
|
self.assertEqual(Document.save, self.test_doc.__class__.save)
|
|
|
|
def test_read_only_nested_context_managers(self):
|
|
original_save = Document.save
|
|
|
|
with read_only_document():
|
|
self.assertNotEqual(Document.save, original_save)
|
|
|
|
with read_only_document():
|
|
self.assertNotEqual(Document.save, original_save)
|
|
|
|
self.assertNotEqual(Document.save, original_save)
|
|
|
|
self.assertEqual(Document.save, original_save)
|
|
|
|
def test_read_only_method_call_details(self):
|
|
with read_only_document():
|
|
with self.assertRaises(frappe.DatabaseModificationError) as cm:
|
|
self.test_doc.save()
|
|
|
|
self.assertIn("Cannot call save in read-only document mode", str(cm.exception))
|
|
|
|
def test_read_only_does_not_affect_reads(self):
|
|
with read_only_document():
|
|
# These operations should not raise exceptions
|
|
doc = frappe.get_doc("ToDo", self.test_doc.name)
|
|
self.assertEqual(doc.description, "Test ToDo")
|
|
|
|
docs = frappe.get_all("ToDo", filters={"name": self.test_doc.name})
|
|
self.assertEqual(len(docs), 1)
|
|
|
|
def test_read_only_with_new_document_instance(self):
|
|
with read_only_document():
|
|
new_doc = frappe.new_doc("ToDo")
|
|
with self.assertRaises(frappe.DatabaseModificationError):
|
|
new_doc.insert()
|