seitime-frappe/frappe/tests/test_document_ro_mode.py
David Arnold c114e5fae8
refactor: unit vs integration treewide (#27992)
* 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
2024-10-06 09:43:36 +00:00

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()