refactor: move whitelist_for_tests to common utils and apply to all test endpoints

- Moved whitelist_for_tests decorator from ui_test_helpers to frappe.tests.utils
- Enhanced decorator to accept kwargs for passing to frappe.whitelist()
- Applied decorator consistently to test endpoints in:
  - test_api.py
  - test_api_v2.py
  - test_caching.py
  - test_search.py
- Updated all imports to use direct import from frappe.tests.utils
This commit is contained in:
Sagar Vora 2025-11-21 15:39:10 +05:30
parent 43133d01f3
commit e3702efd44
6 changed files with 71 additions and 46 deletions

View file

@ -16,6 +16,7 @@ from werkzeug.test import TestResponse
import frappe
from frappe.installer import update_site_config
from frappe.tests import IntegrationTestCase
from frappe.tests.utils import whitelist_for_tests
from frappe.utils import cint, get_test_client, get_url
try:
@ -522,7 +523,7 @@ def generate_admin_keys():
frappe.db.commit()
@frappe.whitelist()
@whitelist_for_tests()
def test(*, fail=False, handled=True, message="Failed"):
if fail:
if handled:
@ -533,6 +534,6 @@ def test(*, fail=False, handled=True, message="Failed"):
frappe.msgprint(message)
@frappe.whitelist(allow_guest=True)
@whitelist_for_tests(allow_guest=True)
def test_array(data):
return data

View file

@ -6,7 +6,7 @@ import requests
import frappe
from frappe.installer import update_site_config
from frappe.tests.test_api import FrappeAPITestCase, suppress_stdout
from frappe.tests.utils import toggle_test_mode
from frappe.tests.utils import toggle_test_mode, whitelist_for_tests
authorization_token = None
@ -317,7 +317,7 @@ def generate_admin_keys():
frappe.db.commit()
@frappe.whitelist()
@whitelist_for_tests()
def test(*, fail=False, handled=True, message="Failed"):
if fail:
if handled:

View file

@ -5,6 +5,7 @@ import frappe
from frappe.core.doctype.doctype.test_doctype import new_doctype
from frappe.tests import IntegrationTestCase
from frappe.tests.test_api import FrappeAPITestCase
from frappe.tests.utils import whitelist_for_tests
from frappe.utils.caching import redis_cache, request_cache, site_cache
CACHE_TTL = 4
@ -21,14 +22,14 @@ def request_specific_api(a: list | tuple | dict | int, b: int) -> int:
return a**b * todays_value
@frappe.whitelist(allow_guest=True)
@whitelist_for_tests(allow_guest=True)
@site_cache
def ping() -> str:
register_with_external_service(frappe.local.site)
return "pong"
@frappe.whitelist(allow_guest=True)
@whitelist_for_tests(allow_guest=True)
@site_cache(ttl=CACHE_TTL)
def ping_with_ttl() -> str:
register_with_external_service(frappe.local.site)

View file

@ -7,6 +7,7 @@ from functools import partial
import frappe
from frappe.desk.search import get_names_for_mentions, search_link, search_widget
from frappe.tests import IntegrationTestCase
from frappe.tests.utils import whitelist_for_tests
class TestSearch(IntegrationTestCase):
@ -186,7 +187,7 @@ def get_data(doctype, txt, searchfield, start, page_len, filters):
return [doctype, txt, searchfield, start, page_len, filters]
@frappe.whitelist()
@whitelist_for_tests()
@frappe.validate_and_sanitize_search_inputs
def query_with_reference_doctype(doctype, txt, searchfield, start, page_len, filters, reference_doctype=None):
return []

View file

@ -4,21 +4,13 @@ import frappe
from frappe import _
from frappe.permissions import AUTOMATIC_ROLES
from frappe.tests.test_helpers import create_test_blog_category
from frappe.tests.utils import whitelist_for_tests
from frappe.utils import add_to_date, now
UI_TEST_USER = "frappe@example.com"
def whitelist_for_tests(fn):
if frappe.request and not (frappe._dev_server and (frappe.conf.allow_tests or os.environ.get("CI"))):
frappe.throw( # nosemgrep: frappe-missing-translate-function-python
'Cannot run UI tests. Use a development server with "bench start" and ensure that the "allow_tests" site config is enabled.'
)
return frappe.whitelist()(fn)
@whitelist_for_tests
@whitelist_for_tests()
def create_if_not_exists(doc):
"""Create records if they dont exist.
Will check for uniqueness by checking if a record exists with these field value pairs
@ -48,7 +40,7 @@ def create_if_not_exists(doc):
return names
@whitelist_for_tests
@whitelist_for_tests()
def create_todo_records():
frappe.db.truncate("ToDo")
@ -84,7 +76,7 @@ def create_todo_records():
).insert()
@whitelist_for_tests
@whitelist_for_tests()
def prepare_webform_test():
for note in frappe.get_all("Note", pluck="name"):
frappe.delete_doc("Note", note, force=True)
@ -92,14 +84,14 @@ def prepare_webform_test():
frappe.delete_doc_if_exists("Web Form", "note")
@whitelist_for_tests
@whitelist_for_tests()
def create_doctype_for_attachment():
create_test_blog_category()
doc = frappe.get_doc("Test Blog Category", "_Test Blog Category 2")
return doc
@whitelist_for_tests
@whitelist_for_tests()
def create_communication_record():
doc = frappe.get_doc(
{
@ -113,7 +105,7 @@ def create_communication_record():
return doc
@whitelist_for_tests
@whitelist_for_tests()
def setup_workflow():
from frappe.workflow.doctype.workflow.test_workflow import create_todo_workflow
@ -122,7 +114,7 @@ def setup_workflow():
frappe.clear_cache()
@whitelist_for_tests
@whitelist_for_tests()
def create_contact_phone_nos_records():
if frappe.get_all("Contact", {"first_name": "Test Contact"}):
return
@ -134,7 +126,7 @@ def create_contact_phone_nos_records():
doc.insert()
@whitelist_for_tests
@whitelist_for_tests()
def create_doctype(name, fields):
fields = frappe.parse_json(fields)
if frappe.db.exists("DocType", name):
@ -152,7 +144,7 @@ def create_doctype(name, fields):
).insert()
@whitelist_for_tests
@whitelist_for_tests()
def create_child_doctype(name, fields):
fields = frappe.parse_json(fields)
if frappe.db.exists("DocType", name):
@ -170,7 +162,7 @@ def create_child_doctype(name, fields):
).insert()
@whitelist_for_tests
@whitelist_for_tests()
def create_contact_records():
if frappe.get_all("Contact", {"first_name": "Test Form Contact 1"}):
return
@ -180,7 +172,7 @@ def create_contact_records():
insert_contact("Test Form Contact 3", "12345")
@whitelist_for_tests
@whitelist_for_tests()
def create_multiple_todo_records():
if frappe.get_all("ToDo", {"description": "Multiple ToDo 1"}):
return
@ -196,7 +188,7 @@ def insert_contact(first_name, phone_number):
doc.insert()
@whitelist_for_tests
@whitelist_for_tests()
def create_form_tour():
if frappe.db.exists("Form Tour", {"name": "Test Form Tour"}):
return
@ -246,7 +238,7 @@ def create_form_tour():
tour.insert()
@whitelist_for_tests
@whitelist_for_tests()
def create_data_for_discussions():
web_page = create_web_page("Test page for discussions", "test-page-discussions", False)
create_topic_and_reply(web_page)
@ -302,7 +294,7 @@ def create_topic_and_reply(web_page):
reply.save()
@whitelist_for_tests
@whitelist_for_tests()
def update_webform_to_multistep():
if not frappe.db.exists("Web Form", "update-profile-duplicate"):
doc = frappe.get_doc("Web Form", "edit-profile")
@ -314,7 +306,7 @@ def update_webform_to_multistep():
_doc.save()
@whitelist_for_tests
@whitelist_for_tests()
def update_child_table(name):
doc = frappe.get_doc("DocType", name)
if len(doc.fields) == 1:
@ -332,7 +324,7 @@ def update_child_table(name):
doc.save()
@whitelist_for_tests
@whitelist_for_tests()
def insert_doctype_with_child_table_record(name):
if frappe.get_all(name, {"title": "Test Grid Search"}):
return
@ -378,7 +370,7 @@ def insert_doctype_with_child_table_record(name):
doc.insert()
@whitelist_for_tests
@whitelist_for_tests()
def insert_translations():
translation = [
{
@ -411,7 +403,7 @@ def insert_translations():
frappe.get_doc(doc).insert(ignore_if_duplicate=True)
@whitelist_for_tests
@whitelist_for_tests()
def create_test_user(username=None):
name = username or UI_TEST_USER
@ -439,7 +431,7 @@ def create_test_user(username=None):
frappe.db.set_single_value("Workspace Settings", "workspace_setup_completed", 1)
@whitelist_for_tests
@whitelist_for_tests()
def setup_tree_doctype():
frappe.delete_doc_if_exists("DocType", "Custom Tree", force=True)
@ -463,7 +455,7 @@ def setup_tree_doctype():
frappe.get_doc({"doctype": "Custom Tree", "tree": "All Trees"}).insert()
@whitelist_for_tests
@whitelist_for_tests()
def setup_image_doctype():
frappe.delete_doc_if_exists("DocType", "Custom Image", force=True)
@ -482,7 +474,7 @@ def setup_image_doctype():
).insert()
@whitelist_for_tests
@whitelist_for_tests()
def setup_inbox():
frappe.db.delete("User Email")
doc = frappe.new_doc("Email Account")
@ -494,7 +486,7 @@ def setup_inbox():
user.save()
@whitelist_for_tests
@whitelist_for_tests()
def setup_default_view(view, force_reroute=None):
frappe.delete_doc_if_exists("Property Setter", "Event-main-default_view")
frappe.delete_doc_if_exists("Property Setter", "Event-main-force_re_route_to_default_view")
@ -525,7 +517,7 @@ def setup_default_view(view, force_reroute=None):
).insert()
@whitelist_for_tests
@whitelist_for_tests()
def create_kanban():
if not frappe.db.exists("Custom Field", "Note-kanban"):
frappe.get_doc(
@ -567,12 +559,12 @@ def create_kanban():
).insert()
@whitelist_for_tests
@whitelist_for_tests()
def create_todo(description):
return frappe.get_doc({"doctype": "ToDo", "description": description}).insert()
@whitelist_for_tests
@whitelist_for_tests()
def create_todo_with_attachment_limit(description):
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
@ -581,7 +573,7 @@ def create_todo_with_attachment_limit(description):
return frappe.get_doc({"doctype": "ToDo", "description": description}).insert()
@whitelist_for_tests
@whitelist_for_tests()
def create_admin_kanban():
if not frappe.db.exists("Kanban Board", "Admin Kanban"):
frappe.get_doc(
@ -610,7 +602,7 @@ def create_admin_kanban():
).insert()
@whitelist_for_tests
@whitelist_for_tests()
def add_remove_role(action, user, role):
user_doc = frappe.get_doc("User", user)
if action == "remove":
@ -619,7 +611,7 @@ def add_remove_role(action, user, role):
user_doc.add_roles(role)
@whitelist_for_tests
@whitelist_for_tests()
def publish_realtime(
event=None,
message=None,
@ -640,7 +632,7 @@ def publish_realtime(
)
@whitelist_for_tests
@whitelist_for_tests()
def publish_progress(duration=3, title=None, doctype=None, docname=None):
# This should consider session user and only show it to current user.
frappe.enqueue(slow_task, duration=duration, title=title, doctype=doctype, docname=docname)
@ -656,7 +648,7 @@ def slow_task(duration, title, doctype, docname):
time.sleep(int(duration) / steps)
@whitelist_for_tests
@whitelist_for_tests()
def empty_my_workspaces():
my_workspaces = frappe.get_doc("Workspace Sidebar", "My Workspaces")
my_workspaces.items = []

View file

@ -1,4 +1,6 @@
import logging
import os
from functools import wraps
import frappe
@ -7,6 +9,34 @@ logger = logging.Logger(__file__)
from .generators import *
def whitelist_for_tests(**whitelist_kwargs):
"""Decorator to whitelist test endpoints.
Only allows access when running in test mode or running a development server with testing enabled.
Supports all parameters that @frappe.whitelist() accepts.
Usage:
@whitelist_for_tests(allow_guest=True)
def my_guest_test_endpoint():
...
"""
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
if not (
frappe.in_test or (frappe._dev_server and (frappe.conf.allow_tests or os.environ.get("CI")))
):
frappe.throw( # nosemgrep: frappe-missing-translate-function-python
'Test endpoints are only available when running in test mode or running a development server ("bench start") with the "allow_tests" site config enabled'
)
return fn(*args, **kwargs)
return frappe.whitelist(**whitelist_kwargs)(wrapper)
return decorator
def check_orpahned_doctypes():
"""Check that all doctypes in DB actually exist after patch test"""
from frappe.model.base_document import get_controller