cactus-test-definitions 1.0.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.
Potentially problematic release.
This version of cactus-test-definitions might be problematic. Click here for more details.
- cactus_test_definitions/__init__.py +39 -0
- cactus_test_definitions/__pycache__/__init__.cpython-312.pyc +0 -0
- cactus_test_definitions/__pycache__/actions.cpython-312.pyc +0 -0
- cactus_test_definitions/__pycache__/checks.cpython-312.pyc +0 -0
- cactus_test_definitions/__pycache__/csipaus.cpython-312.pyc +0 -0
- cactus_test_definitions/__pycache__/errors.cpython-312.pyc +0 -0
- cactus_test_definitions/__pycache__/events.cpython-312.pyc +0 -0
- cactus_test_definitions/__pycache__/parameters.cpython-312.pyc +0 -0
- cactus_test_definitions/__pycache__/schema.cpython-312.pyc +0 -0
- cactus_test_definitions/__pycache__/test_procedures.cpython-312-pytest-8.3.5.pyc +0 -0
- cactus_test_definitions/__pycache__/test_procedures.cpython-312.pyc +0 -0
- cactus_test_definitions/__pycache__/variable_expressions.cpython-312.pyc +0 -0
- cactus_test_definitions/__pycache__/version.cpython-312.pyc +0 -0
- cactus_test_definitions/client/__init__.py +26 -0
- cactus_test_definitions/client/__pycache__/__init__.cpython-312.pyc +0 -0
- cactus_test_definitions/client/__pycache__/actions.cpython-312.pyc +0 -0
- cactus_test_definitions/client/__pycache__/checks.cpython-312.pyc +0 -0
- cactus_test_definitions/client/__pycache__/events.cpython-312.pyc +0 -0
- cactus_test_definitions/client/__pycache__/test_procedures.cpython-312-pytest-8.3.5.pyc +0 -0
- cactus_test_definitions/client/actions.py +98 -0
- cactus_test_definitions/client/checks.py +117 -0
- cactus_test_definitions/client/events.py +71 -0
- cactus_test_definitions/client/procedures/ALL-01.yaml +94 -0
- cactus_test_definitions/client/procedures/ALL-02.yaml +108 -0
- cactus_test_definitions/client/procedures/ALL-03-REJ.yaml +69 -0
- cactus_test_definitions/client/procedures/ALL-03.yaml +110 -0
- cactus_test_definitions/client/procedures/ALL-04.yaml +69 -0
- cactus_test_definitions/client/procedures/ALL-05.yaml +117 -0
- cactus_test_definitions/client/procedures/ALL-06.yaml +128 -0
- cactus_test_definitions/client/procedures/ALL-07.yaml +76 -0
- cactus_test_definitions/client/procedures/ALL-08.yaml +78 -0
- cactus_test_definitions/client/procedures/ALL-09.yaml +103 -0
- cactus_test_definitions/client/procedures/ALL-10.yaml +128 -0
- cactus_test_definitions/client/procedures/ALL-11.yaml +111 -0
- cactus_test_definitions/client/procedures/ALL-12.yaml +108 -0
- cactus_test_definitions/client/procedures/ALL-13.yaml +112 -0
- cactus_test_definitions/client/procedures/ALL-14.yaml +165 -0
- cactus_test_definitions/client/procedures/ALL-15.yaml +109 -0
- cactus_test_definitions/client/procedures/ALL-16.yaml +102 -0
- cactus_test_definitions/client/procedures/ALL-17.yaml +63 -0
- cactus_test_definitions/client/procedures/ALL-18.yaml +288 -0
- cactus_test_definitions/client/procedures/ALL-19.yaml +78 -0
- cactus_test_definitions/client/procedures/ALL-20.yaml +136 -0
- cactus_test_definitions/client/procedures/ALL-21.yaml +203 -0
- cactus_test_definitions/client/procedures/ALL-22.yaml +82 -0
- cactus_test_definitions/client/procedures/ALL-23.yaml +158 -0
- cactus_test_definitions/client/procedures/ALL-24.yaml +132 -0
- cactus_test_definitions/client/procedures/ALL-25.yaml +136 -0
- cactus_test_definitions/client/procedures/ALL-26.yaml +147 -0
- cactus_test_definitions/client/procedures/ALL-27.yaml +144 -0
- cactus_test_definitions/client/procedures/ALL-28.yaml +274 -0
- cactus_test_definitions/client/procedures/ALL-29.yaml +87 -0
- cactus_test_definitions/client/procedures/ALL-30.yaml +188 -0
- cactus_test_definitions/client/procedures/BES-01.yaml +136 -0
- cactus_test_definitions/client/procedures/BES-02.yaml +137 -0
- cactus_test_definitions/client/procedures/BES-03.yaml +135 -0
- cactus_test_definitions/client/procedures/BES-04.yaml +228 -0
- cactus_test_definitions/client/procedures/DRA-01.yaml +54 -0
- cactus_test_definitions/client/procedures/DRA-02.yaml +64 -0
- cactus_test_definitions/client/procedures/DRD-01.yaml +667 -0
- cactus_test_definitions/client/procedures/DRL-01.yaml +327 -0
- cactus_test_definitions/client/procedures/GEN-01.yaml +73 -0
- cactus_test_definitions/client/procedures/GEN-02.yaml +72 -0
- cactus_test_definitions/client/procedures/GEN-03.yaml +160 -0
- cactus_test_definitions/client/procedures/GEN-04.yaml +161 -0
- cactus_test_definitions/client/procedures/GEN-05.yaml +89 -0
- cactus_test_definitions/client/procedures/GEN-06.yaml +90 -0
- cactus_test_definitions/client/procedures/GEN-07.yaml +145 -0
- cactus_test_definitions/client/procedures/GEN-08.yaml +145 -0
- cactus_test_definitions/client/procedures/GEN-09.yaml +117 -0
- cactus_test_definitions/client/procedures/GEN-10.yaml +737 -0
- cactus_test_definitions/client/procedures/GEN-11.yaml +376 -0
- cactus_test_definitions/client/procedures/GEN-12.yaml +376 -0
- cactus_test_definitions/client/procedures/GEN-13.yaml +70 -0
- cactus_test_definitions/client/procedures/LOA-01.yaml +73 -0
- cactus_test_definitions/client/procedures/LOA-02.yaml +73 -0
- cactus_test_definitions/client/procedures/LOA-03.yaml +160 -0
- cactus_test_definitions/client/procedures/LOA-04.yaml +161 -0
- cactus_test_definitions/client/procedures/LOA-05.yaml +85 -0
- cactus_test_definitions/client/procedures/LOA-06.yaml +85 -0
- cactus_test_definitions/client/procedures/LOA-07.yaml +145 -0
- cactus_test_definitions/client/procedures/LOA-08.yaml +145 -0
- cactus_test_definitions/client/procedures/LOA-09.yaml +117 -0
- cactus_test_definitions/client/procedures/LOA-10.yaml +739 -0
- cactus_test_definitions/client/procedures/LOA-11.yaml +376 -0
- cactus_test_definitions/client/procedures/LOA-12.yaml +376 -0
- cactus_test_definitions/client/procedures/LOA-13.yaml +71 -0
- cactus_test_definitions/client/procedures/MUL-01.yaml +92 -0
- cactus_test_definitions/client/procedures/MUL-02.yaml +80 -0
- cactus_test_definitions/client/procedures/MUL-03.yaml +78 -0
- cactus_test_definitions/client/procedures/OPT-1-IN-BAND.yaml +115 -0
- cactus_test_definitions/client/procedures/OPT-1-OUT-OF-BAND.yaml +101 -0
- cactus_test_definitions/client/procedures/test-procedures.yaml +75 -0
- cactus_test_definitions/client/test_procedures.py +296 -0
- cactus_test_definitions/csipaus.py +81 -0
- cactus_test_definitions/errors.py +15 -0
- cactus_test_definitions/parameters.py +149 -0
- cactus_test_definitions/py.typed +0 -0
- cactus_test_definitions/schema.py +22 -0
- cactus_test_definitions/server/README.md +170 -0
- cactus_test_definitions/server/__pycache__/actions.cpython-312.pyc +0 -0
- cactus_test_definitions/server/__pycache__/checks.cpython-312.pyc +0 -0
- cactus_test_definitions/server/__pycache__/test_procedures.cpython-312-pytest-8.3.5.pyc +0 -0
- cactus_test_definitions/server/actions.py +139 -0
- cactus_test_definitions/server/checks.py +117 -0
- cactus_test_definitions/server/procedures/S-ALL-01.yaml +42 -0
- cactus_test_definitions/server/procedures/S-ALL-02.yaml +65 -0
- cactus_test_definitions/server/procedures/S-ALL-03.yaml +65 -0
- cactus_test_definitions/server/procedures/S-ALL-04.yaml +137 -0
- cactus_test_definitions/server/procedures/S-ALL-05.yaml +111 -0
- cactus_test_definitions/server/procedures/S-OPT-01.yaml +42 -0
- cactus_test_definitions/server/procedures/S-OPT-02.yaml +40 -0
- cactus_test_definitions/server/procedures/S-OPT-03.yaml +44 -0
- cactus_test_definitions/server/procedures/S-OPT-04.yaml +32 -0
- cactus_test_definitions/server/procedures/test-procedures.yaml +14 -0
- cactus_test_definitions/server/test_procedures.py +183 -0
- cactus_test_definitions/variable_expressions.py +419 -0
- cactus_test_definitions-1.0.0.dist-info/METADATA +288 -0
- cactus_test_definitions-1.0.0.dist-info/RECORD +135 -0
- cactus_test_definitions-1.0.0.dist-info/WHEEL +5 -0
- cactus_test_definitions-1.0.0.dist-info/licenses/LICENSE.txt +22 -0
- cactus_test_definitions-1.0.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/unit/__init__.py +0 -0
- tests/unit/client/__init__.py +0 -0
- tests/unit/client/test_actions.py +72 -0
- tests/unit/client/test_checks.py +47 -0
- tests/unit/client/test_config.py +61 -0
- tests/unit/client/test_events.py +36 -0
- tests/unit/client/test_test_procedures.py +150 -0
- tests/unit/server/__init__.py +0 -0
- tests/unit/server/test_test_procedures.py +86 -0
- tests/unit/test_csipaus.py +49 -0
- tests/unit/test_parameters.py +197 -0
- tests/unit/test_variable_expressions.py +402 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from cactus_test_definitions.client import TestProcedureConfig
|
|
6
|
+
from cactus_test_definitions.errors import TestProcedureDefinitionError
|
|
7
|
+
from cactus_test_definitions.variable_expressions import (
|
|
8
|
+
Constant,
|
|
9
|
+
Expression,
|
|
10
|
+
NamedVariable,
|
|
11
|
+
NamedVariableType,
|
|
12
|
+
OperationType,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize(
|
|
17
|
+
"filename",
|
|
18
|
+
[
|
|
19
|
+
# No 'steps' parameters defined for enable-steps action (NOT-A-VALID-PARAMETER-NAME supplied instead)
|
|
20
|
+
"tests/data/config_with_errors1.yaml",
|
|
21
|
+
# Action refers to step "NOT-A-VALID-STEP"
|
|
22
|
+
"tests/data/config_with_errors2.yaml",
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
def test_TestProcedures_validate_raises_exception(filename: str):
|
|
26
|
+
|
|
27
|
+
with pytest.raises(TestProcedureDefinitionError):
|
|
28
|
+
_ = TestProcedureConfig.from_yamlfile(path=Path(filename))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_TestProcedures_action_parameter_types():
|
|
32
|
+
"""Tests that lists/dicts/constants/datetimes can all be encoded/decoded via the yaml action definition"""
|
|
33
|
+
cfg = TestProcedureConfig.from_yamlfile(path=Path("tests/data/config_action_parameters.yaml"), skip_validation=True)
|
|
34
|
+
test_proc = cfg.test_procedures["CUSTOM-01"]
|
|
35
|
+
step = test_proc.steps["Step-1"]
|
|
36
|
+
action = step.actions[0]
|
|
37
|
+
|
|
38
|
+
assert action.parameters == {
|
|
39
|
+
"param_list_str": ["List Item 1", "List Item 2", "List Item 3"],
|
|
40
|
+
"param_list_int": [11, 22, 33],
|
|
41
|
+
"param_list_mixed": [
|
|
42
|
+
11,
|
|
43
|
+
2.2,
|
|
44
|
+
"List Item 3",
|
|
45
|
+
datetime(2020, 1, 2, 3, 4, 5, tzinfo=timezone.utc),
|
|
46
|
+
],
|
|
47
|
+
"param_dict_str": {"key1": "value1", "key2": "value2"},
|
|
48
|
+
"param_dict_int": {"key11": 11, "key22": 22},
|
|
49
|
+
"param_int": 11,
|
|
50
|
+
"param_str": "Value 11",
|
|
51
|
+
"param_datetime_utc": datetime(2025, 1, 2, 3, 4, 5, tzinfo=timezone.utc),
|
|
52
|
+
"param_datetime_naive": datetime(2025, 1, 2, 3, 4, 5, tzinfo=None),
|
|
53
|
+
"param_with_variable_date": NamedVariable(NamedVariableType.NOW),
|
|
54
|
+
"param_with_variable_db_lookup": NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W),
|
|
55
|
+
"param_with_variable_relative_db_lookup": Expression(
|
|
56
|
+
OperationType.MULTIPLY, Constant(0.25), NamedVariable(NamedVariableType.DERSETTING_SET_MAX_W)
|
|
57
|
+
),
|
|
58
|
+
"param_with_variable_relative_date": Expression(
|
|
59
|
+
OperationType.SUBTRACT, NamedVariable(NamedVariableType.NOW), Constant(timedelta(minutes=5))
|
|
60
|
+
),
|
|
61
|
+
}
|
|
@@ -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,150 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from cactus_test_definitions.client.test_procedures import (
|
|
3
|
+
TestProcedure,
|
|
4
|
+
TestProcedureConfig,
|
|
5
|
+
TestProcedureId,
|
|
6
|
+
)
|
|
7
|
+
from cactus_test_definitions.variable_expressions import (
|
|
8
|
+
NamedVariableType,
|
|
9
|
+
has_named_variable,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Failures here will raise an issue in the test_from_yamlfile test
|
|
13
|
+
ALL_TEST_PROCEDURES: list[tuple[str, TestProcedure]] = []
|
|
14
|
+
try:
|
|
15
|
+
ALL_TEST_PROCEDURES = [(k, tp) for k, tp in TestProcedureConfig.from_resource().test_procedures.items()]
|
|
16
|
+
except Exception:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_from_yamlfile():
|
|
21
|
+
"""This test confirms the standard test procedure yaml file (intended for production use)
|
|
22
|
+
can be read and converted to the appropriate dataclasses.
|
|
23
|
+
"""
|
|
24
|
+
test_procedures = TestProcedureConfig.from_resource()
|
|
25
|
+
test_procedures.validate()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_TestProcedureId_synchronised():
|
|
29
|
+
"""Ensures that TestProcedureId is in sync with all available TestProcedures"""
|
|
30
|
+
available_tests = set(TestProcedureConfig.available_tests())
|
|
31
|
+
for t in available_tests:
|
|
32
|
+
assert t in TestProcedureId, "TestProcedureConfig has a procedure not encoded in TestProcedureId"
|
|
33
|
+
|
|
34
|
+
# By convention - test ALL-01 will be an enum ALL_01
|
|
35
|
+
# for t in (t.replace("_", "-") for t in TestProcedureId):
|
|
36
|
+
for t in TestProcedureId:
|
|
37
|
+
assert t.value in available_tests, "TestProcedureId has extra procedures not found in TestProcedureConfig"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_available_tests_populated():
|
|
41
|
+
"""Force test procedures to load and ensure they all validate (and we at least have a few)"""
|
|
42
|
+
assert len(TestProcedureConfig.available_tests()) > 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.parametrize("tp_id, tp", ALL_TEST_PROCEDURES)
|
|
46
|
+
def test_each_step_accessible(tp_id: str, tp: TestProcedure):
|
|
47
|
+
"""Ensures that each test procedure's steps are enabled/removed once. Failures here indicate a bad reference to
|
|
48
|
+
enable/remove steps"""
|
|
49
|
+
|
|
50
|
+
first_step = [k for k in tp.steps.keys()][0]
|
|
51
|
+
step_names = set(tp.steps.keys())
|
|
52
|
+
|
|
53
|
+
removal_count = dict([(s, 0) for s in step_names])
|
|
54
|
+
enabled_count = dict([(s, 0) for s in step_names])
|
|
55
|
+
|
|
56
|
+
# Tally up the number of times each step is added/removed via actions
|
|
57
|
+
for s in tp.steps.values():
|
|
58
|
+
for enabled_name in [name for a in s.actions if a.type == "enable-steps" for name in a.parameters["steps"]]:
|
|
59
|
+
enabled_count[enabled_name] = enabled_count[enabled_name] + 1
|
|
60
|
+
for removed_name in [name for a in s.actions if a.type == "remove-steps" for name in a.parameters["steps"]]:
|
|
61
|
+
removal_count[removed_name] = removal_count[removed_name] + 1
|
|
62
|
+
|
|
63
|
+
# Also check the preconditions for actions
|
|
64
|
+
if tp.preconditions and tp.preconditions.actions:
|
|
65
|
+
for enabled_name in [
|
|
66
|
+
name for a in tp.preconditions.actions if a.type == "enable-steps" for name in a.parameters["steps"]
|
|
67
|
+
]:
|
|
68
|
+
enabled_count[enabled_name] = enabled_count[enabled_name] + 1
|
|
69
|
+
for removed_name in [
|
|
70
|
+
name for a in tp.preconditions.actions if a.type == "remove-steps" for name in a.parameters["steps"]
|
|
71
|
+
]:
|
|
72
|
+
removal_count[removed_name] = removal_count[removed_name] + 1
|
|
73
|
+
|
|
74
|
+
# Each test should be added once and removed once
|
|
75
|
+
for step_name in step_names:
|
|
76
|
+
assert (
|
|
77
|
+
removal_count[step_name] == 1
|
|
78
|
+
), f"{step_name}: Each test should be removed once otherwise the test cannot complete"
|
|
79
|
+
|
|
80
|
+
if step_name == first_step:
|
|
81
|
+
assert enabled_count[step_name] == 0, f"{step_name}: The first test step is already enabled"
|
|
82
|
+
else:
|
|
83
|
+
assert (
|
|
84
|
+
enabled_count[step_name] == 1
|
|
85
|
+
), f"{step_name}: Each test should be added once - not expecting loops of steps"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.mark.parametrize("tp_id, tp", ALL_TEST_PROCEDURES)
|
|
89
|
+
def test_each_step_connected(tp_id: str, tp: TestProcedure):
|
|
90
|
+
"""Interprets each test a graph of steps linking to other steps (via enable-steps). Passes if the test procedure
|
|
91
|
+
is fully connected from the first step (i.e. there are no isolated islands of steps that can't be reached from
|
|
92
|
+
the first step)"""
|
|
93
|
+
|
|
94
|
+
first_step = [k for k in tp.steps.keys()][0]
|
|
95
|
+
step_names = set(tp.steps.keys())
|
|
96
|
+
|
|
97
|
+
step_links_by_name: dict[str, list[str]] = {}
|
|
98
|
+
for step_name in step_names:
|
|
99
|
+
step = tp.steps[step_name]
|
|
100
|
+
child_step_names = [name for a in step.actions if a.type == "enable-steps" for name in a.parameters["steps"]]
|
|
101
|
+
step_links_by_name[step_name] = child_step_names
|
|
102
|
+
|
|
103
|
+
# Walk through our simple graph/tree - make sure we visit every node in that tree
|
|
104
|
+
def gather_connected_steps(links: dict[str, list[str]], node: str, visited_nodes: set[str]) -> None:
|
|
105
|
+
if node in visited_nodes:
|
|
106
|
+
return
|
|
107
|
+
visited_nodes.add(node)
|
|
108
|
+
for child in links[node]:
|
|
109
|
+
gather_connected_steps(links, child, visited_nodes)
|
|
110
|
+
|
|
111
|
+
visited_nodes: set[str] = set()
|
|
112
|
+
gather_connected_steps(step_links_by_name, first_step, visited_nodes)
|
|
113
|
+
|
|
114
|
+
# Also note that steps might be started as a precondition - so include those as visited too
|
|
115
|
+
if tp.preconditions and tp.preconditions.actions:
|
|
116
|
+
for n in [name for a in tp.preconditions.actions if a.type == "enable-steps" for name in a.parameters["steps"]]:
|
|
117
|
+
visited_nodes.add(n)
|
|
118
|
+
|
|
119
|
+
assert (
|
|
120
|
+
step_names == visited_nodes
|
|
121
|
+
), "Missing entries here indicate a step (or steps) that can't be reached from the root node"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@pytest.mark.parametrize("_, tp", ALL_TEST_PROCEDURES)
|
|
125
|
+
def test_procedures_have_required_preconditions(_: str, tp: TestProcedure):
|
|
126
|
+
|
|
127
|
+
# Immediate start implies that EndDevice registration will happen during the test body - these tests
|
|
128
|
+
# don't require end-device-contents (as it will cause the test to fail anyway)
|
|
129
|
+
is_immediate_start = tp.preconditions is not None and tp.preconditions.immediate_start
|
|
130
|
+
if not is_immediate_start:
|
|
131
|
+
assert tp.preconditions is not None, "Expected precondition check 'end-device-contents'"
|
|
132
|
+
assert tp.preconditions.checks is not None, "Expected precondition check 'end-device-contents'"
|
|
133
|
+
assert any(
|
|
134
|
+
[check.type == "end-device-contents" for check in tp.preconditions.checks]
|
|
135
|
+
), "Expected precondition check 'end-device-contents'"
|
|
136
|
+
|
|
137
|
+
# Check 'der-settings-contents' present if any precondition action parameter references setMaxW
|
|
138
|
+
if tp.preconditions is not None and tp.preconditions.actions is not None:
|
|
139
|
+
for action in tp.preconditions.actions:
|
|
140
|
+
if action.parameters is not None:
|
|
141
|
+
if any(
|
|
142
|
+
[
|
|
143
|
+
has_named_variable(
|
|
144
|
+
parameter_value=parameter, named_variable=NamedVariableType.DERSETTING_SET_MAX_W
|
|
145
|
+
)
|
|
146
|
+
for parameter in action.parameters.values()
|
|
147
|
+
]
|
|
148
|
+
):
|
|
149
|
+
assert tp.preconditions.checks is not None
|
|
150
|
+
assert any([check.type == "der-settings-contents" for check in tp.preconditions.checks])
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from cactus_test_definitions.server.actions import ACTION_PARAMETER_SCHEMA
|
|
3
|
+
from cactus_test_definitions.server.test_procedures import (
|
|
4
|
+
TestProcedure,
|
|
5
|
+
TestProcedureConfig,
|
|
6
|
+
TestProcedureId,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
# Failures here will raise an issue in the test_from_yamlfile test
|
|
10
|
+
ALL_TEST_PROCEDURES: list[tuple[str, TestProcedure]] = []
|
|
11
|
+
try:
|
|
12
|
+
ALL_TEST_PROCEDURES = [(k, tp) for k, tp in TestProcedureConfig.from_resource().test_procedures.items()]
|
|
13
|
+
except Exception:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_ALL_TEST_PROCEDURES_parsed():
|
|
18
|
+
assert len(ALL_TEST_PROCEDURES) > 0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_from_yamlfile():
|
|
22
|
+
"""This test confirms the standard test procedure yaml file (intended for production use)
|
|
23
|
+
can be read and converted to the appropriate dataclasses.
|
|
24
|
+
"""
|
|
25
|
+
test_procedures = TestProcedureConfig.from_resource()
|
|
26
|
+
test_procedures.validate()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_TestProcedureId_synchronised():
|
|
30
|
+
"""Ensures that TestProcedureId is in sync with all available TestProcedures"""
|
|
31
|
+
available_tests = set(TestProcedureConfig.available_tests())
|
|
32
|
+
for t in available_tests:
|
|
33
|
+
assert t in TestProcedureId, "TestProcedureConfig has a procedure not encoded in TestProcedureId"
|
|
34
|
+
|
|
35
|
+
# By convention - test ALL-01 will be an enum ALL_01
|
|
36
|
+
# for t in (t.replace("_", "-") for t in TestProcedureId):
|
|
37
|
+
for t in TestProcedureId:
|
|
38
|
+
assert t.value in available_tests, "TestProcedureId has extra procedures not found in TestProcedureConfig"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_available_tests_populated():
|
|
42
|
+
"""Force test procedures to load and ensure they all validate (and we at least have a few)"""
|
|
43
|
+
assert len(TestProcedureConfig.available_tests()) > 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.parametrize("tp_id, tp", ALL_TEST_PROCEDURES)
|
|
47
|
+
def test_each_step_id_unique(tp_id: str, tp: TestProcedure):
|
|
48
|
+
all_ids = [s.id for s in tp.steps]
|
|
49
|
+
assert list(sorted(all_ids)) == list(sorted(set(all_ids)))
|
|
50
|
+
assert len(set((s.id for s in tp.steps))) == len(tp.steps), "All steps must have a unique id property"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize("tp_id, tp", ALL_TEST_PROCEDURES)
|
|
54
|
+
def test_each_alias_defined(tp_id: str, tp: TestProcedure):
|
|
55
|
+
"""Ensures that each test procedure's steps that have actions using an alias... define those aliases in advance"""
|
|
56
|
+
|
|
57
|
+
# sanity check - make sure we are looking for the action names that are valid
|
|
58
|
+
UPSERT_MUP_ACTION = "upsert-mup"
|
|
59
|
+
INSERT_MUP_ACTION = "insert-readings"
|
|
60
|
+
CREATE_SUB_ACTION = "create-subscription"
|
|
61
|
+
DELETE_SUB_ACTION = "delete-subscription"
|
|
62
|
+
assert UPSERT_MUP_ACTION in ACTION_PARAMETER_SCHEMA, "If this fails - the action name has changed. Update this test"
|
|
63
|
+
assert INSERT_MUP_ACTION in ACTION_PARAMETER_SCHEMA, "If this fails - the action name has changed. Update this test"
|
|
64
|
+
assert CREATE_SUB_ACTION in ACTION_PARAMETER_SCHEMA, "If this fails - the action name has changed. Update this test"
|
|
65
|
+
assert DELETE_SUB_ACTION in ACTION_PARAMETER_SCHEMA, "If this fails - the action name has changed. Update this test"
|
|
66
|
+
|
|
67
|
+
mup_aliases_found: set[str] = set()
|
|
68
|
+
sub_aliases_found: set[str] = set()
|
|
69
|
+
for step in tp.steps:
|
|
70
|
+
if step.action.parameters:
|
|
71
|
+
mup_id = step.action.parameters.get("mup_id", None)
|
|
72
|
+
sub_id = step.action.parameters.get("sub_id", None)
|
|
73
|
+
else:
|
|
74
|
+
mup_id = None
|
|
75
|
+
sub_id = None
|
|
76
|
+
|
|
77
|
+
if step.action.type == UPSERT_MUP_ACTION:
|
|
78
|
+
assert mup_id and (mup_id not in mup_aliases_found), "mup_id is already defined in this test."
|
|
79
|
+
mup_aliases_found.add(mup_id)
|
|
80
|
+
elif step.action.type == INSERT_MUP_ACTION:
|
|
81
|
+
assert mup_id and (mup_id in mup_aliases_found), "mup_id hasn't been defined yet."
|
|
82
|
+
elif step.action.type == CREATE_SUB_ACTION:
|
|
83
|
+
assert sub_id and (sub_id not in sub_aliases_found), "sub_id is already defined in this test."
|
|
84
|
+
sub_aliases_found.add(sub_id)
|
|
85
|
+
elif step.action.type == DELETE_SUB_ACTION:
|
|
86
|
+
assert sub_id and (sub_id in sub_aliases_found), "sub_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
|
|
@@ -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)
|