Coverage for tests/test_diagram_layout.py: 100%
45 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-18 22:03 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-18 22:03 +0000
1import types
2import typing
4import pytest
6from archdocs.main import SettingsForArchdocs
7from tests import diagram_parts, diagram_rendering
10# Mermaid has no coordinates: a diagram is laid out from the direction of its arrows and from
11# the order its lines are written in, so both are part of the contract.
12_INBOUND_GROUP: typing.Final = "Inbound API"
13_OUTBOUND_GROUP: typing.Final = "Outbound calls"
14_MESSAGING_GROUP: typing.Final = "Messaging & tasks"
15_DATA_STORES_GROUP: typing.Final = "Data stores"
16_CONFIGURATION_GROUP: typing.Final = "Configuration"
17_SHOWCASE_NODE_ID: typing.Final = "showcase_service"
18_EXPECTED_SHOWCASE_GROUPS: typing.Final = types.MappingProxyType(
19 {
20 "external_client": _INBOUND_GROUP,
21 "rabbit": _MESSAGING_GROUP,
22 "kafka": _MESSAGING_GROUP,
23 "nats": _MESSAGING_GROUP,
24 "redis": _MESSAGING_GROUP,
25 "TaskQueue_Worker": _MESSAGING_GROUP,
26 "External_API": _OUTBOUND_GROUP,
27 "PersistentVolume": _DATA_STORES_GROUP,
28 "redisdb": _DATA_STORES_GROUP,
29 "redisdb_cluster0": _DATA_STORES_GROUP,
30 "redisdb_cluster1": _DATA_STORES_GROUP,
31 "redisdb_cluster2": _DATA_STORES_GROUP,
32 "redisdb_sentinel0": _DATA_STORES_GROUP,
33 "redisdb_sentinel1": _DATA_STORES_GROUP,
34 "redisdb_sentinel2": _DATA_STORES_GROUP,
35 "postgresql_asyncpgdb": _DATA_STORES_GROUP,
36 "postgresql_psycopgdb0": _DATA_STORES_GROUP,
37 "postgresql_psycopgdb1": _DATA_STORES_GROUP,
38 "postgresql_psycopgdb2": _DATA_STORES_GROUP,
39 "sqlite_aiosqlitedb": _DATA_STORES_GROUP,
40 "ConfigMap_app_config": _CONFIGURATION_GROUP,
41 "Secret_app_secrets": _CONFIGURATION_GROUP,
42 "ConfigMap_app_tuning": _CONFIGURATION_GROUP,
43 },
44)
45_PLACEMENT_ORDER_OF_MARKS: typing.Final = (
46 'subgraph group_configuration["Configuration"]',
47 'subgraph service_row[" "]',
48 "direction LR",
49 'subgraph group_inbound_api["Inbound API"]',
50 'external_client["User/Client"]',
51 'showcase_service{"',
52 'subgraph group_messaging_and_tasks["Messaging & tasks"]',
53 'subgraph group_outbound_calls["Outbound calls"]',
54 "style service_row fill:none,stroke:none",
55 'subgraph group_data_stores["Data stores"]',
56)
57_ALL_SERVICE_NAMES: typing.Final = (
58 ("payments-api", "payments_api"),
59 ("svc.v2", "svc_v2"),
60 ("Billing Service", "Billing_Service"),
61 # Only the separators are stripped off the ends: a name may open and close with a letter.
62 ("Xray-svc", "Xray_svc"),
63 ("!!!", "archdocs_service"),
64)
67@pytest.mark.parametrize(diagram_rendering.SETTINGS_ARGUMENT, diagram_rendering.ALL_EXAMPLE_SETTINGS)
68def test_arrows_agree_with_their_side(arch_settings: SettingsForArchdocs) -> None:
69 rendered_diagram: typing.Final = diagram_rendering.render_example_diagram(arch_settings)
71 group_of_node: typing.Final = diagram_parts.collect_group_of_every_node(rendered_diagram)
72 all_edge_ends: typing.Final = diagram_parts.collect_edge_ends(rendered_diagram)
73 all_target_groups: typing.Final = {group_of_node.get(one_ends.target_id) for one_ends in all_edge_ends}
74 all_source_groups: typing.Final = {group_of_node.get(one_ends.source_id) for one_ends in all_edge_ends}
75 assert all_edge_ends
76 assert _INBOUND_GROUP not in all_target_groups
77 assert _OUTBOUND_GROUP not in all_source_groups
80def test_showcase_nodes_sit_in_their_groups() -> None:
81 rendered_diagram: typing.Final = diagram_rendering.render_example_diagram(diagram_rendering.SHOWCASE_SETTINGS)
83 all_edge_lines: typing.Final = diagram_parts.extract_edge_lines(rendered_diagram)
84 assert diagram_parts.collect_group_of_every_node(rendered_diagram) == _EXPECTED_SHOWCASE_GROUPS
85 assert all_edge_lines
86 assert all(_SHOWCASE_NODE_ID in one_edge_line for one_edge_line in all_edge_lines)
89def test_groups_sit_around_the_service() -> None:
90 all_lines: typing.Final = [
91 one_line.strip()
92 for one_line in diagram_rendering.render_example_diagram(diagram_rendering.SHOWCASE_SETTINGS).split("\n")
93 ]
95 all_positions: typing.Final = [
96 next(line_index for line_index, one_line in enumerate(all_lines) if one_line.startswith(one_mark))
97 for one_mark in _PLACEMENT_ORDER_OF_MARKS
98 ]
99 assert all_positions == sorted(all_positions)
102@pytest.mark.parametrize(("service_name", "expected_node_id"), _ALL_SERVICE_NAMES)
103def test_service_name_becomes_the_node_id(service_name: str, expected_node_id: str) -> None:
104 rendered_diagram: typing.Final = diagram_rendering.render_example_diagram(
105 diagram_rendering.build_named_settings(service_name)
106 )
108 all_edge_lines: typing.Final = diagram_parts.extract_edge_lines(rendered_diagram)
109 assert f'{expected_node_id}{{"{service_name}"}}' in rendered_diagram
110 assert all_edge_lines
111 assert all(expected_node_id in one_edge_line for one_edge_line in all_edge_lines)
114def test_credentials_never_reach_the_diagram() -> None:
115 rendered_diagram: typing.Final = diagram_rendering.render_example_diagram(
116 diagram_rendering.build_named_settings("secretive-svc")
117 )
119 assert "user:password" not in rendered_diagram
120 assert "://***@" in rendered_diagram