seitime-frappe/frappe/tests/test_website.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

436 lines
15 KiB
Python

from unittest.mock import patch
import frappe
from frappe import get_hooks
from frappe.tests import IntegrationTestCase
from frappe.utils import set_request
from frappe.website.page_renderers.static_page import StaticPage
from frappe.website.serve import get_response, get_response_content
from frappe.website.utils import build_response, clear_website_cache, get_home_page
class TestWebsite(IntegrationTestCase):
def setUp(self):
frappe.set_user("Guest")
self._clearRequest()
def tearDown(self):
frappe.db.delete("Access Log")
frappe.set_user("Administrator")
self._clearRequest()
def _clearRequest(self):
if hasattr(frappe.local, "request"):
delattr(frappe.local, "request")
def test_home_page(self):
frappe.set_user("Administrator")
# test home page via role
user = frappe.get_doc(
doctype="User", email="test-user-for-home-page@example.com", first_name="test"
).insert(ignore_if_duplicate=True)
user.reload()
role = frappe.get_doc(doctype="Role", role_name="home-page-test", desk_access=0).insert(
ignore_if_duplicate=True
)
user.add_roles(role.name)
user.save()
frappe.db.set_value("Role", "home-page-test", "home_page", "home-page-test")
frappe.set_user("test-user-for-home-page@example.com")
self.assertEqual(get_home_page(), "home-page-test")
frappe.set_user("Administrator")
frappe.db.set_value("Role", "home-page-test", "home_page", "")
# home page via portal settings
frappe.db.set_single_value("Portal Settings", "default_portal_home", "test-portal-home")
frappe.set_user("test-user-for-home-page@example.com")
frappe.cache.hdel("home_page", frappe.session.user)
self.assertEqual(get_home_page(), "test-portal-home")
frappe.db.set_single_value("Portal Settings", "default_portal_home", "")
clear_website_cache()
# home page via website settings
frappe.db.set_single_value("Website Settings", "home_page", "contact")
self.assertEqual(get_home_page(), "contact")
frappe.db.set_single_value("Website Settings", "home_page", None)
clear_website_cache()
# fallback homepage
self.assertEqual(get_home_page(), "me")
# fallback homepage for guest
frappe.set_user("Guest")
self.assertEqual(get_home_page(), "login")
frappe.set_user("Administrator")
# test homepage via hooks
clear_website_cache()
with patch.object(
frappe,
"get_hooks",
patched_get_hooks(
"get_website_user_home_page", ["frappe.www._test._test_home_page.get_website_user_home_page"]
),
):
self.assertEqual(get_home_page(), "_test/_test_folder")
clear_website_cache()
with patch.object(frappe, "get_hooks", patched_get_hooks("website_user_home_page", ["login"])):
self.assertEqual(get_home_page(), "login")
clear_website_cache()
with patch.object(frappe, "get_hooks", patched_get_hooks("home_page", ["about"])):
self.assertEqual(get_home_page(), "about")
clear_website_cache()
with patch.object(
frappe, "get_hooks", patched_get_hooks("role_home_page", {"home-page-test": ["home-page-test"]})
):
self.assertEqual(get_home_page(), "home-page-test")
def test_page_load(self):
set_request(method="POST", path="login")
response = get_response()
self.assertEqual(response.status_code, 200)
html = frappe.safe_decode(response.get_data())
self.assertTrue("// login.js" in html)
self.assertTrue("<!-- login.html -->" in html)
def test_static_page(self):
set_request(method="GET", path="/_test/static-file-test.png")
response = get_response()
self.assertEqual(response.status_code, 200)
set_request(method="GET", path="/_test/assets/image.jpg")
response = get_response()
self.assertEqual(response.status_code, 200)
set_request(method="GET", path="/_test/assets/image")
response = get_response()
self.assertEqual(response.status_code, 200)
with patch.object(StaticPage, "render") as static_render:
set_request(method="GET", path="/_test/assets/image")
response = get_response()
static_render.assert_called()
def test_error_page(self):
set_request(method="GET", path="/_test/problematic_page")
response = get_response()
self.assertEqual(response.status_code, 417)
def test_login(self):
set_request(method="GET", path="/login")
response = get_response()
self.assertEqual(response.status_code, 200)
html = frappe.safe_decode(response.get_data())
self.assertTrue("// login.js" in html)
self.assertTrue("<!-- login.html -->" in html)
def test_app(self):
frappe.set_user("Administrator")
set_request(method="GET", path="/app")
response = get_response()
self.assertEqual(response.status_code, 200)
html = frappe.safe_decode(response.get_data())
self.assertTrue("window.app = true;" in html)
frappe.local.session_obj = None
def test_not_found(self):
set_request(method="GET", path="/_test/missing")
response = get_response()
self.assertEqual(response.status_code, 404)
def test_redirect(self):
import frappe.hooks
frappe.set_user("Administrator")
frappe.hooks.website_redirects = [
dict(source=r"/testfrom", target=r"://testto1"),
dict(source=r"/testfromregex.*", target=r"://testto2"),
dict(source=r"/testsub/(.*)", target=r"://testto3/\1"),
dict(source=r"/courses/course\?course=(.*)", target=r"/courses/\1", match_with_query_string=True),
dict(
source="/test307",
target="/test",
redirect_http_status=307,
),
]
website_settings = frappe.get_doc("Website Settings")
website_settings.append(
"route_redirects",
{"source": "/testsource", "target": "/testtarget"},
)
website_settings.append(
"route_redirects",
{"source": "/testdoc307", "target": "/testtarget", "redirect_http_status": 307},
)
website_settings.save()
set_request(method="GET", path="/testfrom")
response = get_response()
self.assertEqual(response.status_code, 301)
self.assertEqual(response.headers.get("Location"), r"://testto1")
set_request(method="GET", path="/testfromregex/test")
response = get_response()
self.assertEqual(response.status_code, 301)
self.assertEqual(response.headers.get("Location"), r"://testto2")
set_request(method="GET", path="/testsub/me")
response = get_response()
self.assertEqual(response.status_code, 301)
self.assertEqual(response.headers.get("Location"), r"://testto3/me")
set_request(method="GET", path="/test404")
response = get_response()
self.assertEqual(response.status_code, 404)
set_request(method="GET", path="/testsource")
response = get_response()
self.assertEqual(response.status_code, 301)
self.assertEqual(response.headers.get("Location"), "/testtarget")
set_request(method="GET", path="/testdoc307")
response = get_response()
self.assertEqual(response.status_code, 307)
self.assertEqual(response.headers.get("Location"), "/testtarget")
set_request(method="GET", path="/courses/course?course=data")
response = get_response()
self.assertEqual(response.status_code, 301)
self.assertEqual(response.headers.get("Location"), "/courses/data")
set_request(method="GET", path="/test307")
response = get_response()
self.assertEqual(response.status_code, 307)
self.assertEqual(response.headers.get("Location"), "/test")
set_request(method="POST", path="/test307")
response = get_response()
self.assertEqual(response.status_code, 307)
self.assertEqual(response.headers.get("Location"), "/test")
delattr(frappe.hooks, "website_redirects")
frappe.cache.delete_key("app_hooks")
def test_custom_page_renderer(self):
from frappe import get_hooks
def patched_get_hooks(*args, **kwargs):
return_value = get_hooks(*args, **kwargs)
if args and args[0] == "page_renderer":
return_value = ["frappe.tests.test_website.CustomPageRenderer"]
return return_value
with patch.object(frappe, "get_hooks", patched_get_hooks):
set_request(method="GET", path="/custom")
response = get_response()
self.assertEqual(response.status_code, 3984)
set_request(method="GET", path="/new")
content = get_response_content()
self.assertIn("<div>Custom Page Response</div>", content)
set_request(method="GET", path="/random")
response = get_response()
self.assertEqual(response.status_code, 404)
def test_printview_page(self):
frappe.db.value_cache[("DocType", "Language", "name")] = (("Language",),)
frappe.set_user("Administrator")
content = get_response_content("/Language/ru")
self.assertIn('<div class="print-format">', content)
self.assertIn("<div>Language</div>", content)
def test_custom_base_template_path(self):
content = get_response_content("/_test/_test_folder/_test_page")
# assert the text in base template is rendered
self.assertIn("<h1>This is for testing</h1>", content)
# assert template block rendered
self.assertIn("<p>Test content</p>", content)
def test_json_sidebar_data(self):
frappe.flags.look_for_sidebar = False
content = get_response_content("/_test/_test_folder/_test_page")
self.assertNotIn("Test Sidebar", content)
clear_website_cache()
frappe.flags.look_for_sidebar = True
content = get_response_content("/_test/_test_folder/_test_page")
self.assertIn("Test Sidebar", content)
frappe.flags.look_for_sidebar = False
def test_base_template(self):
content = get_response_content("/_test/_test_custom_base.html")
# assert the text in base template is rendered
self.assertIn("<h1>This is for testing</h1>", content)
# assert template block rendered
self.assertIn("<p>Test content</p>", content)
def test_index_and_next_comment(self):
content = get_response_content("/_test/_test_folder")
# test if {index} was rendered
self.assertIn('<a href="/_test/_test_folder/_test_page"> Test Page</a>', content)
self.assertIn('<a href="/_test/_test_folder/_test_toc">Test TOC</a>', content)
content = get_response_content("/_test/_test_folder/_test_page")
# test if {next} was rendered
self.assertIn('Next: <a class="btn-next" href="/_test/_test_folder/_test_toc">Test TOC</a>', content)
def test_colocated_assets(self):
content = get_response_content("/_test/_test_folder/_test_page")
self.assertIn("""<script>console.log("test data");\n</script>""", content)
self.assertIn("background-color: var(--bg-color);", content)
def test_raw_assets_are_loaded(self):
content = get_response_content("/_test/assets/js_asset.min.js")
# minified js files should not be passed through jinja renderer
self.assertEqual("""//{% if title %} {{title}} {% endif %}\nconsole.log("in");\n""", content)
content = get_response_content("/_test/assets/css_asset.css")
self.assertEqual("""body{color:red}""", content)
def test_breadcrumbs(self):
content = get_response_content("/_test/_test_folder/_test_page")
self.assertIn('<span itemprop="name">Test Folder</span>', content)
self.assertIn('<span itemprop="name"> Test Page</span>', content)
content = get_response_content("/_test/_test_folder/index")
self.assertIn('<span itemprop="name"> Test</span>', content)
self.assertIn('<span itemprop="name">Test Folder</span>', content)
def test_get_context_without_context_object(self):
content = get_response_content("/_test/_test_no_context")
self.assertIn("Custom Content", content)
def test_caching(self):
# to enable caching
frappe.flags.force_website_cache = True
clear_website_cache()
# first response no-cache
response = get_response("/_test/_test_folder/_test_page")
self.assertIn(("X-From-Cache", "False"), list(response.headers))
# first response returned from cache
response = get_response("/_test/_test_folder/_test_page")
self.assertIn(("X-From-Cache", "True"), list(response.headers))
frappe.flags.force_website_cache = False
def test_safe_render(self):
content = get_response_content("/_test/_test_safe_render_on")
self.assertNotIn("Safe Render On", content)
self.assertIn("frappe.exceptions.ValidationError: Illegal template", content)
content = get_response_content("/_test/_test_safe_render_off")
self.assertIn("Safe Render Off", content)
self.assertIn("test.__test", content)
self.assertNotIn("frappe.exceptions.ValidationError: Illegal template", content)
def test_never_render(self):
from pathlib import Path
from random import choices
WWW = Path(frappe.get_app_path("frappe")) / "www"
FILES_TO_SKIP = choices(list(WWW.glob("**/*.py*")), k=10)
for suffix in FILES_TO_SKIP:
path: str = suffix.relative_to(WWW).as_posix()
content = get_response_content(path)
self.assertIn("<title>Not Found</title>", content)
def test_metatags(self):
content = get_response_content("/_test/_test_metatags")
self.assertIn('<meta name="title" content="Test Title Metatag">', content)
self.assertIn('<meta name="description" content="Test Description for Metatag">', content)
def test_resolve_class(self):
from frappe.utils.jinja_globals import resolve_class
context = frappe._dict(primary=True)
self.assertEqual(resolve_class("test"), "test")
self.assertEqual(resolve_class("test", "test-2"), "test test-2")
self.assertEqual(resolve_class("test", {"test-2": False, "test-3": True}), "test test-3")
self.assertEqual(
resolve_class(["test1", "test2", context.primary and "primary"]), "test1 test2 primary"
)
content = '<a class="{{ resolve_class("btn btn-default", primary and "btn-primary") }}">Test</a>'
self.assertEqual(
frappe.render_template(content, context), '<a class="btn btn-default btn-primary">Test</a>'
)
def test_app_include(self):
from frappe import get_hooks
def patched_get_hooks(*args, **kwargs):
return_value = get_hooks(*args, **kwargs)
if isinstance(return_value, dict) and "app_include_js" in return_value:
return_value.app_include_js.append("test_app_include.js")
return_value.app_include_css.append("test_app_include.css")
return return_value
with patch.object(frappe, "get_hooks", patched_get_hooks):
frappe.set_user("Administrator")
frappe.hooks.app_include_js.append("test_app_include.js")
frappe.hooks.app_include_css.append("test_app_include.css")
frappe.conf.update({"app_include_js": ["test_app_include_via_site_config.js"]})
frappe.conf.update({"app_include_css": ["test_app_include_via_site_config.css"]})
set_request(method="GET", path="/app")
content = get_response_content("/app")
self.assertIn('<script type="text/javascript" src="/test_app_include.js"></script>', content)
self.assertIn(
'<script type="text/javascript" src="/test_app_include_via_site_config.js"></script>', content
)
self.assertIn('<link type="text/css" rel="stylesheet" href="/test_app_include.css">', content)
self.assertIn(
'<link type="text/css" rel="stylesheet" href="/test_app_include_via_site_config.css">',
content,
)
delattr(frappe.local, "request")
frappe.set_user("Guest")
def patched_get_hooks(hook, value):
def wrapper(*args, **kwargs):
return_value = get_hooks(*args, **kwargs)
if args[0] == hook:
return_value = value
return return_value
return wrapper
class CustomPageRenderer:
def __init__(self, path, status_code=None):
self.path = path
# custom status code
self.status_code = 3984
def can_render(self):
if self.path in ("new", "custom"):
return True
def render(self):
return build_response(self.path, """<div>Custom Page Response</div>""", self.status_code)