The test runner walks link-field dependencies recursively to pre-generate test records via `get_missing_records_doctypes`. If any DocType in the transitive link graph belonged to an app not installed on the test site, the walk crashed with `DoesNotExistError`, aborting the entire suite before a single test ran. Treat such link targets as dead-end leaves instead: - `get_modules` now returns `(None, None)` when the DocType row does not exist, instead of falling through into `load_doctype_module` which raises. - `get_missing_records_doctypes` checks for `module is None`, logs a warning naming the parent DocType that linked to it, and returns without descending further. This restores the ability to run downstream test suites that link (directly or transitively) to optional/uninstalled apps without forcing every CI environment to know the full transitive link graph. Fixes #38747
71 lines
2.5 KiB
Python
71 lines
2.5 KiB
Python
import logging
|
|
from datetime import timedelta
|
|
|
|
import frappe
|
|
from frappe.tests import IntegrationTestCase
|
|
from frappe.tests.utils.generators import get_missing_records_doctypes, get_modules
|
|
from frappe.utils.data import now_datetime
|
|
|
|
|
|
class TestTestUtils(IntegrationTestCase):
|
|
SHOW_TRANSACTION_COMMIT_WARNINGS = True
|
|
|
|
def test_document_assertions(self):
|
|
currency = frappe.new_doc("Currency")
|
|
currency.currency_name = "STONKS"
|
|
currency.smallest_currency_fraction_value = 0.420_001
|
|
currency.save()
|
|
|
|
self.assertDocumentEqual(currency.as_dict(), currency)
|
|
|
|
def test_thread_locals(self):
|
|
frappe.flags.temp_flag_to_be_discarded = True
|
|
|
|
def test_temp_setting_changes(self):
|
|
current_setting = frappe.get_system_settings("logout_on_password_reset")
|
|
|
|
with IntegrationTestCase.change_settings(
|
|
"System Settings", {"logout_on_password_reset": int(not current_setting)}
|
|
):
|
|
updated_settings = frappe.get_single_value("System Settings", "logout_on_password_reset")
|
|
self.assertNotEqual(current_setting, updated_settings)
|
|
|
|
restored_settings = frappe.get_single_value("System Settings", "logout_on_password_reset")
|
|
self.assertEqual(current_setting, restored_settings)
|
|
|
|
def test_time_freezing(self):
|
|
now = now_datetime()
|
|
|
|
tomorrow = now + timedelta(days=1)
|
|
with self.freeze_time(tomorrow):
|
|
self.assertEqual(now_datetime(), tomorrow)
|
|
|
|
def test_get_modules_returns_none_for_missing_doctype(self):
|
|
"""DocTypes from uninstalled apps should resolve to (None, None) instead of raising."""
|
|
get_modules.cache_clear()
|
|
try:
|
|
module, test_module = get_modules("Definitely Not A Real DocType")
|
|
finally:
|
|
get_modules.cache_clear()
|
|
self.assertIsNone(module)
|
|
self.assertIsNone(test_module)
|
|
|
|
def test_get_missing_records_doctypes_skips_missing_doctype(self):
|
|
"""Missing link targets should be skipped with a warning, not crash the walk."""
|
|
get_modules.cache_clear()
|
|
try:
|
|
with self.assertLogs("frappe.testing.generators", level=logging.WARNING) as log_ctx:
|
|
result = get_missing_records_doctypes("Definitely Not A Real DocType")
|
|
finally:
|
|
get_modules.cache_clear()
|
|
self.assertEqual(result, [])
|
|
self.assertTrue(
|
|
any("Definitely Not A Real DocType" in line for line in log_ctx.output),
|
|
f"Expected warning mentioning the missing doctype, got: {log_ctx.output}",
|
|
)
|
|
|
|
|
|
def tearDownModule():
|
|
"""assertions for ensuring tests didn't leave state behind"""
|
|
assert "temp_flag_to_be_discarded" not in frappe.flags
|
|
assert not frappe.db.exists("Currency", "STONKS")
|