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,197 @@
1
+ from datetime import datetime, timezone
2
+ from decimal import Decimal
3
+ from itertools import product
4
+ from typing import Any
5
+
6
+ import pytest
7
+ from cactus_test_definitions.errors import TestProcedureDefinitionError
8
+ from cactus_test_definitions.parameters import (
9
+ ParameterSchema,
10
+ ParameterType,
11
+ is_valid_parameter_type,
12
+ validate_parameters,
13
+ )
14
+ from cactus_test_definitions.variable_expressions import (
15
+ Constant,
16
+ Expression,
17
+ NamedVariable,
18
+ NamedVariableType,
19
+ OperationType,
20
+ )
21
+
22
+
23
+ @pytest.mark.parametrize(
24
+ "value, type",
25
+ product(
26
+ [
27
+ None,
28
+ Expression(OperationType.ADD, Constant(1), Constant(2)),
29
+ Constant(1),
30
+ NamedVariable(NamedVariableType.NOW),
31
+ ],
32
+ list(ParameterType),
33
+ ),
34
+ )
35
+ def test_is_valid_parameter_type_skipped_values(value: Any, type: ParameterType):
36
+ """Tests that a number of values aren't considered and always return True"""
37
+ actual = is_valid_parameter_type(type, value)
38
+ assert isinstance(actual, bool)
39
+ assert actual
40
+
41
+
42
+ @pytest.mark.parametrize(
43
+ "type, value, expected",
44
+ [
45
+ (ParameterType.Boolean, True, True),
46
+ (ParameterType.Boolean, False, True),
47
+ (ParameterType.Boolean, 1, False),
48
+ (ParameterType.Boolean, [1], False),
49
+ (ParameterType.Boolean, "True", False),
50
+ (ParameterType.Float, 0, True),
51
+ (ParameterType.Float, 0.0, True),
52
+ (ParameterType.Float, 1, True),
53
+ (ParameterType.Float, 1.1, True),
54
+ (ParameterType.Float, Decimal("1.1"), True),
55
+ (ParameterType.Float, "1.1", False),
56
+ (ParameterType.Float, [1.1], False),
57
+ (ParameterType.Integer, 0, True),
58
+ (ParameterType.Integer, 0.0, True),
59
+ (ParameterType.Integer, 2, True),
60
+ (ParameterType.Integer, 2.0, True),
61
+ (ParameterType.Integer, Decimal("2.0"), True),
62
+ (ParameterType.Integer, Decimal("2.1"), False),
63
+ (ParameterType.Integer, 2.1, False),
64
+ (ParameterType.Integer, [2], False),
65
+ (ParameterType.Integer, "2", False),
66
+ (ParameterType.DateTime, datetime(2022, 11, 1, 1, 2, 3), True),
67
+ (ParameterType.DateTime, datetime(2022, 11, 1, 1, 2, 3, tzinfo=timezone.utc), True),
68
+ (ParameterType.DateTime, "2022-11-01T01:02:03Z", False),
69
+ (ParameterType.DateTime, "2022-11-01T01:02:03", False),
70
+ (ParameterType.DateTime, 2022, False),
71
+ (ParameterType.DateTime, [], False),
72
+ (ParameterType.String, "", True),
73
+ (ParameterType.String, "abc", True),
74
+ (ParameterType.String, "12", True),
75
+ (ParameterType.String, 12, False),
76
+ (ParameterType.String, ["a"], False),
77
+ (ParameterType.ListString, [], True),
78
+ (ParameterType.ListString, [""], True),
79
+ (ParameterType.ListString, ["", "b"], True),
80
+ (ParameterType.ListString, ["", 4, "b"], False),
81
+ (ParameterType.ListString, False, False),
82
+ (ParameterType.ListString, True, False),
83
+ (ParameterType.ListString, 123, False),
84
+ (ParameterType.HexBinary, "AB", True),
85
+ (ParameterType.HexBinary, "XY", False),
86
+ (ParameterType.HexBinary, 12, False),
87
+ (ParameterType.HexBinary, ["AB"], False),
88
+ (ParameterType.HexBinary, 0.0, False),
89
+ (ParameterType.HexBinary, [12], False),
90
+ (ParameterType.HexBinary, 1.2, False),
91
+ (ParameterType.HexBinary, Decimal("1.0"), False),
92
+ (ParameterType.HexBinary, True, False),
93
+ (ParameterType.CSIPAusResource, True, False),
94
+ (ParameterType.CSIPAusResource, 123, False),
95
+ (ParameterType.CSIPAusResource, "NotAResourceType", False),
96
+ (ParameterType.CSIPAusResource, "ActivePowerInstantaneous", False),
97
+ (ParameterType.CSIPAusResource, "EndDevice", True),
98
+ (ParameterType.CSIPAusResource, ["EndDevice"], False),
99
+ (ParameterType.ListCSIPAusResource, True, False),
100
+ (ParameterType.ListCSIPAusResource, 123, False),
101
+ (ParameterType.ListCSIPAusResource, "NotAResourceType", False),
102
+ (ParameterType.ListCSIPAusResource, "EndDevice", False),
103
+ (ParameterType.ListCSIPAusResource, ["EndDevice"], True),
104
+ (ParameterType.ListCSIPAusResource, ["EndDevice", "NotAResource"], False),
105
+ (ParameterType.ListCSIPAusResource, ["ActivePowerInstantaneous"], False),
106
+ (ParameterType.CSIPAusReadingType, True, False),
107
+ (ParameterType.CSIPAusReadingType, 123, False),
108
+ (ParameterType.CSIPAusReadingType, "NotAResourceType", False),
109
+ (ParameterType.CSIPAusReadingType, "EndDevice", False),
110
+ (ParameterType.CSIPAusReadingType, "ActivePowerInstantaneous", True),
111
+ (ParameterType.CSIPAusReadingType, ["ActivePowerSiteInstantaneous"], False),
112
+ (ParameterType.ListCSIPAusReadingType, True, False),
113
+ (ParameterType.ListCSIPAusReadingType, 123, False),
114
+ (ParameterType.ListCSIPAusReadingType, "NotAResourceType", False),
115
+ (ParameterType.ListCSIPAusReadingType, "EndDevice", False),
116
+ (ParameterType.ListCSIPAusReadingType, "ActivePowerInstantaneous", False),
117
+ (ParameterType.ListCSIPAusReadingType, ["ActivePowerInstantaneous"], True),
118
+ (ParameterType.ListCSIPAusReadingType, ["ActivePowerInstantaneous", "NotAResource"], False),
119
+ (ParameterType.ListCSIPAusReadingType, ["EndDevice"], False),
120
+ (ParameterType.CSIPAusReadingLocation, True, False),
121
+ (ParameterType.CSIPAusReadingLocation, 123, False),
122
+ (ParameterType.CSIPAusReadingLocation, "NotAResourceType", False),
123
+ (ParameterType.CSIPAusReadingLocation, "EndDevice", False),
124
+ (ParameterType.CSIPAusReadingLocation, "Site", True),
125
+ (ParameterType.CSIPAusReadingLocation, ["Site"], False),
126
+ (ParameterType.ReadingTypeValues, 123, False),
127
+ (ParameterType.ReadingTypeValues, "ActivePowerInstantaneous", False),
128
+ (ParameterType.ReadingTypeValues, [123], False),
129
+ (ParameterType.ReadingTypeValues, {}, False),
130
+ (
131
+ ParameterType.ReadingTypeValues,
132
+ {"ActivePowerInstantaneous": [100, 1.23], "ActivePowerAverage": [200, -300]},
133
+ True,
134
+ ),
135
+ (
136
+ ParameterType.ReadingTypeValues,
137
+ {"ActivePowerInstantaneous": [100, 200], "ActivePowerAverage": [200, 300, 400]},
138
+ False,
139
+ ),
140
+ (
141
+ ParameterType.ReadingTypeValues,
142
+ {"ActivePowerInstantaneous": [100, 200], "ActivePowerAverage": [200, 300], "Foo": [100, 200]},
143
+ False,
144
+ ),
145
+ (
146
+ ParameterType.ReadingTypeValues,
147
+ {"ActivePowerInstantaneous": [100, 200], "ActivePowerAverage": [200, "1.23"]},
148
+ False,
149
+ ),
150
+ (
151
+ ParameterType.ReadingTypeValues,
152
+ {"ActivePowerInstantaneous": 100, "ActivePowerAverage": 200},
153
+ False,
154
+ ),
155
+ ],
156
+ )
157
+ def test_is_valid_parameter_type(type: ParameterType, value: Any, expected: bool):
158
+ actual = is_valid_parameter_type(type, value)
159
+ assert isinstance(actual, bool)
160
+ assert actual == expected
161
+
162
+
163
+ @pytest.mark.parametrize(
164
+ "parameters, schema, is_valid",
165
+ [
166
+ ({}, {}, True),
167
+ ({"foo": 1}, {}, False),
168
+ ({"foo": 1}, {"foo": ParameterSchema(True, ParameterType.Integer)}, True),
169
+ ({"foo": 1}, {"foo": ParameterSchema(True, ParameterType.Float)}, True),
170
+ ({"foo": 1}, {"foo": ParameterSchema(True, ParameterType.String)}, False), # wrong type
171
+ (
172
+ {"foo": 1},
173
+ {"foo": ParameterSchema(True, ParameterType.Integer), "bar": ParameterSchema(False, ParameterType.String)},
174
+ True,
175
+ ), # Optional param is OK to miss
176
+ (
177
+ {"foo": 1},
178
+ {"foo": ParameterSchema(True, ParameterType.Integer), "bar": ParameterSchema(True, ParameterType.String)},
179
+ False,
180
+ ), # Missing mandatory param
181
+ (
182
+ {"foo": None, "bar": "2", "baz": 4},
183
+ {
184
+ "foo": ParameterSchema(True, ParameterType.Integer),
185
+ "bar": ParameterSchema(True, ParameterType.String),
186
+ "baz": ParameterSchema(False, ParameterType.Float),
187
+ },
188
+ True,
189
+ ),
190
+ ],
191
+ )
192
+ def test_validate_action_parameters(parameters: dict, schema: dict, is_valid: bool):
193
+ if is_valid:
194
+ validate_parameters("foo", parameters, schema)
195
+ else:
196
+ with pytest.raises(TestProcedureDefinitionError):
197
+ validate_parameters("foo", parameters, schema)
@@ -0,0 +1,402 @@
1
+ from datetime import datetime, timedelta
2
+ from decimal import Decimal
3
+ from typing import Any
4
+
5
+ import pytest
6
+ from cactus_test_definitions.client.test_procedures import Action
7
+ from cactus_test_definitions.variable_expressions import (
8
+ Constant,
9
+ Expression,
10
+ NamedVariable,
11
+ NamedVariableType,
12
+ OperationType,
13
+ UnparseableVariableExpressionError,
14
+ has_named_variable,
15
+ is_resolvable_variable,
16
+ parse_time_delta,
17
+ parse_variable_expression_body,
18
+ try_extract_variable_expression,
19
+ snake_to_camel,
20
+ named_variable_repr,
21
+ operation_repr,
22
+ BaseExpression,
23
+ )
24
+
25
+
26
+ @pytest.mark.parametrize(
27
+ "body, expected",
28
+ [
29
+ (None, None),
30
+ (123, None),
31
+ (12.3, None),
32
+ (["$no_list_inspection"], None),
33
+ ({"$no_dict_inspection": "$no_dict_inspection"}, None),
34
+ (datetime(2001, 2, 3), None),
35
+ ("", None),
36
+ (" ", None),
37
+ ("$now", "now"),
38
+ (" $now\n ", "now"),
39
+ ("$(now)", "now"),
40
+ ("$(now - '5 minutes')", "now - '5 minutes'"),
41
+ ("$( now - '5 minutes' )", " now - '5 minutes' "),
42
+ ("\\$(now)", None), # escaped
43
+ (" \\$(now) ", None), # escaped
44
+ (" \\$now ", None), # escaped
45
+ ("\\$now", None), # escaped
46
+ ("$variable_with_556", "variable_with_556"),
47
+ ("$(variable_with_556)", "variable_with_556"),
48
+ ("$variable-556", ValueError), # Don't allow "non variable" data to get omitted
49
+ ("$(variable-556)", "variable-556"),
50
+ ("$longer_variable no spaces", ValueError),
51
+ ("$(longer_variable but include everything)", "longer_variable but include everything"),
52
+ ("$(now", ValueError), # Unclosed bracket
53
+ (" $(now ", ValueError), # Unclosed bracket
54
+ (" $(now foo", ValueError), # Unclosed bracket
55
+ ("$ invalid_space", ValueError),
56
+ ("$-invalid_char", ValueError),
57
+ ("$", ValueError), # No variable body included
58
+ ("$ ", ValueError), # No variable body included
59
+ ("\\$", None),
60
+ ("$()", ValueError), # No variable body included
61
+ ("\\$()", None),
62
+ (" \\$() ", None),
63
+ ("$(valid) no trailing data", ValueError), # A string is either a variable or not. Can't substring variables
64
+ ("no leading data $(valid)", ValueError), # A string is either a variable or not. Can't substring variables
65
+ ],
66
+ )
67
+ def test_try_extract_variable_expression(body: Any, expected: str | None | type[Exception]):
68
+ """Various test cases that try expose edge cases in try_extract_variable_expression"""
69
+ if isinstance(expected, type):
70
+ with pytest.raises(expected):
71
+ try_extract_variable_expression(body)
72
+ else:
73
+ actual = try_extract_variable_expression(body)
74
+ assert actual is None or isinstance(actual, str)
75
+ assert actual == expected, f"Input string: '{body}'"
76
+
77
+
78
+ @pytest.mark.parametrize(
79
+ "unquoted_raw, expected",
80
+ [
81
+ ("", UnparseableVariableExpressionError),
82
+ ("5 mins", timedelta(minutes=5)),
83
+ ("-5 mins", timedelta(minutes=-5)),
84
+ ("5.23 mins", timedelta(minutes=5.23)),
85
+ ("-5.23 mins", timedelta(minutes=-5.23)),
86
+ ("-0.03 days", timedelta(days=-0.03)),
87
+ ("-0.03 days", timedelta(days=-0.03)),
88
+ ("-0.03days", timedelta(days=-0.03)),
89
+ ("4 minutes", timedelta(minutes=4)),
90
+ ("4 minute", timedelta(minutes=4)),
91
+ ("0 minute", timedelta(minutes=0)),
92
+ ("4 hours", timedelta(hours=4)),
93
+ ("4 hour", timedelta(hours=4)),
94
+ ("4 hrs", timedelta(hours=4)),
95
+ ("4 hr", timedelta(hours=4)),
96
+ ("4 days", timedelta(days=4)),
97
+ ("4 day", timedelta(days=4)),
98
+ ("4 seconds", timedelta(seconds=4)),
99
+ ("4 second", timedelta(seconds=4)),
100
+ ("4 secs", timedelta(seconds=4)),
101
+ ("4 sec", timedelta(seconds=4)),
102
+ ("4 foo", UnparseableVariableExpressionError), # Unknown unit
103
+ ("4-.2 foo", UnparseableVariableExpressionError), # Not a number
104
+ ("4.2.3 foo", UnparseableVariableExpressionError), # Not a number
105
+ ],
106
+ )
107
+ def test_parse_time_delta(unquoted_raw: str, expected: timedelta | type[Exception]):
108
+ """Tests the various ways a time delta interval can be encoded (and the various ways it can fail)"""
109
+ VALID_QUOTE_CHARS = ["'", '"']
110
+
111
+ # Check weird edge cases from unquoted intervals or badly quoted intervals
112
+ with pytest.raises(UnparseableVariableExpressionError):
113
+ parse_time_delta(unquoted_raw)
114
+
115
+ for quote_char in ["'", '"']:
116
+ with pytest.raises(UnparseableVariableExpressionError):
117
+ parse_time_delta(quote_char + unquoted_raw)
118
+ with pytest.raises(UnparseableVariableExpressionError):
119
+ parse_time_delta(unquoted_raw + quote_char)
120
+
121
+ with pytest.raises(UnparseableVariableExpressionError):
122
+ parse_time_delta(VALID_QUOTE_CHARS[0] + quote_char + VALID_QUOTE_CHARS[1])
123
+ with pytest.raises(UnparseableVariableExpressionError):
124
+ parse_time_delta(VALID_QUOTE_CHARS[1] + quote_char + VALID_QUOTE_CHARS[0])
125
+
126
+ # Now do the actual test
127
+ if isinstance(expected, type):
128
+ for quote_char in VALID_QUOTE_CHARS:
129
+ with pytest.raises(expected):
130
+ parse_time_delta(quote_char + unquoted_raw + quote_char)
131
+ else:
132
+ for quote_char in VALID_QUOTE_CHARS:
133
+ actual = parse_time_delta(quote_char + unquoted_raw + quote_char)
134
+ assert isinstance(actual, timedelta)
135
+ assert actual == expected, f"Input string: {quote_char + unquoted_raw + quote_char}"
136
+
137
+
138
+ @pytest.mark.parametrize(
139
+ "var_body, expected",
140
+ [
141
+ ("1.23", Constant(1.23)),
142
+ ("123", Constant(123)),
143
+ (" 123 ", Constant(123)),
144
+ ("'-4.56 hours'", Constant(timedelta(hours=-4.56))),
145
+ ('"0.12 days"', Constant(timedelta(days=0.12))),
146
+ (" \t '-4.56 hours' \t ", Constant(timedelta(hours=-4.56))),
147
+ ("now", NamedVariable(NamedVariableType.NOW)),
148
+ ("NOW", UnparseableVariableExpressionError), # case sensitive
149
+ ("setMaxW", NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W)),
150
+ ("SETMAXW", UnparseableVariableExpressionError), # case sensitive
151
+ ("foo", UnparseableVariableExpressionError), # unknown named variable
152
+ (
153
+ "0.5 * 0.2",
154
+ Expression(OperationType.MULTIPLY, Constant(0.5), Constant(0.2)),
155
+ ),
156
+ (
157
+ "0.5 * setMaxW",
158
+ Expression(OperationType.MULTIPLY, Constant(0.5), NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W)),
159
+ ),
160
+ (
161
+ "setMaxW / 2",
162
+ Expression(OperationType.DIVIDE, NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W), Constant(2)),
163
+ ),
164
+ (
165
+ " now - '-12 minutes' ",
166
+ Expression(OperationType.SUBTRACT, NamedVariable(NamedVariableType.NOW), Constant(timedelta(minutes=-12))),
167
+ ),
168
+ (
169
+ "now-'-12minutes'",
170
+ Expression(OperationType.SUBTRACT, NamedVariable(NamedVariableType.NOW), Constant(timedelta(minutes=-12))),
171
+ ),
172
+ (
173
+ 'now + "3 day"',
174
+ Expression(OperationType.ADD, NamedVariable(NamedVariableType.NOW), Constant(timedelta(days=3))),
175
+ ),
176
+ (
177
+ '"3 day" < "5 day"',
178
+ Expression(OperationType.LT, Constant(timedelta(days=3)), Constant(timedelta(days=5))),
179
+ ),
180
+ (
181
+ "rtgMaxW / 2",
182
+ Expression(OperationType.DIVIDE, NamedVariable(NamedVariableType.DERCAPABILITY_RTG_MAX_W), Constant(2)),
183
+ ),
184
+ ("setMaxVA", NamedVariable(NamedVariableType.DERSETTING_SET_MAX_VA)),
185
+ ("setMaxVar", NamedVariable(NamedVariableType.DERSETTING_SET_MAX_VAR)),
186
+ ("setMaxChargeRateW", NamedVariable(NamedVariableType.DERSETTING_SET_MAX_CHARGE_RATE_W)),
187
+ ("setMaxDischargeRateW", NamedVariable(NamedVariableType.DERSETTING_SET_MAX_DISCHARGE_RATE_W)),
188
+ ("setMaxWh", NamedVariable(NamedVariableType.DERSETTING_SET_MAX_WH)),
189
+ (
190
+ "rtgMaxVar == 5.0",
191
+ Expression(OperationType.EQ, NamedVariable(NamedVariableType.DERCAPABILITY_RTG_MAX_VAR), Constant(5.0)),
192
+ ),
193
+ (
194
+ "rtgMaxW != 0.5",
195
+ Expression(OperationType.NE, NamedVariable(NamedVariableType.DERCAPABILITY_RTG_MAX_W), Constant(0.5)),
196
+ ),
197
+ (
198
+ "rtgMaxChargeRateW <= 0.5",
199
+ Expression(
200
+ OperationType.LTE, NamedVariable(NamedVariableType.DERCAPABILITY_RTG_MAX_CHARGE_RATE_W), Constant(0.5)
201
+ ),
202
+ ),
203
+ (
204
+ "rtgMaxDischargeRateW > 0.5",
205
+ Expression(
206
+ OperationType.GT, NamedVariable(NamedVariableType.DERCAPABILITY_RTG_MAX_DISCHARGE_RATE_W), Constant(0.5)
207
+ ),
208
+ ),
209
+ (
210
+ "rtgMaxWh >= 0.5",
211
+ Expression(OperationType.GTE, NamedVariable(NamedVariableType.DERCAPABILITY_RTG_MAX_WH), Constant(0.5)),
212
+ ),
213
+ ("now + foo", UnparseableVariableExpressionError),
214
+ ("now foo +", UnparseableVariableExpressionError),
215
+ ("now foo ", UnparseableVariableExpressionError),
216
+ ("7 + + 8 ", UnparseableVariableExpressionError),
217
+ ("'5 mins + now ", UnparseableVariableExpressionError), # Unterminated string literal
218
+ ("now + '5 mins", UnparseableVariableExpressionError), # Unterminated string literal
219
+ # Storage extension
220
+ (
221
+ "setMinWh >= 0.5",
222
+ Expression(OperationType.GTE, NamedVariable(NamedVariableType.DERSETTING_SET_MIN_WH), Constant(0.5)),
223
+ ),
224
+ (
225
+ "negRtgMaxChargeRateW + 50",
226
+ Expression(
227
+ OperationType.ADD,
228
+ NamedVariable(NamedVariableType.DERCAPABILITY_NEG_RTG_MAX_CHARGE_RATE_W),
229
+ Constant(50),
230
+ ),
231
+ ),
232
+ ],
233
+ )
234
+ def test_parse_variable_expression_body(
235
+ var_body: str, expected: type[Exception] | NamedVariable | Constant | Expression
236
+ ):
237
+ """Top level parsing test to ensure that a variety of variable bodies parse (or fail) in an expected fashion"""
238
+
239
+ if isinstance(expected, type):
240
+ with pytest.raises(expected):
241
+ parse_variable_expression_body(var_body, None)
242
+ else:
243
+ actual = parse_variable_expression_body(var_body, None)
244
+ assert isinstance(actual, NamedVariable) or isinstance(actual, Constant) or isinstance(actual, Expression)
245
+ assert actual == expected, f"Input string: {var_body}"
246
+
247
+
248
+ @pytest.mark.parametrize(
249
+ "var_body,expected,param_key",
250
+ [
251
+ (
252
+ "this < rtgMaxVA",
253
+ Expression(
254
+ OperationType.LT,
255
+ NamedVariable(NamedVariableType.DERSETTING_SET_MAX_VA),
256
+ NamedVariable(NamedVariableType.DERCAPABILITY_RTG_MAX_VA),
257
+ ),
258
+ "setMaxVA",
259
+ ),
260
+ (
261
+ "this < rtgMaxVA",
262
+ UnparseableVariableExpressionError,
263
+ "someUndefinedNamedVariable",
264
+ ),
265
+ ],
266
+ )
267
+ def test_parse_variable_expression_body_this(
268
+ var_body: str, expected: type[Exception] | NamedVariable | Constant | Expression, param_key: str | None
269
+ ) -> None:
270
+ """Parsing to ensure that variable bodies that contain `this` parse (or fail) as expected"""
271
+ if isinstance(expected, type):
272
+ with pytest.raises(expected):
273
+ parse_variable_expression_body(var_body, param_key)
274
+ else:
275
+ actual = parse_variable_expression_body(var_body, param_key)
276
+ assert isinstance(actual, NamedVariable) or isinstance(actual, Constant) or isinstance(actual, Expression)
277
+ assert actual == expected, f"Input string: {var_body}"
278
+
279
+
280
+ @pytest.mark.parametrize(
281
+ "input, expected",
282
+ [
283
+ (None, False),
284
+ ("", False),
285
+ ("string value", False),
286
+ (123, False),
287
+ (1.23, False),
288
+ (Decimal("1.2"), False),
289
+ (datetime(2022, 11, 3), False),
290
+ (timedelta(2), False),
291
+ (Action("", {}), False),
292
+ ([], False),
293
+ ({}, False),
294
+ (NamedVariable(NamedVariableType.NOW), True),
295
+ (NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W), True),
296
+ (Constant(1.23), True),
297
+ (Constant(timedelta(5)), True),
298
+ (Expression(OperationType.ADD, Constant(1.23), NamedVariable(NamedVariableType.NOW)), True),
299
+ ],
300
+ )
301
+ def test_is_resolvable_variable(input: Any, expected: bool):
302
+ result = is_resolvable_variable(input)
303
+ assert isinstance(result, bool)
304
+ assert result == expected
305
+
306
+
307
+ @pytest.mark.parametrize(
308
+ "parameter,variable,expected",
309
+ [
310
+ (Constant(value=0), NamedVariableType.DERSETTING_SET_MAX_W, False),
311
+ (Constant(value=5), NamedVariableType.DERSETTING_SET_MAX_W, False),
312
+ (NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W), NamedVariableType.DERSETTING_SET_MAX_W, True),
313
+ (NamedVariable(NamedVariableType.DERSETTING_SET_MAX_VA), NamedVariableType.DERSETTING_SET_MAX_W, False),
314
+ (
315
+ Expression(OperationType.ADD, Constant(1.23), NamedVariable(NamedVariableType.NOW)),
316
+ NamedVariableType.DERSETTING_SET_MAX_W,
317
+ False,
318
+ ),
319
+ (
320
+ Expression(OperationType.ADD, NamedVariable(NamedVariableType.NOW), Constant(1.23)),
321
+ NamedVariableType.DERSETTING_SET_MAX_W,
322
+ False,
323
+ ),
324
+ (
325
+ Expression(OperationType.ADD, Constant(1.23), NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W)),
326
+ NamedVariableType.DERSETTING_SET_MAX_W,
327
+ True,
328
+ ),
329
+ (
330
+ Expression(OperationType.ADD, NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W), Constant(1.23)),
331
+ NamedVariableType.DERSETTING_SET_MAX_W,
332
+ True,
333
+ ),
334
+ ],
335
+ )
336
+ def test_has_named_variable(
337
+ parameter: NamedVariable | Expression | Constant, variable: NamedVariableType, expected: bool
338
+ ):
339
+ result = has_named_variable(parameter_value=parameter, named_variable=variable)
340
+ assert isinstance(result, bool)
341
+ assert result == expected
342
+
343
+
344
+ @pytest.mark.parametrize(
345
+ "input,expected",
346
+ [
347
+ ("SET_MAX_W", "setMaxW"),
348
+ ("SET_MAX_VAR", "setMaxVar"),
349
+ ("NOW", "now"),
350
+ ],
351
+ )
352
+ def test_snake_to_camel(input: str, expected: str) -> None:
353
+ assert snake_to_camel(input) == expected
354
+
355
+
356
+ @pytest.mark.parametrize(
357
+ "input,expected",
358
+ [
359
+ (NamedVariableType.DERCAPABILITY_RTG_MAX_VAR, "DERCapability.rtgMaxVar"),
360
+ (NamedVariableType.DERSETTING_SET_MIN_WH, "DERSetting.setMinWh"),
361
+ (NamedVariableType.DERCAPABILITY_RTG_MAX_VA, "DERCapability.rtgMaxVA"),
362
+ (NamedVariableType.DERSETTING_SET_MAX_VA, "DERSetting.setMaxVA"),
363
+ (NamedVariableType.DERCAPABILITY_NEG_RTG_MAX_CHARGE_RATE_W, "(-DERCapability.rtgMaxChargeRateW)"),
364
+ (NamedVariableType.NOW, "now"),
365
+ ],
366
+ )
367
+ def test_named_variable_repr(input: NamedVariableType, expected: str) -> None:
368
+ assert named_variable_repr(input) == expected
369
+
370
+
371
+ @pytest.mark.parametrize(
372
+ "input,expected",
373
+ [
374
+ (OperationType.ADD, "+"),
375
+ (OperationType.MULTIPLY, "*"),
376
+ (OperationType.SUBTRACT, "-"),
377
+ (OperationType.DIVIDE, "/"),
378
+ (OperationType.EQ, "=="),
379
+ (OperationType.GT, ">"),
380
+ (OperationType.GTE, ">="),
381
+ (OperationType.LT, "<"),
382
+ (OperationType.LTE, "<="),
383
+ (OperationType.NE, "!="),
384
+ ],
385
+ )
386
+ def test_operation_repr(input: OperationType, expected: str) -> None:
387
+ assert operation_repr(input) == expected
388
+
389
+
390
+ @pytest.mark.parametrize(
391
+ "input,expected",
392
+ [
393
+ (NamedVariable(variable=NamedVariableType.DERCAPABILITY_RTG_MAX_VA), "DERCapability.rtgMaxVA"),
394
+ (
395
+ Expression(OperationType.ADD, NamedVariable(NamedVariableType.DERSETTING_SET_MIN_WH), Constant(5.5)),
396
+ "DERSetting.setMinWh + 5.5",
397
+ ),
398
+ (Constant(654.456), "654.456"),
399
+ ],
400
+ )
401
+ def test_base_expression_expression_representation(input: BaseExpression, expected: str) -> None:
402
+ assert input.expression_representation() == expected