cactus-test-definitions 1.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. cactus_test_definitions/__init__.py +39 -0
  2. cactus_test_definitions/__pycache__/__init__.cpython-312.pyc +0 -0
  3. cactus_test_definitions/__pycache__/actions.cpython-312.pyc +0 -0
  4. cactus_test_definitions/__pycache__/checks.cpython-312.pyc +0 -0
  5. cactus_test_definitions/__pycache__/csipaus.cpython-312.pyc +0 -0
  6. cactus_test_definitions/__pycache__/errors.cpython-312.pyc +0 -0
  7. cactus_test_definitions/__pycache__/events.cpython-312.pyc +0 -0
  8. cactus_test_definitions/__pycache__/parameters.cpython-312.pyc +0 -0
  9. cactus_test_definitions/__pycache__/schema.cpython-312.pyc +0 -0
  10. cactus_test_definitions/__pycache__/test_procedures.cpython-312-pytest-8.3.5.pyc +0 -0
  11. cactus_test_definitions/__pycache__/test_procedures.cpython-312.pyc +0 -0
  12. cactus_test_definitions/__pycache__/variable_expressions.cpython-312.pyc +0 -0
  13. cactus_test_definitions/__pycache__/version.cpython-312.pyc +0 -0
  14. cactus_test_definitions/client/__init__.py +30 -0
  15. cactus_test_definitions/client/__pycache__/__init__.cpython-312.pyc +0 -0
  16. cactus_test_definitions/client/__pycache__/actions.cpython-312.pyc +0 -0
  17. cactus_test_definitions/client/__pycache__/checks.cpython-312.pyc +0 -0
  18. cactus_test_definitions/client/__pycache__/events.cpython-312.pyc +0 -0
  19. cactus_test_definitions/client/__pycache__/test_procedures.cpython-312-pytest-8.3.5.pyc +0 -0
  20. cactus_test_definitions/client/__pycache__/validate.cpython-312.pyc +0 -0
  21. cactus_test_definitions/client/actions.py +99 -0
  22. cactus_test_definitions/client/checks.py +128 -0
  23. cactus_test_definitions/client/events.py +71 -0
  24. cactus_test_definitions/client/procedures/ALL-01.yaml +94 -0
  25. cactus_test_definitions/client/procedures/ALL-02.yaml +113 -0
  26. cactus_test_definitions/client/procedures/ALL-03-REJ.yaml +69 -0
  27. cactus_test_definitions/client/procedures/ALL-03.yaml +110 -0
  28. cactus_test_definitions/client/procedures/ALL-04.yaml +69 -0
  29. cactus_test_definitions/client/procedures/ALL-05.yaml +117 -0
  30. cactus_test_definitions/client/procedures/ALL-06.yaml +128 -0
  31. cactus_test_definitions/client/procedures/ALL-07.yaml +76 -0
  32. cactus_test_definitions/client/procedures/ALL-08.yaml +78 -0
  33. cactus_test_definitions/client/procedures/ALL-09.yaml +111 -0
  34. cactus_test_definitions/client/procedures/ALL-10.yaml +128 -0
  35. cactus_test_definitions/client/procedures/ALL-11.yaml +111 -0
  36. cactus_test_definitions/client/procedures/ALL-12.yaml +126 -0
  37. cactus_test_definitions/client/procedures/ALL-13.yaml +112 -0
  38. cactus_test_definitions/client/procedures/ALL-14.yaml +165 -0
  39. cactus_test_definitions/client/procedures/ALL-15.yaml +109 -0
  40. cactus_test_definitions/client/procedures/ALL-16.yaml +102 -0
  41. cactus_test_definitions/client/procedures/ALL-17.yaml +63 -0
  42. cactus_test_definitions/client/procedures/ALL-18.yaml +289 -0
  43. cactus_test_definitions/client/procedures/ALL-19.yaml +78 -0
  44. cactus_test_definitions/client/procedures/ALL-20.yaml +136 -0
  45. cactus_test_definitions/client/procedures/ALL-21.yaml +203 -0
  46. cactus_test_definitions/client/procedures/ALL-22.yaml +82 -0
  47. cactus_test_definitions/client/procedures/ALL-23.yaml +158 -0
  48. cactus_test_definitions/client/procedures/ALL-24.yaml +132 -0
  49. cactus_test_definitions/client/procedures/ALL-25-EXT.yaml +228 -0
  50. cactus_test_definitions/client/procedures/ALL-25.yaml +136 -0
  51. cactus_test_definitions/client/procedures/ALL-26.yaml +147 -0
  52. cactus_test_definitions/client/procedures/ALL-27.yaml +144 -0
  53. cactus_test_definitions/client/procedures/ALL-28.yaml +273 -0
  54. cactus_test_definitions/client/procedures/ALL-29.yaml +87 -0
  55. cactus_test_definitions/client/procedures/ALL-30.yaml +188 -0
  56. cactus_test_definitions/client/procedures/BES-01.yaml +136 -0
  57. cactus_test_definitions/client/procedures/BES-02.yaml +137 -0
  58. cactus_test_definitions/client/procedures/BES-03.yaml +135 -0
  59. cactus_test_definitions/client/procedures/BES-04.yaml +228 -0
  60. cactus_test_definitions/client/procedures/DRA-01.yaml +54 -0
  61. cactus_test_definitions/client/procedures/DRA-02.yaml +64 -0
  62. cactus_test_definitions/client/procedures/DRD-01.yaml +667 -0
  63. cactus_test_definitions/client/procedures/DRL-01.yaml +327 -0
  64. cactus_test_definitions/client/procedures/GEN-01.yaml +73 -0
  65. cactus_test_definitions/client/procedures/GEN-02.yaml +72 -0
  66. cactus_test_definitions/client/procedures/GEN-03.yaml +160 -0
  67. cactus_test_definitions/client/procedures/GEN-04.yaml +161 -0
  68. cactus_test_definitions/client/procedures/GEN-05.yaml +89 -0
  69. cactus_test_definitions/client/procedures/GEN-06.yaml +90 -0
  70. cactus_test_definitions/client/procedures/GEN-07.yaml +145 -0
  71. cactus_test_definitions/client/procedures/GEN-08.yaml +145 -0
  72. cactus_test_definitions/client/procedures/GEN-09.yaml +117 -0
  73. cactus_test_definitions/client/procedures/GEN-10.yaml +793 -0
  74. cactus_test_definitions/client/procedures/GEN-11.yaml +402 -0
  75. cactus_test_definitions/client/procedures/GEN-12.yaml +402 -0
  76. cactus_test_definitions/client/procedures/GEN-13.yaml +70 -0
  77. cactus_test_definitions/client/procedures/LOA-01.yaml +73 -0
  78. cactus_test_definitions/client/procedures/LOA-02.yaml +73 -0
  79. cactus_test_definitions/client/procedures/LOA-03.yaml +160 -0
  80. cactus_test_definitions/client/procedures/LOA-04.yaml +161 -0
  81. cactus_test_definitions/client/procedures/LOA-05.yaml +89 -0
  82. cactus_test_definitions/client/procedures/LOA-06.yaml +89 -0
  83. cactus_test_definitions/client/procedures/LOA-07.yaml +145 -0
  84. cactus_test_definitions/client/procedures/LOA-08.yaml +145 -0
  85. cactus_test_definitions/client/procedures/LOA-09.yaml +117 -0
  86. cactus_test_definitions/client/procedures/LOA-10.yaml +793 -0
  87. cactus_test_definitions/client/procedures/LOA-11.yaml +402 -0
  88. cactus_test_definitions/client/procedures/LOA-12.yaml +402 -0
  89. cactus_test_definitions/client/procedures/LOA-13.yaml +71 -0
  90. cactus_test_definitions/client/procedures/MUL-01.yaml +92 -0
  91. cactus_test_definitions/client/procedures/MUL-02.yaml +80 -0
  92. cactus_test_definitions/client/procedures/MUL-03.yaml +74 -0
  93. cactus_test_definitions/client/test_procedures.py +185 -0
  94. cactus_test_definitions/client/validate.py +98 -0
  95. cactus_test_definitions/csipaus.py +83 -0
  96. cactus_test_definitions/errors.py +15 -0
  97. cactus_test_definitions/parameters.py +153 -0
  98. cactus_test_definitions/py.typed +0 -0
  99. cactus_test_definitions/schema.py +22 -0
  100. cactus_test_definitions/server/README.md +172 -0
  101. cactus_test_definitions/server/__init__.py +27 -0
  102. cactus_test_definitions/server/__pycache__/__init__.cpython-312.pyc +0 -0
  103. cactus_test_definitions/server/__pycache__/actions.cpython-312.pyc +0 -0
  104. cactus_test_definitions/server/__pycache__/checks.cpython-312.pyc +0 -0
  105. cactus_test_definitions/server/__pycache__/test_procedures.cpython-312-pytest-8.3.5.pyc +0 -0
  106. cactus_test_definitions/server/__pycache__/validate.cpython-312.pyc +0 -0
  107. cactus_test_definitions/server/actions.py +140 -0
  108. cactus_test_definitions/server/checks.py +152 -0
  109. cactus_test_definitions/server/procedures/S-ALL-01.yaml +45 -0
  110. cactus_test_definitions/server/procedures/S-ALL-02.yaml +65 -0
  111. cactus_test_definitions/server/procedures/S-ALL-03.yaml +65 -0
  112. cactus_test_definitions/server/procedures/S-ALL-04.yaml +137 -0
  113. cactus_test_definitions/server/procedures/S-ALL-05.yaml +111 -0
  114. cactus_test_definitions/server/procedures/S-ALL-06.yaml +111 -0
  115. cactus_test_definitions/server/procedures/S-ALL-07.yaml +100 -0
  116. cactus_test_definitions/server/procedures/S-ALL-08.yaml +77 -0
  117. cactus_test_definitions/server/procedures/S-ALL-09.yaml +43 -0
  118. cactus_test_definitions/server/procedures/S-ALL-10.yaml +68 -0
  119. cactus_test_definitions/server/procedures/S-ALL-11.yaml +36 -0
  120. cactus_test_definitions/server/procedures/S-ALL-12.yaml +36 -0
  121. cactus_test_definitions/server/procedures/S-ALL-13.yaml +36 -0
  122. cactus_test_definitions/server/procedures/S-ALL-14.yaml +36 -0
  123. cactus_test_definitions/server/procedures/S-ALL-15.yaml +79 -0
  124. cactus_test_definitions/server/procedures/S-ALL-16.yaml +79 -0
  125. cactus_test_definitions/server/procedures/S-ALL-17.yaml +39 -0
  126. cactus_test_definitions/server/procedures/S-ALL-18.yaml +35 -0
  127. cactus_test_definitions/server/procedures/S-ALL-19.yaml +34 -0
  128. cactus_test_definitions/server/procedures/S-ALL-20.yaml +34 -0
  129. cactus_test_definitions/server/procedures/S-ALL-21.yaml +34 -0
  130. cactus_test_definitions/server/procedures/S-ALL-22.yaml +34 -0
  131. cactus_test_definitions/server/procedures/S-ALL-23.yaml +34 -0
  132. cactus_test_definitions/server/procedures/S-ALL-24.yaml +37 -0
  133. cactus_test_definitions/server/procedures/S-ALL-25.yaml +151 -0
  134. cactus_test_definitions/server/procedures/S-ALL-41.yaml +343 -0
  135. cactus_test_definitions/server/procedures/S-ALL-42.yaml +81 -0
  136. cactus_test_definitions/server/procedures/S-ALL-43.yaml +146 -0
  137. cactus_test_definitions/server/procedures/S-ALL-44.yaml +51 -0
  138. cactus_test_definitions/server/procedures/S-ALL-45.yaml +34 -0
  139. cactus_test_definitions/server/procedures/S-ALL-48.yaml +29 -0
  140. cactus_test_definitions/server/procedures/S-ALL-49.yaml +56 -0
  141. cactus_test_definitions/server/procedures/S-ALL-51.yaml +35 -0
  142. cactus_test_definitions/server/procedures/S-ALL-52.yaml +35 -0
  143. cactus_test_definitions/server/procedures/S-ALL-53.yaml +42 -0
  144. cactus_test_definitions/server/procedures/S-ALL-55.yaml +45 -0
  145. cactus_test_definitions/server/procedures/S-ALL-56.yaml +36 -0
  146. cactus_test_definitions/server/procedures/S-ALL-57.yaml +62 -0
  147. cactus_test_definitions/server/procedures/S-OPT-01.yaml +42 -0
  148. cactus_test_definitions/server/procedures/S-OPT-02.yaml +40 -0
  149. cactus_test_definitions/server/procedures/S-OPT-03.yaml +47 -0
  150. cactus_test_definitions/server/procedures/S-OPT-04.yaml +32 -0
  151. cactus_test_definitions/server/procedures/S-OPT-05.yaml +33 -0
  152. cactus_test_definitions/server/test_procedures.py +156 -0
  153. cactus_test_definitions/server/validate.py +30 -0
  154. cactus_test_definitions/variable_expressions.py +419 -0
  155. cactus_test_definitions-1.8.0.dist-info/METADATA +289 -0
  156. cactus_test_definitions-1.8.0.dist-info/RECORD +173 -0
  157. cactus_test_definitions-1.8.0.dist-info/WHEEL +5 -0
  158. cactus_test_definitions-1.8.0.dist-info/licenses/LICENSE.txt +22 -0
  159. cactus_test_definitions-1.8.0.dist-info/top_level.txt +2 -0
  160. tests/__init__.py +0 -0
  161. tests/unit/__init__.py +0 -0
  162. tests/unit/client/__init__.py +0 -0
  163. tests/unit/client/test_actions.py +72 -0
  164. tests/unit/client/test_checks.py +71 -0
  165. tests/unit/client/test_events.py +36 -0
  166. tests/unit/client/test_test_procedures.py +103 -0
  167. tests/unit/client/test_validate.py +200 -0
  168. tests/unit/server/__init__.py +0 -0
  169. tests/unit/server/test_test_procedures.py +60 -0
  170. tests/unit/server/test_validate.py +75 -0
  171. tests/unit/test_csipaus.py +49 -0
  172. tests/unit/test_parameters.py +197 -0
  173. tests/unit/test_variable_expressions.py +402 -0
@@ -0,0 +1,71 @@
1
+ import pytest
2
+ from cactus_test_definitions import variable_expressions as varexps
3
+ from cactus_test_definitions.client.checks import Check, validate_check_parameters
4
+ from cactus_test_definitions.errors import TestProcedureDefinitionError
5
+
6
+
7
+ @pytest.mark.parametrize(
8
+ "check, is_valid",
9
+ [
10
+ (Check("foo", {}), False), # Unknown check
11
+ (Check("readings-site-active-power", {}), True),
12
+ (Check("readings-site-active-power", {"minimum_count": "3"}), False),
13
+ (Check("readings-site-active-power", {"minimum_count": 3}), True),
14
+ (Check("readings-site-active-power", {"minimum_count": 3, "minimum_level": 12.3}), True),
15
+ (Check("readings-site-active-power", {"minimum_count": 3, "maximum_level": 12.3}), True),
16
+ (Check("readings-site-active-power", {"minimum_count": 3, "minimum_level": 12.3, "maximum_level": 12.3}), True),
17
+ (
18
+ Check(
19
+ "readings-site-active-power",
20
+ {"minimum_count": 3, "minimum_level": 12.3, "maximum_level": 12.3, "window_seconds": 12.3},
21
+ ),
22
+ False,
23
+ ),
24
+ (
25
+ Check(
26
+ "readings-site-active-power",
27
+ {"minimum_count": 3, "minimum_level": 12.3, "maximum_level": 12.3, "window_seconds": 180},
28
+ ),
29
+ True,
30
+ ),
31
+ (
32
+ Check(
33
+ "readings-site-active-power",
34
+ {"minimum_count": 3, "minimum_level": 12.3, "maximum_level": 12.3, "window_seconds": -180},
35
+ ),
36
+ False,
37
+ ),
38
+ (Check("der-settings-contents", {"setGradW": 27}), True),
39
+ (Check("der-settings-contents", {"doeModesEnabled_set": "0f"}), True),
40
+ (Check("der-settings-contents", {"doeModesEnabled_unset": "0f"}), True),
41
+ (Check("der-settings-contents", {"modesEnabled_set": "E"}), True),
42
+ (Check("der-settings-contents", {"modesEnabled_unset": "E"}), True),
43
+ (Check("der-settings-contents", {"doeModesEnabled_set": 12}), False),
44
+ (Check("der-settings-contents", {"doeModesEnabled_unset": 12}), False),
45
+ # Storage extension
46
+ (Check("der-settings-contents", {"vppModesEnabled_set": "1"}), True),
47
+ (Check("der-settings-contents", {"vppModesEnabled_unset": "1"}), True),
48
+ (Check("der-capability-contents", {"vppModesSupported_set": "1"}), True),
49
+ (Check("der-capability-contents", {"vppModesSupported_unset": "1"}), True),
50
+ (Check("readings-der-stored-energy", {"minimum_count": 3}), True),
51
+ ],
52
+ )
53
+ def test_validate_check_parameters(check: Check, is_valid: bool):
54
+ if is_valid:
55
+ validate_check_parameters("foo", check)
56
+ else:
57
+ with pytest.raises(TestProcedureDefinitionError):
58
+ validate_check_parameters("foo", check)
59
+
60
+
61
+ def test_check_expression() -> None:
62
+ """Tests the creation of a Check that has an expression as one of its parameters"""
63
+ type_str = "some_check"
64
+ params = {"setMaxW": "$(this < rtgMaxW)"}
65
+ check = Check(type_str, params)
66
+
67
+ check_set_max_w = check.parameters["setMaxW"]
68
+ assert isinstance(check_set_max_w, varexps.Expression)
69
+ assert check_set_max_w.operation == varexps.OperationType.LT
70
+ assert check_set_max_w.lhs_operand == varexps.NamedVariable(varexps.NamedVariableType.DERSETTING_SET_MAX_W)
71
+ assert check_set_max_w.rhs_operand == varexps.NamedVariable(varexps.NamedVariableType.DERCAPABILITY_RTG_MAX_W)
@@ -0,0 +1,36 @@
1
+ import pytest
2
+ from cactus_test_definitions import variable_expressions as varexps
3
+ from cactus_test_definitions.client.events import Event, validate_event_parameters
4
+ from cactus_test_definitions.errors import TestProcedureDefinitionError
5
+
6
+
7
+ @pytest.mark.parametrize(
8
+ "event, is_valid",
9
+ [
10
+ (Event("foo", {}), False), # Unknown check
11
+ (Event("wait", {}), False),
12
+ (Event("wait", {"duration_seconds": "3"}), False),
13
+ (Event("wait", {"duration_seconds": 3}), True),
14
+ (Event("wait", {"duration_seconds": 3, "other": 4}), False),
15
+ (Event("GET-request-received", {"endpoint": "/dcap"}), True),
16
+ ],
17
+ )
18
+ def test_validate_event_parameters(event: Event, is_valid: bool):
19
+ if is_valid:
20
+ validate_event_parameters("foo", "bar", event)
21
+ else:
22
+ with pytest.raises(TestProcedureDefinitionError):
23
+ validate_event_parameters("foo", "bar", event)
24
+
25
+
26
+ def test_event_expression() -> None:
27
+ """Tests the creation of an Event that has an expression as one of its parameters"""
28
+ type_str = "some_event"
29
+ params = {"setMaxW": "$(this < rtgMaxW)"}
30
+ event = Event(type_str, params)
31
+
32
+ check_set_max_w = event.parameters["setMaxW"]
33
+ assert isinstance(check_set_max_w, varexps.Expression)
34
+ assert check_set_max_w.operation == varexps.OperationType.LT
35
+ assert check_set_max_w.lhs_operand == varexps.NamedVariable(varexps.NamedVariableType.DERSETTING_SET_MAX_W)
36
+ assert check_set_max_w.rhs_operand == varexps.NamedVariable(varexps.NamedVariableType.DERCAPABILITY_RTG_MAX_W)
@@ -0,0 +1,103 @@
1
+ from datetime import datetime, timedelta, timezone
2
+ from importlib import resources
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+ from assertical.asserts.type import assert_dict_type
7
+ from cactus_test_definitions.client.test_procedures import (
8
+ TestProcedure,
9
+ TestProcedureId,
10
+ get_all_test_procedures,
11
+ parse_test_procedure,
12
+ )
13
+ from cactus_test_definitions.variable_expressions import (
14
+ Constant,
15
+ Expression,
16
+ NamedVariable,
17
+ NamedVariableType,
18
+ OperationType,
19
+ )
20
+ from dataclass_wizard.errors import UnknownKeysError
21
+
22
+
23
+ def test_TestProcedureId_synchronised():
24
+ """Ensures that TestProcedureId is in sync with all available TestProcedures YAML files.
25
+
26
+ Each MY-TEST.yaml must have a corresponding TestProcedureId.MY_TEST"""
27
+
28
+ # Discover all the YAML files
29
+ suffix = ".yaml"
30
+ raw_file_names: set[str] = set()
31
+ for yaml_file in resources.files("cactus_test_definitions.client.procedures").iterdir():
32
+ if yaml_file.is_file() and yaml_file.name.endswith(suffix):
33
+ raw_file_names.add(yaml_file.name[: -len(suffix)])
34
+
35
+ # Compare them against the TestProcedureId
36
+ for file_name in raw_file_names:
37
+ assert file_name in TestProcedureId
38
+ for tp_id in TestProcedureId:
39
+ assert tp_id in raw_file_names
40
+ assert len(raw_file_names) == len(TestProcedureId)
41
+
42
+
43
+ def test_available_tests_populated():
44
+ """Force test procedures to load and ensure they all validate (and we at least have a few)"""
45
+
46
+ all_tps = get_all_test_procedures()
47
+ assert_dict_type(TestProcedureId, TestProcedure, all_tps, count=len(TestProcedureId))
48
+ assert all_tps[TestProcedureId.ALL_01] != all_tps[TestProcedureId.ALL_02], "Sanity check on uniqueness"
49
+
50
+
51
+ def test_error_on_duplicate_key():
52
+ """Force test procedures to load and ensure they all validate (and we at least have a few)"""
53
+
54
+ with open(Path("tests/data/client/tp_error_duplicate_keys.yaml"), "r") as fp:
55
+ yaml_contents = fp.read()
56
+
57
+ with pytest.raises(ValueError):
58
+ parse_test_procedure(yaml_contents)
59
+
60
+
61
+ def test_error_on_extra_key():
62
+ """Force test procedures to load and ensure they all validate (and we at least have a few)"""
63
+
64
+ with open(Path("tests/data/client/tp_error_extra_keys.yaml"), "r") as fp:
65
+ yaml_contents = fp.read()
66
+
67
+ with pytest.raises(UnknownKeysError):
68
+ parse_test_procedure(yaml_contents)
69
+
70
+
71
+ def test_TestProcedures_action_parameter_types():
72
+ """Tests that lists/dicts/constants/datetimes can all be encoded/decoded via the yaml action definition"""
73
+
74
+ with open(Path("tests/data/client/tp_action_parameters.yaml"), "r") as fp:
75
+ tp = parse_test_procedure(fp.read())
76
+
77
+ step = tp.steps["Step-1"]
78
+ action = step.actions[0]
79
+
80
+ assert action.parameters == {
81
+ "param_list_str": ["List Item 1", "List Item 2", "List Item 3"],
82
+ "param_list_int": [11, 22, 33],
83
+ "param_list_mixed": [
84
+ 11,
85
+ 2.2,
86
+ "List Item 3",
87
+ datetime(2020, 1, 2, 3, 4, 5, tzinfo=timezone.utc),
88
+ ],
89
+ "param_dict_str": {"key1": "value1", "key2": "value2"},
90
+ "param_dict_int": {"key11": 11, "key22": 22},
91
+ "param_int": 11,
92
+ "param_str": "Value 11",
93
+ "param_datetime_utc": datetime(2025, 1, 2, 3, 4, 5, tzinfo=timezone.utc),
94
+ "param_datetime_naive": datetime(2025, 1, 2, 3, 4, 5, tzinfo=None),
95
+ "param_with_variable_date": NamedVariable(NamedVariableType.NOW),
96
+ "param_with_variable_db_lookup": NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W),
97
+ "param_with_variable_relative_db_lookup": Expression(
98
+ OperationType.MULTIPLY, Constant(0.25), NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W)
99
+ ),
100
+ "param_with_variable_relative_date": Expression(
101
+ OperationType.SUBTRACT, NamedVariable(NamedVariableType.NOW), Constant(timedelta(minutes=5))
102
+ ),
103
+ }
@@ -0,0 +1,200 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+ from cactus_test_definitions.client.test_procedures import (
5
+ TestProcedureId,
6
+ get_test_procedure,
7
+ parse_test_procedure,
8
+ )
9
+ from cactus_test_definitions.client.validate import validate_test_procedure
10
+ from cactus_test_definitions.errors import TestProcedureDefinitionError
11
+ from cactus_test_definitions.variable_expressions import (
12
+ NamedVariableType,
13
+ has_named_variable,
14
+ )
15
+
16
+
17
+ @pytest.mark.parametrize(
18
+ "tp_file",
19
+ [
20
+ Path("tests/data/client/tp_invalid_bad_param.yaml"),
21
+ Path("tests/data/client/tp_invalid_bad_step_enable.yaml"),
22
+ Path("tests/data/client/tp_invalid_bad_step_remove.yaml"),
23
+ ],
24
+ )
25
+ def test_TestProcedure_invalid_examples(tp_file: Path):
26
+ with open(tp_file, "r") as fp:
27
+ tp = parse_test_procedure(fp.read())
28
+
29
+ with pytest.raises(TestProcedureDefinitionError):
30
+ validate_test_procedure(tp, TestProcedureId.ALL_01)
31
+
32
+
33
+ @pytest.mark.parametrize("tp_id", TestProcedureId)
34
+ def test_TestProcedure_individually_valid(tp_id: TestProcedureId):
35
+ """Tests that each TestProcedureId can be loaded via get_test_procedure without issue AND that it
36
+ passes validate_test_procedure."""
37
+ tp = get_test_procedure(tp_id)
38
+ assert tp.steps, "There should be steps configured"
39
+ validate_test_procedure(tp, tp_id)
40
+
41
+
42
+ @pytest.mark.parametrize("tp_id", TestProcedureId)
43
+ def test_each_step_accessible(tp_id: TestProcedureId):
44
+ """Ensures that each test procedure's steps are enabled/removed once. Failures here indicate a bad reference to
45
+ enable/remove steps"""
46
+
47
+ tp = get_test_procedure(tp_id)
48
+
49
+ first_step = [k for k in tp.steps.keys()][0]
50
+ step_names = set(tp.steps.keys())
51
+
52
+ removal_count = dict([(s, 0) for s in step_names])
53
+ enabled_count = dict([(s, 0) for s in step_names])
54
+
55
+ # Tally up the number of times each step is added/removed via actions
56
+ for s in tp.steps.values():
57
+ for enabled_name in [name for a in s.actions if a.type == "enable-steps" for name in a.parameters["steps"]]:
58
+ enabled_count[enabled_name] = enabled_count[enabled_name] + 1
59
+ for removed_name in [name for a in s.actions if a.type == "remove-steps" for name in a.parameters["steps"]]:
60
+ removal_count[removed_name] = removal_count[removed_name] + 1
61
+
62
+ # Also check the preconditions for actions
63
+ if tp.preconditions and tp.preconditions.actions:
64
+ for enabled_name in [
65
+ name for a in tp.preconditions.actions if a.type == "enable-steps" for name in a.parameters["steps"]
66
+ ]:
67
+ enabled_count[enabled_name] = enabled_count[enabled_name] + 1
68
+ for removed_name in [
69
+ name for a in tp.preconditions.actions if a.type == "remove-steps" for name in a.parameters["steps"]
70
+ ]:
71
+ removal_count[removed_name] = removal_count[removed_name] + 1
72
+
73
+ # Each test should be added once and removed once
74
+ for step_name in step_names:
75
+ assert (
76
+ removal_count[step_name] == 1
77
+ ), f"{step_name}: Each test should be removed once otherwise the test cannot complete"
78
+
79
+ if step_name == first_step:
80
+ assert enabled_count[step_name] == 0, f"{step_name}: The first test step is already enabled"
81
+ else:
82
+ assert (
83
+ enabled_count[step_name] == 1
84
+ ), f"{step_name}: Each test should be added once - not expecting loops of steps"
85
+
86
+
87
+ @pytest.mark.parametrize("tp_id", TestProcedureId)
88
+ def test_each_step_connected(tp_id: TestProcedureId):
89
+ """Interprets each test a graph of steps linking to other steps (via enable-steps). Passes if the test procedure
90
+ is fully connected from the first step (i.e. there are no isolated islands of steps that can't be reached from
91
+ the first step)"""
92
+
93
+ tp = get_test_procedure(tp_id)
94
+
95
+ first_step = [k for k in tp.steps.keys()][0]
96
+ step_names = set(tp.steps.keys())
97
+
98
+ step_links_by_name: dict[str, list[str]] = {}
99
+ for step_name in step_names:
100
+ step = tp.steps[step_name]
101
+ child_step_names = [name for a in step.actions if a.type == "enable-steps" for name in a.parameters["steps"]]
102
+ step_links_by_name[step_name] = child_step_names
103
+
104
+ # Walk through our simple graph/tree - make sure we visit every node in that tree
105
+ def gather_connected_steps(links: dict[str, list[str]], node: str, visited_nodes: set[str]) -> None:
106
+ if node in visited_nodes:
107
+ return
108
+ visited_nodes.add(node)
109
+ for child in links[node]:
110
+ gather_connected_steps(links, child, visited_nodes)
111
+
112
+ visited_nodes: set[str] = set()
113
+ gather_connected_steps(step_links_by_name, first_step, visited_nodes)
114
+
115
+ # Also note that steps might be started as a precondition - so include those as visited too
116
+ if tp.preconditions and tp.preconditions.actions:
117
+ for n in [name for a in tp.preconditions.actions if a.type == "enable-steps" for name in a.parameters["steps"]]:
118
+ visited_nodes.add(n)
119
+
120
+ assert (
121
+ step_names == visited_nodes
122
+ ), "Missing entries here indicate a step (or steps) that can't be reached from the root node"
123
+
124
+
125
+ @pytest.mark.parametrize("tp_id", TestProcedureId)
126
+ def test_procedures_have_required_preconditions(tp_id: TestProcedureId):
127
+
128
+ tp = get_test_procedure(tp_id)
129
+
130
+ # Immediate start implies that EndDevice registration will happen during the test body - these tests
131
+ # don't require end-device-contents (as it will cause the test to fail anyway)
132
+ is_immediate_start = tp.preconditions is not None and tp.preconditions.immediate_start
133
+ if not is_immediate_start:
134
+ assert tp.preconditions is not None, "Expected precondition check 'end-device-contents'"
135
+ assert tp.preconditions.checks is not None, "Expected precondition check 'end-device-contents'"
136
+ assert any(
137
+ [check.type == "end-device-contents" for check in tp.preconditions.checks]
138
+ ), "Expected precondition check 'end-device-contents'"
139
+
140
+ # Check 'der-settings-contents' present if any precondition action parameter references setMaxW
141
+ if tp.preconditions is not None and tp.preconditions.actions is not None:
142
+ for action in tp.preconditions.actions:
143
+ if action.parameters is not None:
144
+ if any(
145
+ [
146
+ has_named_variable(
147
+ parameter_value=parameter, named_variable=NamedVariableType.DERSETTING_SET_MAX_W
148
+ )
149
+ for parameter in action.parameters.values()
150
+ ]
151
+ ):
152
+ assert tp.preconditions.checks is not None
153
+ assert any([check.type == "der-settings-contents" for check in tp.preconditions.checks])
154
+
155
+
156
+ @pytest.mark.parametrize("tp_id", TestProcedureId)
157
+ def test_response_subject_tags_have_corresponding_der_control_tags(tp_id: TestProcedureId):
158
+ """Ensures that every subject_tag in response-contents checks has a corresponding
159
+ tag defined in a create-der-control action in the same test procedure."""
160
+
161
+ tp = get_test_procedure(tp_id)
162
+
163
+ # Collect all tags defined in create-der-control actions
164
+ der_control_tags = set()
165
+
166
+ if tp.preconditions and tp.preconditions.actions:
167
+ der_control_tags.update(
168
+ [
169
+ a.parameters.get("tag")
170
+ for a in tp.preconditions.actions
171
+ if a.type == "create-der-control" and a.parameters and a.parameters.get("tag")
172
+ ]
173
+ )
174
+
175
+ der_control_tags.update(
176
+ [
177
+ a.parameters.get("tag")
178
+ for s in tp.steps.values()
179
+ for a in s.actions
180
+ if a.type == "create-der-control" and a.parameters and a.parameters.get("tag")
181
+ ]
182
+ )
183
+
184
+ # Collect all subject_tags referenced in response-contents checks
185
+ referenced_subject_tags = set(
186
+ [
187
+ c.parameters.get("subject_tag")
188
+ for s in tp.steps.values()
189
+ if s.event.checks
190
+ for c in s.event.checks
191
+ if c.type == "response-contents" and c.parameters and c.parameters.get("subject_tag")
192
+ ]
193
+ )
194
+
195
+ # Verify all referenced subject_tags have corresponding der-control tags
196
+ missing_tags = referenced_subject_tags - der_control_tags
197
+ assert not missing_tags, (
198
+ f"The following subject_tags in response-contents checks do not have corresponding "
199
+ f"tags in create-der-control actions: {missing_tags}"
200
+ )
File without changes
@@ -0,0 +1,60 @@
1
+ from importlib import resources
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+ from assertical.asserts.type import assert_dict_type
6
+ from cactus_test_definitions.server.test_procedures import (
7
+ TestProcedure,
8
+ TestProcedureId,
9
+ get_all_test_procedures,
10
+ parse_test_procedure,
11
+ )
12
+ from dataclass_wizard.errors import UnknownKeysError
13
+
14
+
15
+ def test_TestProcedureId_synchronised():
16
+ """Ensures that TestProcedureId is in sync with all available TestProcedures YAML files.
17
+
18
+ Each MY-TEST.yaml must have a corresponding TestProcedureId.MY_TEST"""
19
+
20
+ # Discover all the YAML files
21
+ suffix = ".yaml"
22
+ raw_file_names: set[str] = set()
23
+ for yaml_file in resources.files("cactus_test_definitions.server.procedures").iterdir():
24
+ if yaml_file.is_file() and yaml_file.name.endswith(suffix):
25
+ raw_file_names.add(yaml_file.name[: -len(suffix)])
26
+
27
+ # Compare them against the TestProcedureId
28
+ for file_name in raw_file_names:
29
+ assert file_name in TestProcedureId
30
+ for tp_id in TestProcedureId:
31
+ assert tp_id in raw_file_names
32
+ assert len(raw_file_names) == len(TestProcedureId)
33
+
34
+
35
+ def test_available_tests_populated():
36
+ """Force test procedures to load and ensure they all validate (and we at least have a few)"""
37
+
38
+ all_tps = get_all_test_procedures()
39
+ assert_dict_type(TestProcedureId, TestProcedure, all_tps, count=len(TestProcedureId))
40
+ assert all_tps[TestProcedureId.S_ALL_01] != all_tps[TestProcedureId.S_ALL_02], "Sanity check on uniqueness"
41
+
42
+
43
+ def test_error_on_duplicate_key():
44
+ """Force test procedures to load and ensure they all validate (and we at least have a few)"""
45
+
46
+ with open(Path("tests/data/server/tp_error_duplicate_keys.yaml"), "r") as fp:
47
+ yaml_contents = fp.read()
48
+
49
+ with pytest.raises(ValueError):
50
+ parse_test_procedure(yaml_contents)
51
+
52
+
53
+ def test_error_on_extra_key():
54
+ """Force test procedures to load and ensure they all validate (and we at least have a few)"""
55
+
56
+ with open(Path("tests/data/server/tp_error_extra_keys.yaml"), "r") as fp:
57
+ yaml_contents = fp.read()
58
+
59
+ with pytest.raises(UnknownKeysError):
60
+ parse_test_procedure(yaml_contents)
@@ -0,0 +1,75 @@
1
+ import pytest
2
+ from cactus_test_definitions.server.actions import ACTION_PARAMETER_SCHEMA
3
+ from cactus_test_definitions.server.test_procedures import (
4
+ TestProcedureId,
5
+ get_test_procedure,
6
+ )
7
+ from cactus_test_definitions.server.validate import validate_test_procedure
8
+
9
+
10
+ @pytest.mark.parametrize("tp_id", TestProcedureId)
11
+ def test_TestProcedure_individually_valid(tp_id: TestProcedureId):
12
+ """Tests that each TestProcedureId can be loaded via get_test_procedure without issue AND that it
13
+ passes validate_test_procedure."""
14
+ tp = get_test_procedure(tp_id)
15
+ assert tp.steps, "There should be steps configured"
16
+ validate_test_procedure(tp, tp_id)
17
+
18
+
19
+ @pytest.mark.parametrize("tp_id", TestProcedureId)
20
+ def test_each_step_id_unique(tp_id: TestProcedureId):
21
+ tp = get_test_procedure(tp_id)
22
+ all_ids = [s.id for s in tp.steps]
23
+ assert list(sorted(all_ids)) == list(sorted(set(all_ids)))
24
+ assert len(set((s.id for s in tp.steps))) == len(tp.steps), "All steps must have a unique id property"
25
+
26
+
27
+ @pytest.mark.parametrize("tp_id", TestProcedureId)
28
+ def test_each_alias_defined(tp_id: str):
29
+ """Ensures that each test procedure's steps that have actions using an alias... define those aliases in advance"""
30
+
31
+ tp = get_test_procedure(tp_id)
32
+
33
+ # sanity check - make sure we are looking for the action names that are valid
34
+ UPSERT_MUP_ACTION = "upsert-mup"
35
+ INSERT_READING_ACTION = "insert-readings"
36
+ CREATE_SUB_ACTION = "create-subscription"
37
+ DELETE_SUB_ACTION = "delete-subscription"
38
+ assert UPSERT_MUP_ACTION in ACTION_PARAMETER_SCHEMA, "If this fails - the action name has changed. Update this test"
39
+ assert (
40
+ INSERT_READING_ACTION in ACTION_PARAMETER_SCHEMA
41
+ ), "If this fails - the action name has changed. Update this test"
42
+ assert CREATE_SUB_ACTION in ACTION_PARAMETER_SCHEMA, "If this fails - the action name has changed. Update this test"
43
+ assert DELETE_SUB_ACTION in ACTION_PARAMETER_SCHEMA, "If this fails - the action name has changed. Update this test"
44
+
45
+ mup_aliases_found: set[str] = set()
46
+ sub_aliases_found: set[str] = set()
47
+ for step in tp.steps:
48
+ if step.action.parameters:
49
+ mup_id = step.action.parameters.get("mup_id", None)
50
+ sub_id = step.action.parameters.get("sub_id", None)
51
+ else:
52
+ mup_id = None
53
+ sub_id = None
54
+
55
+ if step.action.type == UPSERT_MUP_ACTION:
56
+ mup_aliases_found.add(mup_id)
57
+ elif step.action.type == INSERT_READING_ACTION:
58
+ assert mup_id and (mup_id in mup_aliases_found), "mup_id hasn't been defined yet."
59
+ elif step.action.type == CREATE_SUB_ACTION:
60
+ assert sub_id and (sub_id not in sub_aliases_found), "sub_id is already defined in this test."
61
+ sub_aliases_found.add(sub_id)
62
+ elif step.action.type == DELETE_SUB_ACTION:
63
+ assert sub_id and (sub_id in sub_aliases_found), "sub_id hasn't been defined yet."
64
+
65
+ # Any instance of "sub_id" / "mup_id" in a check must have ALSO been defined in an earlier action
66
+ for check in step.checks or []:
67
+ sub_id = check.parameters.get("sub_id", None)
68
+ if sub_id:
69
+ # This will match
70
+ assert sub_id and (sub_id in sub_aliases_found), "sub_id hasn't been defined yet."
71
+
72
+ mup_id = check.parameters.get("mup_id", None)
73
+ if mup_id:
74
+ # This will match
75
+ assert mup_id and (mup_id in mup_aliases_found), "mup_id hasn't been defined yet."
@@ -0,0 +1,49 @@
1
+ import pytest
2
+ from cactus_test_definitions.csipaus import (
3
+ CSIPAusReadingLocation,
4
+ CSIPAusReadingType,
5
+ CSIPAusResource,
6
+ is_list_resource,
7
+ )
8
+
9
+
10
+ @pytest.mark.parametrize("enum_val", CSIPAusResource)
11
+ def test_CSIPAusResource_name_matches_value(enum_val: CSIPAusResource):
12
+ """We want case varying enum values (to match the values in CSIP Aus). This means we need to manage the names
13
+ manually and ensures we don't end up with a bad copy paste"""
14
+ assert enum_val.value == enum_val.name
15
+
16
+
17
+ @pytest.mark.parametrize("enum_val", CSIPAusReadingLocation)
18
+ def test_CSIPAusReadingLocation_name_matches_value(enum_val: CSIPAusReadingLocation):
19
+ """We want case varying enum values (to match the values in CSIP Aus). This means we need to manage the names
20
+ manually and ensures we don't end up with a bad copy paste"""
21
+ assert enum_val.value == enum_val.name
22
+
23
+
24
+ @pytest.mark.parametrize("enum_val", CSIPAusReadingType)
25
+ def test_CSIPAusReadingType_name_matches_value(enum_val: CSIPAusReadingType):
26
+ """We want case varying enum values (to match the values in CSIP Aus). This means we need to manage the names
27
+ manually and ensures we don't end up with a bad copy paste"""
28
+ assert enum_val.value == enum_val.name
29
+
30
+
31
+ @pytest.mark.parametrize(
32
+ "resource, expected",
33
+ [
34
+ (CSIPAusResource.ConnectionPoint, False),
35
+ (CSIPAusResource.DeviceCapability, False),
36
+ (CSIPAusResource.DERSettings, False),
37
+ (CSIPAusResource.EndDeviceList, True),
38
+ (CSIPAusResource.EndDevice, False),
39
+ (CSIPAusResource.DERList, True),
40
+ (CSIPAusResource.DERProgramList, True),
41
+ (CSIPAusResource.MirrorUsagePointList, True),
42
+ (CSIPAusResource.DERControlList, True),
43
+ (CSIPAusResource.DERControl, False),
44
+ ],
45
+ )
46
+ def test_is_list_resource(resource: CSIPAusResource, expected: bool):
47
+ actual = is_list_resource(resource)
48
+ assert isinstance(actual, bool)
49
+ assert actual is expected