seitime-frappe/frappe/tests/test_document_ro_mode.py
David Arnold 6f35a554a5
fix: make read only mode thread safe (#28359)
* fix: Apply read_only_method decorator to Document methods

* fix: update tests for read-only document context manager

* refactor: place mappers into read-only mode

Reapply "refactor: place mappers into read-only mode"

This reverts commit a8208d57069c63f0982bf2881bcad28a4b349f3c.

read-only mode is now thread safe
2024-11-18 15:00:50 +01:00

184 lines
6 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_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):
"""Test that read_only_depth is properly managed in nested contexts"""
self.assertFalse(hasattr(frappe.local, "read_only_depth"))
with read_only_document():
self.assertEqual(frappe.local.read_only_depth, 1)
with read_only_document():
self.assertEqual(frappe.local.read_only_depth, 2)
self.assertEqual(frappe.local.read_only_depth, 1)
self.assertFalse(hasattr(frappe.local, "read_only_depth"))
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()