Coverage for tests/test_diagram_layout.py: 100%

42 statements  

« prev     ^ index     » next       coverage.py v7.15.3, created at 2026-08-03 00:00 +0000

1import types 

2import typing 

3 

4import pytest 

5 

6from archdocs.main import SettingsForArchdocs 

7from tests.diagram_parts import collect_edge_ends, collect_group_of_every_node, extract_edge_lines 

8from tests.rendered_diagram import ( 

9 ALL_EXAMPLE_SETTINGS, 

10 SETTINGS_ARGUMENT, 

11 SHOWCASE_SETTINGS, 

12 build_named_settings, 

13 render_example_diagram, 

14) 

15 

16 

17# Mermaid has no coordinates: a diagram is laid out from the direction of its arrows and from 

18# the order its lines are written in, so both are part of the contract. 

19_INBOUND_GROUP: typing.Final = "Inbound API" 

20_OUTBOUND_GROUP: typing.Final = "Outbound calls" 

21_MESSAGING_GROUP: typing.Final = "Messaging & tasks" 

22_DATA_STORES_GROUP: typing.Final = "Data stores" 

23_CONFIGURATION_GROUP: typing.Final = "Configuration" 

24_SHOWCASE_NODE_ID: typing.Final = "showcase_service" 

25_EXPECTED_SHOWCASE_GROUPS: typing.Final = types.MappingProxyType( 

26 { 

27 "external_client": _INBOUND_GROUP, 

28 "rabbit": _MESSAGING_GROUP, 

29 "kafka": _MESSAGING_GROUP, 

30 "nats": _MESSAGING_GROUP, 

31 "redis": _MESSAGING_GROUP, 

32 "TaskQueue_Worker": _MESSAGING_GROUP, 

33 "External_API": _OUTBOUND_GROUP, 

34 "PersistentVolume": _DATA_STORES_GROUP, 

35 "redisdb": _DATA_STORES_GROUP, 

36 "redisdb_cluster0": _DATA_STORES_GROUP, 

37 "redisdb_cluster1": _DATA_STORES_GROUP, 

38 "redisdb_cluster2": _DATA_STORES_GROUP, 

39 "redisdb_sentinel0": _DATA_STORES_GROUP, 

40 "redisdb_sentinel1": _DATA_STORES_GROUP, 

41 "redisdb_sentinel2": _DATA_STORES_GROUP, 

42 "postgresql_asyncpgdb": _DATA_STORES_GROUP, 

43 "postgresql_psycopgdb0": _DATA_STORES_GROUP, 

44 "postgresql_psycopgdb1": _DATA_STORES_GROUP, 

45 "postgresql_psycopgdb2": _DATA_STORES_GROUP, 

46 "sqlite_aiosqlitedb": _DATA_STORES_GROUP, 

47 "ConfigMap_app_config": _CONFIGURATION_GROUP, 

48 "Secret_app_secrets": _CONFIGURATION_GROUP, 

49 "ConfigMap_app_tuning": _CONFIGURATION_GROUP, 

50 }, 

51) 

52_PLACEMENT_ORDER_OF_MARKS: typing.Final = ( 

53 'subgraph group_configuration["Configuration"]', 

54 'subgraph service_row[" "]', 

55 "direction LR", 

56 'subgraph group_inbound_api["Inbound API"]', 

57 'external_client["User/Client"]', 

58 'showcase_service{"', 

59 'subgraph group_messaging_and_tasks["Messaging & tasks"]', 

60 'subgraph group_outbound_calls["Outbound calls"]', 

61 "style service_row fill:none,stroke:none", 

62 'subgraph group_data_stores["Data stores"]', 

63) 

64_ALL_SERVICE_NAMES: typing.Final = ( 

65 ("payments-api", "payments_api"), 

66 ("svc.v2", "svc_v2"), 

67 ("Billing Service", "Billing_Service"), 

68 ("!!!", "archdocs_service"), 

69) 

70 

71 

72@pytest.mark.parametrize(SETTINGS_ARGUMENT, ALL_EXAMPLE_SETTINGS) 

73def test_arrows_agree_with_their_side(arch_settings: SettingsForArchdocs) -> None: 

74 rendered_diagram: typing.Final = render_example_diagram(arch_settings) 

75 

76 group_of_node: typing.Final = collect_group_of_every_node(rendered_diagram) 

77 all_edge_ends: typing.Final = collect_edge_ends(rendered_diagram) 

78 all_target_groups: typing.Final = {group_of_node.get(one_ends.target_id) for one_ends in all_edge_ends} 

79 all_source_groups: typing.Final = {group_of_node.get(one_ends.source_id) for one_ends in all_edge_ends} 

80 assert all_edge_ends 

81 assert _INBOUND_GROUP not in all_target_groups 

82 assert _OUTBOUND_GROUP not in all_source_groups 

83 

84 

85def test_showcase_nodes_sit_in_their_groups() -> None: 

86 rendered_diagram: typing.Final = render_example_diagram(SHOWCASE_SETTINGS) 

87 

88 assert collect_group_of_every_node(rendered_diagram) == _EXPECTED_SHOWCASE_GROUPS 

89 assert all(_SHOWCASE_NODE_ID in one_edge_line for one_edge_line in extract_edge_lines(rendered_diagram)) 

90 

91 

92def test_groups_sit_around_the_service() -> None: 

93 all_lines: typing.Final = [one_line.strip() for one_line in render_example_diagram(SHOWCASE_SETTINGS).split("\n")] 

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 = render_example_diagram(build_named_settings(service_name)) 

105 

106 assert f'{expected_node_id}{{"{service_name}"}}' in rendered_diagram 

107 assert all(expected_node_id in one_edge_line for one_edge_line in extract_edge_lines(rendered_diagram)) 

108 

109 

110def test_credentials_never_reach_the_diagram() -> None: 

111 rendered_diagram: typing.Final = render_example_diagram(build_named_settings("secretive-svc")) 

112 

113 assert "user:password" not in rendered_diagram 

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