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

1import types 

2import typing 

3 

4import pytest 

5 

6from archdocs.main import SettingsForArchdocs 

7from tests import diagram_parts, diagram_rendering 

8 

9 

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) 

65 

66 

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) 

70 

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 

78 

79 

80def test_showcase_nodes_sit_in_their_groups() -> None: 

81 rendered_diagram: typing.Final = diagram_rendering.render_example_diagram(diagram_rendering.SHOWCASE_SETTINGS) 

82 

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) 

87 

88 

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 ] 

94 

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) 

100 

101 

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 ) 

107 

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) 

112 

113 

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 ) 

118 

119 assert "user:password" not in rendered_diagram 

120 assert "://***@" in rendered_diagram