Coverage for tests/test_served_page.py: 100%
58 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-09 14:41 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-09 14:41 +0000
1import dataclasses
2import typing
4import hypothesis
5import pytest
6from hypothesis import strategies as st
8from archdocs import settings
9from tests import served_page
10from tests.diagram_rendering import build_named_settings
13# Minifying the template by joining on "" used to glue attributes onto their tags, turning the
14# mermaid script tag into `<scriptsrc=...>`: the served document is the only proof there is.
15_SERVICE_NAME: typing.Final = "page-svc"
16# A quote in the name closes the node label early and takes the whole render down with it, so
17# it may not reach the page at all — the markup characters around it have to survive escaped.
18_MARKED_UP_SERVICE_NAME: typing.Final = 'svc<b>&"x'
19# The template ships a demo diagram inside its `<pre>`: the page has to replace that slot, not
20# grow a second one next to it.
21_TEMPLATE_DEMO_MARK: typing.Final = "Example Start Node"
22_RENDERER_ARGUMENT: typing.Final = "render_page"
23_HYPOTHESIS_EXAMPLES: typing.Final = 20
24_TEMPLATE_WITHOUT_A_SLOT: typing.Final = "<!DOCTYPE html><html lang='en'><body>no room here</body></html>"
25_REQUIRED_PAGE_TAGS: typing.Final = (
26 "<!DOCTYPE html>",
27 '<html lang="en">',
28 '<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js">',
29 '<link href="https://fonts.googleapis.com/css2?family=Inter:wght@600&display=swap" rel="stylesheet" />',
30 '<pre class="archdoc__diagram">',
31 "graph TB",
32)
33_NAME_ALPHABET: typing.Final = st.characters(whitelist_categories=["Ll", "Lu"], whitelist_characters=["_", "-"])
34_NAME_STRATEGY: typing.Final = st.text(min_size=1, max_size=10, alphabet=_NAME_ALPHABET)
37@pytest.mark.parametrize(_RENDERER_ARGUMENT, served_page.ALL_PAGE_RENDERERS, ids=served_page.FRAMEWORK_IDS)
38def test_default_route_serves_a_whole_document(render_page: served_page.PageRenderer) -> None:
39 page_html: typing.Final = render_page(build_named_settings(_SERVICE_NAME), None)
41 for one_required_tag in _REQUIRED_PAGE_TAGS:
42 assert page_html.count(one_required_tag) == 1, one_required_tag
43 assert _SERVICE_NAME in page_html
44 assert _TEMPLATE_DEMO_MARK not in page_html
47# Without settings the scan starts from the working directory, which under pytest is the
48# repository itself: the page has to name the default service and draw its node.
49@pytest.mark.parametrize(_RENDERER_ARGUMENT, served_page.ALL_PAGE_RENDERERS, ids=served_page.FRAMEWORK_IDS)
50def test_omitted_settings_draw_the_default(render_page: served_page.PageRenderer) -> None:
51 page_html: typing.Final = render_page(None, None)
53 assert 'example_service{"example-service' in page_html
56# A template shipped without a `<pre>` slot has nowhere to put the diagram: the page still has
57# to be a page.
58@pytest.mark.parametrize(_RENDERER_ARGUMENT, served_page.ALL_PAGE_RENDERERS, ids=served_page.FRAMEWORK_IDS)
59def test_template_without_a_slot_is_served_whole(
60 render_page: served_page.PageRenderer,
61 monkeypatch: pytest.MonkeyPatch,
62) -> None:
63 monkeypatch.setattr(settings, "UI_HTML_TEMPLATE", _TEMPLATE_WITHOUT_A_SLOT)
65 page_html: typing.Final = render_page(build_named_settings(_SERVICE_NAME), None)
67 assert page_html == _TEMPLATE_WITHOUT_A_SLOT
70@pytest.mark.parametrize(_RENDERER_ARGUMENT, served_page.ALL_PAGE_RENDERERS, ids=served_page.FRAMEWORK_IDS)
71def test_markup_characters_are_escaped(render_page: served_page.PageRenderer) -> None:
72 page_html: typing.Final = render_page(build_named_settings(_MARKED_UP_SERVICE_NAME), None)
74 assert 'svc<b>&x"}' in page_html
75 assert "svc<b>" not in page_html
78@pytest.mark.parametrize(_RENDERER_ARGUMENT, served_page.ALL_PAGE_RENDERERS, ids=served_page.FRAMEWORK_IDS)
79@hypothesis.settings(deadline=None, max_examples=_HYPOTHESIS_EXAMPLES)
80@hypothesis.given(service_name=_NAME_STRATEGY, route_value=_NAME_STRATEGY)
81def test_any_service_name_and_route_are_served(
82 render_page: served_page.PageRenderer,
83 service_name: str,
84 route_value: str,
85) -> None:
86 page_html: typing.Final = render_page(build_named_settings(service_name), f"/{route_value}")
88 assert service_name in page_html
91# A network that cannot reach the CDNs serves mermaid itself and goes without the font: neither
92# public host may be left on the page, or the browser still waits on it.
93@pytest.mark.parametrize(_RENDERER_ARGUMENT, served_page.ALL_PAGE_RENDERERS, ids=served_page.FRAMEWORK_IDS)
94def test_offline_page_names_no_public_host(render_page: served_page.PageRenderer) -> None:
95 offline_settings: typing.Final = dataclasses.replace(
96 build_named_settings(_SERVICE_NAME),
97 mermaid_script_url="/static/mermaid.min.js?v=11&lang=en",
98 font_stylesheet_url=None,
99 )
101 page_html: typing.Final = render_page(offline_settings, None)
103 assert '<script src="/static/mermaid.min.js?v=11&lang=en"></script>' in page_html
104 assert "cdn.jsdelivr.net" not in page_html
105 assert "fonts.googleapis.com" not in page_html
106 assert _SERVICE_NAME in page_html
109@pytest.mark.parametrize(_RENDERER_ARGUMENT, served_page.ALL_PAGE_RENDERERS, ids=served_page.FRAMEWORK_IDS)
110def test_own_font_replaces_the_default(render_page: served_page.PageRenderer) -> None:
111 own_font_settings: typing.Final = dataclasses.replace(
112 build_named_settings(_SERVICE_NAME),
113 font_stylesheet_url="/static/inter.css",
114 )
116 page_html: typing.Final = render_page(own_font_settings, None)
118 assert '<link href="/static/inter.css" rel="stylesheet" />' in page_html
119 assert "fonts.googleapis.com" not in page_html