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.
- 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 +30 -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/__pycache__/validate.cpython-312.pyc +0 -0
- cactus_test_definitions/client/actions.py +99 -0
- cactus_test_definitions/client/checks.py +128 -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 +113 -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 +111 -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 +126 -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 +289 -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-EXT.yaml +228 -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 +273 -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 +793 -0
- cactus_test_definitions/client/procedures/GEN-11.yaml +402 -0
- cactus_test_definitions/client/procedures/GEN-12.yaml +402 -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 +89 -0
- cactus_test_definitions/client/procedures/LOA-06.yaml +89 -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 +793 -0
- cactus_test_definitions/client/procedures/LOA-11.yaml +402 -0
- cactus_test_definitions/client/procedures/LOA-12.yaml +402 -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 +74 -0
- cactus_test_definitions/client/test_procedures.py +185 -0
- cactus_test_definitions/client/validate.py +98 -0
- cactus_test_definitions/csipaus.py +83 -0
- cactus_test_definitions/errors.py +15 -0
- cactus_test_definitions/parameters.py +153 -0
- cactus_test_definitions/py.typed +0 -0
- cactus_test_definitions/schema.py +22 -0
- cactus_test_definitions/server/README.md +172 -0
- cactus_test_definitions/server/__init__.py +27 -0
- cactus_test_definitions/server/__pycache__/__init__.cpython-312.pyc +0 -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/__pycache__/validate.cpython-312.pyc +0 -0
- cactus_test_definitions/server/actions.py +140 -0
- cactus_test_definitions/server/checks.py +152 -0
- cactus_test_definitions/server/procedures/S-ALL-01.yaml +45 -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-ALL-06.yaml +111 -0
- cactus_test_definitions/server/procedures/S-ALL-07.yaml +100 -0
- cactus_test_definitions/server/procedures/S-ALL-08.yaml +77 -0
- cactus_test_definitions/server/procedures/S-ALL-09.yaml +43 -0
- cactus_test_definitions/server/procedures/S-ALL-10.yaml +68 -0
- cactus_test_definitions/server/procedures/S-ALL-11.yaml +36 -0
- cactus_test_definitions/server/procedures/S-ALL-12.yaml +36 -0
- cactus_test_definitions/server/procedures/S-ALL-13.yaml +36 -0
- cactus_test_definitions/server/procedures/S-ALL-14.yaml +36 -0
- cactus_test_definitions/server/procedures/S-ALL-15.yaml +79 -0
- cactus_test_definitions/server/procedures/S-ALL-16.yaml +79 -0
- cactus_test_definitions/server/procedures/S-ALL-17.yaml +39 -0
- cactus_test_definitions/server/procedures/S-ALL-18.yaml +35 -0
- cactus_test_definitions/server/procedures/S-ALL-19.yaml +34 -0
- cactus_test_definitions/server/procedures/S-ALL-20.yaml +34 -0
- cactus_test_definitions/server/procedures/S-ALL-21.yaml +34 -0
- cactus_test_definitions/server/procedures/S-ALL-22.yaml +34 -0
- cactus_test_definitions/server/procedures/S-ALL-23.yaml +34 -0
- cactus_test_definitions/server/procedures/S-ALL-24.yaml +37 -0
- cactus_test_definitions/server/procedures/S-ALL-25.yaml +151 -0
- cactus_test_definitions/server/procedures/S-ALL-41.yaml +343 -0
- cactus_test_definitions/server/procedures/S-ALL-42.yaml +81 -0
- cactus_test_definitions/server/procedures/S-ALL-43.yaml +146 -0
- cactus_test_definitions/server/procedures/S-ALL-44.yaml +51 -0
- cactus_test_definitions/server/procedures/S-ALL-45.yaml +34 -0
- cactus_test_definitions/server/procedures/S-ALL-48.yaml +29 -0
- cactus_test_definitions/server/procedures/S-ALL-49.yaml +56 -0
- cactus_test_definitions/server/procedures/S-ALL-51.yaml +35 -0
- cactus_test_definitions/server/procedures/S-ALL-52.yaml +35 -0
- cactus_test_definitions/server/procedures/S-ALL-53.yaml +42 -0
- cactus_test_definitions/server/procedures/S-ALL-55.yaml +45 -0
- cactus_test_definitions/server/procedures/S-ALL-56.yaml +36 -0
- cactus_test_definitions/server/procedures/S-ALL-57.yaml +62 -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 +47 -0
- cactus_test_definitions/server/procedures/S-OPT-04.yaml +32 -0
- cactus_test_definitions/server/procedures/S-OPT-05.yaml +33 -0
- cactus_test_definitions/server/test_procedures.py +156 -0
- cactus_test_definitions/server/validate.py +30 -0
- cactus_test_definitions/variable_expressions.py +419 -0
- cactus_test_definitions-1.8.0.dist-info/METADATA +289 -0
- cactus_test_definitions-1.8.0.dist-info/RECORD +173 -0
- cactus_test_definitions-1.8.0.dist-info/WHEEL +5 -0
- cactus_test_definitions-1.8.0.dist-info/licenses/LICENSE.txt +22 -0
- cactus_test_definitions-1.8.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 +71 -0
- tests/unit/client/test_events.py +36 -0
- tests/unit/client/test_test_procedures.py +103 -0
- tests/unit/client/test_validate.py +200 -0
- tests/unit/server/__init__.py +0 -0
- tests/unit/server/test_test_procedures.py +60 -0
- tests/unit/server/test_validate.py +75 -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,185 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from importlib import resources
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from cactus_test_definitions.client.actions import Action
|
|
7
|
+
from cactus_test_definitions.client.checks import Check
|
|
8
|
+
from cactus_test_definitions.client.events import Event
|
|
9
|
+
from cactus_test_definitions.csipaus import CSIPAusVersion
|
|
10
|
+
from cactus_test_definitions.schema import UniqueKeyLoader
|
|
11
|
+
from dataclass_wizard import LoadMeta, YAMLWizard
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestProcedureId(StrEnum):
|
|
15
|
+
"""The set of all available test ID's
|
|
16
|
+
|
|
17
|
+
This should be kept in sync with the current set of client test procedures loaded from the procedures directory"""
|
|
18
|
+
|
|
19
|
+
__test__ = False # Prevent pytest from picking up this class
|
|
20
|
+
ALL_01 = "ALL-01"
|
|
21
|
+
ALL_02 = "ALL-02"
|
|
22
|
+
ALL_03 = "ALL-03"
|
|
23
|
+
ALL_03_REJ = "ALL-03-REJ"
|
|
24
|
+
ALL_04 = "ALL-04"
|
|
25
|
+
ALL_05 = "ALL-05"
|
|
26
|
+
ALL_06 = "ALL-06"
|
|
27
|
+
ALL_07 = "ALL-07"
|
|
28
|
+
ALL_08 = "ALL-08"
|
|
29
|
+
ALL_09 = "ALL-09"
|
|
30
|
+
ALL_10 = "ALL-10"
|
|
31
|
+
ALL_11 = "ALL-11"
|
|
32
|
+
ALL_12 = "ALL-12"
|
|
33
|
+
ALL_13 = "ALL-13"
|
|
34
|
+
ALL_14 = "ALL-14"
|
|
35
|
+
ALL_15 = "ALL-15"
|
|
36
|
+
ALL_16 = "ALL-16"
|
|
37
|
+
ALL_17 = "ALL-17"
|
|
38
|
+
ALL_18 = "ALL-18"
|
|
39
|
+
ALL_19 = "ALL-19"
|
|
40
|
+
ALL_20 = "ALL-20"
|
|
41
|
+
ALL_21 = "ALL-21"
|
|
42
|
+
ALL_22 = "ALL-22"
|
|
43
|
+
ALL_23 = "ALL-23"
|
|
44
|
+
ALL_24 = "ALL-24"
|
|
45
|
+
ALL_25 = "ALL-25"
|
|
46
|
+
ALL_25_EXT = "ALL-25-EXT"
|
|
47
|
+
ALL_26 = "ALL-26"
|
|
48
|
+
ALL_27 = "ALL-27"
|
|
49
|
+
ALL_28 = "ALL-28"
|
|
50
|
+
ALL_29 = "ALL-29"
|
|
51
|
+
ALL_30 = "ALL-30"
|
|
52
|
+
DRA_01 = "DRA-01"
|
|
53
|
+
DRA_02 = "DRA-02"
|
|
54
|
+
DRD_01 = "DRD-01"
|
|
55
|
+
DRL_01 = "DRL-01"
|
|
56
|
+
GEN_01 = "GEN-01"
|
|
57
|
+
GEN_02 = "GEN-02"
|
|
58
|
+
GEN_03 = "GEN-03"
|
|
59
|
+
GEN_04 = "GEN-04"
|
|
60
|
+
GEN_05 = "GEN-05"
|
|
61
|
+
GEN_06 = "GEN-06"
|
|
62
|
+
GEN_07 = "GEN-07"
|
|
63
|
+
GEN_08 = "GEN-08"
|
|
64
|
+
GEN_09 = "GEN-09"
|
|
65
|
+
GEN_10 = "GEN-10"
|
|
66
|
+
GEN_11 = "GEN-11"
|
|
67
|
+
GEN_12 = "GEN-12"
|
|
68
|
+
GEN_13 = "GEN-13"
|
|
69
|
+
LOA_01 = "LOA-01"
|
|
70
|
+
LOA_02 = "LOA-02"
|
|
71
|
+
LOA_03 = "LOA-03"
|
|
72
|
+
LOA_04 = "LOA-04"
|
|
73
|
+
LOA_05 = "LOA-05"
|
|
74
|
+
LOA_06 = "LOA-06"
|
|
75
|
+
LOA_07 = "LOA-07"
|
|
76
|
+
LOA_08 = "LOA-08"
|
|
77
|
+
LOA_09 = "LOA-09"
|
|
78
|
+
LOA_10 = "LOA-10"
|
|
79
|
+
LOA_11 = "LOA-11"
|
|
80
|
+
LOA_12 = "LOA-12"
|
|
81
|
+
LOA_13 = "LOA-13"
|
|
82
|
+
MUL_01 = "MUL-01"
|
|
83
|
+
MUL_02 = "MUL-02"
|
|
84
|
+
MUL_03 = "MUL-03"
|
|
85
|
+
|
|
86
|
+
# Storage extension
|
|
87
|
+
BES_01 = "BES-01"
|
|
88
|
+
BES_02 = "BES-02"
|
|
89
|
+
BES_03 = "BES-03"
|
|
90
|
+
BES_04 = "BES-04"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class Step:
|
|
95
|
+
"""A step is a part of the test procedure that waits for some form of event before running a set of actions.
|
|
96
|
+
|
|
97
|
+
It's common for a step to activate other "steps" so that the state of the active test procedure can "evolve" in
|
|
98
|
+
response to client behaviour
|
|
99
|
+
|
|
100
|
+
Instructions are out-of-band operations that need performing during the step
|
|
101
|
+
e.g. disconnect DER from grid, disable power consumption etc.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
event: Event # The event to act as a trigger
|
|
105
|
+
actions: list[Action] # The actions to execute when the trigger is met
|
|
106
|
+
instructions: list[str] | None = None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class Preconditions:
|
|
111
|
+
"""Preconditions are run during the "initialization" state that precedes the start of a test. They typically
|
|
112
|
+
allow for the setup of the test.
|
|
113
|
+
|
|
114
|
+
Checks are also included to prevent a client from starting a test before they have correctly met preconditions
|
|
115
|
+
|
|
116
|
+
Instructions are out-of-band operations that need performing at the start of the test procedure
|
|
117
|
+
e.g. attach a load etc.
|
|
118
|
+
|
|
119
|
+
If immediate_start is set to True - the "initialization" step will be progressed through immediately so that the
|
|
120
|
+
client has no opportunity to interact with the server in this state. Any actions will still be executed. Do NOT
|
|
121
|
+
utilise immediate_start with precondition checks.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
init_actions: list[Action] | None = None # To be executed as the runner starts (before anything can occur)
|
|
125
|
+
immediate_start: bool = False # If True - a test execution will have NO "pre-start" phase.
|
|
126
|
+
actions: list[Action] | None = None # To be executed as the test case "starts" (usually on request of client)
|
|
127
|
+
checks: list[Check] | None = None # Will prevent move from "init" state to "started" state of a test if any fail
|
|
128
|
+
instructions: list[str] | None = None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass
|
|
132
|
+
class Criteria:
|
|
133
|
+
"""Criteria represent the final pass/fail analysis run after a TestProcedure completion. They can consider both
|
|
134
|
+
the final state of the test system as well as the interactions that happened while it was running"""
|
|
135
|
+
|
|
136
|
+
checks: list[Check] | None = None # These should be run at test procedure finalization to determine pass/fail
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class TestProcedure(YAMLWizard):
|
|
141
|
+
"""Top level object for collecting everything relevant to a single TestProcedure"""
|
|
142
|
+
|
|
143
|
+
__test__ = False # Prevent pytest from picking up this class
|
|
144
|
+
description: str # Metadata from test definitions
|
|
145
|
+
category: str # Metadata from test definitions
|
|
146
|
+
classes: list[str] # Metadata from test definitions
|
|
147
|
+
target_versions: list[CSIPAusVersion] # What version(s) of csip-aus is this test targeting?
|
|
148
|
+
steps: dict[str, Step]
|
|
149
|
+
preconditions: Preconditions | None = None # These execute during "init" and setup the test for a valid start state
|
|
150
|
+
criteria: Criteria | None = None # How will success/failure of this procedure be determined?
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
LoadMeta(raise_on_unknown_json_key=True).bind_to(TestProcedure)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def parse_test_procedure(yaml_contents: str) -> TestProcedure:
|
|
157
|
+
"""Given a YAML string - parse a TestProcedure.
|
|
158
|
+
|
|
159
|
+
This will ensure the YAML parser will use all the "strict" extensions to reduce the incidence of errors"""
|
|
160
|
+
|
|
161
|
+
return TestProcedure.from_yaml(
|
|
162
|
+
yaml_contents,
|
|
163
|
+
decoder=yaml.load, # type: ignore
|
|
164
|
+
Loader=UniqueKeyLoader,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_yaml_contents(test_procedure_id: TestProcedureId) -> str:
|
|
169
|
+
"""Finds the YAML contents for the TestProcedure with the specified TestProcedureId"""
|
|
170
|
+
yaml_resource = resources.files("cactus_test_definitions.client.procedures") / f"{test_procedure_id}.yaml"
|
|
171
|
+
with resources.as_file(yaml_resource) as yaml_file:
|
|
172
|
+
with open(yaml_file, "r") as f:
|
|
173
|
+
yaml_contents = f.read()
|
|
174
|
+
return yaml_contents
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_test_procedure(test_procedure_id: TestProcedureId) -> TestProcedure:
|
|
178
|
+
"""Gets the TestProcedure with the nominated ID by loading its definition from disk"""
|
|
179
|
+
yaml_contents = get_yaml_contents(test_procedure_id)
|
|
180
|
+
return parse_test_procedure(yaml_contents)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_all_test_procedures() -> dict[TestProcedureId, TestProcedure]:
|
|
184
|
+
"""Gets every TestProcedure, keyed by their TestProcedureId"""
|
|
185
|
+
return {tp_id: get_test_procedure(tp_id) for tp_id in TestProcedureId}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from cactus_test_definitions.client.actions import Action, validate_action_parameters
|
|
2
|
+
from cactus_test_definitions.client.checks import validate_check_parameters
|
|
3
|
+
from cactus_test_definitions.client.events import validate_event_parameters
|
|
4
|
+
from cactus_test_definitions.client.test_procedures import (
|
|
5
|
+
TestProcedure,
|
|
6
|
+
TestProcedureId,
|
|
7
|
+
)
|
|
8
|
+
from cactus_test_definitions.errors import TestProcedureDefinitionError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def validate_action(
|
|
12
|
+
procedure: TestProcedure, test_procedure_id: TestProcedureId, location: str, action: Action
|
|
13
|
+
) -> None:
|
|
14
|
+
"""Handles the full validation of an action's definition for a parent procedure.
|
|
15
|
+
|
|
16
|
+
procedure: The parent TestProcedure for action
|
|
17
|
+
test_procedure_id: The name of procedure (used for labelling errors)
|
|
18
|
+
location: Where in procedure can you find action? (used for labelling errors)
|
|
19
|
+
action: The action to validate
|
|
20
|
+
|
|
21
|
+
raises TestProcedureDefinitionError on failure
|
|
22
|
+
"""
|
|
23
|
+
validate_action_parameters(test_procedure_id, location, action)
|
|
24
|
+
|
|
25
|
+
# Provide additional "action specific" validation
|
|
26
|
+
match action.type:
|
|
27
|
+
case "enable-steps" | "remove-steps":
|
|
28
|
+
for step_name in action.parameters["steps"]:
|
|
29
|
+
if step_name not in procedure.steps.keys():
|
|
30
|
+
raise TestProcedureDefinitionError(
|
|
31
|
+
f"{test_procedure_id}.{location}. Refers to unknown step '{step_name}'."
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def validate_test_procedure_actions(test_procedure: TestProcedure, test_procedure_id: TestProcedureId) -> None:
|
|
36
|
+
"""Validate actions of test procedure steps / preconditions
|
|
37
|
+
|
|
38
|
+
Ensure,
|
|
39
|
+
- action has the correct parameters
|
|
40
|
+
- if parameters refer to steps then those steps are defined for the test procedure
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# Validate actions in the preconditions
|
|
44
|
+
if test_procedure.preconditions:
|
|
45
|
+
if test_procedure.preconditions.actions:
|
|
46
|
+
for action in test_procedure.preconditions.actions:
|
|
47
|
+
validate_action(test_procedure, test_procedure_id, "Precondition", action)
|
|
48
|
+
|
|
49
|
+
if test_procedure.preconditions.init_actions:
|
|
50
|
+
for action in test_procedure.preconditions.init_actions:
|
|
51
|
+
validate_action(test_procedure, test_procedure_id, "Precondition", action)
|
|
52
|
+
|
|
53
|
+
# Validate actions that exist on steps
|
|
54
|
+
for step_name, step in test_procedure.steps.items():
|
|
55
|
+
for action in step.actions:
|
|
56
|
+
validate_action(test_procedure, test_procedure_id, step_name, action)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def validate_test_procedure_checks(test_procedure: TestProcedure, test_procedure_id: TestProcedureId) -> None:
|
|
60
|
+
"""Validate checks of a test procedure
|
|
61
|
+
|
|
62
|
+
Ensure,
|
|
63
|
+
- check has the correct parameters
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
if test_procedure.criteria and test_procedure.criteria.checks:
|
|
67
|
+
for check in test_procedure.criteria.checks:
|
|
68
|
+
validate_check_parameters(f"{test_procedure_id}: Criteria", check)
|
|
69
|
+
|
|
70
|
+
if test_procedure.preconditions and test_procedure.preconditions.checks:
|
|
71
|
+
for check in test_procedure.preconditions.checks:
|
|
72
|
+
validate_check_parameters(f"{test_procedure_id}: Preconditions", check)
|
|
73
|
+
|
|
74
|
+
for step_name, step in test_procedure.steps.items():
|
|
75
|
+
if step.event.checks:
|
|
76
|
+
for check in step.event.checks:
|
|
77
|
+
validate_check_parameters(f"{test_procedure_id}: Step {step_name}", check)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def validate_test_procedure_events(test_procedure: TestProcedure, test_procedure_id: TestProcedureId) -> None:
|
|
81
|
+
"""Validate events of test procedure steps
|
|
82
|
+
|
|
83
|
+
Ensure,
|
|
84
|
+
- event has the correct parameters
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
if test_procedure.steps:
|
|
88
|
+
for step_name, step in test_procedure.steps.items():
|
|
89
|
+
validate_event_parameters(test_procedure_id, step_name, step.event)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def validate_test_procedure(test_procedure: TestProcedure, test_procedure_id: TestProcedureId) -> None:
|
|
93
|
+
"""Performs additional "high level" validation of a test procedure. (eg: ensuring all action names are valid)
|
|
94
|
+
|
|
95
|
+
raises TestProcedureDefinitionError on error"""
|
|
96
|
+
validate_test_procedure_actions(test_procedure, test_procedure_id)
|
|
97
|
+
validate_test_procedure_checks(test_procedure, test_procedure_id)
|
|
98
|
+
validate_test_procedure_events(test_procedure, test_procedure_id)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CSIPAusVersion(StrEnum):
|
|
5
|
+
"""The various version identifiers for CSIP-Aus. Used for distinguishing what tests are compatible with what
|
|
6
|
+
released versions CSIP-Aus."""
|
|
7
|
+
|
|
8
|
+
RELEASE_1_2 = "v1.2"
|
|
9
|
+
BETA_1_3_STORAGE = "v1.3-beta/storage"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CSIPAusResource(StrEnum):
|
|
13
|
+
"""Labels for each resource type that the server/client tests might reference. This is not designed to be
|
|
14
|
+
an exhaustive list of all SEP2 / CSIP-Aus models - only the entities that might have tests applied to them."""
|
|
15
|
+
|
|
16
|
+
DeviceCapability = "DeviceCapability"
|
|
17
|
+
|
|
18
|
+
Time = "Time"
|
|
19
|
+
|
|
20
|
+
MirrorUsagePointList = "MirrorUsagePointList"
|
|
21
|
+
MirrorUsagePoint = "MirrorUsagePoint"
|
|
22
|
+
|
|
23
|
+
EndDeviceList = "EndDeviceList"
|
|
24
|
+
EndDevice = "EndDevice"
|
|
25
|
+
ConnectionPoint = "ConnectionPoint"
|
|
26
|
+
Registration = "Registration"
|
|
27
|
+
|
|
28
|
+
FunctionSetAssignmentsList = "FunctionSetAssignmentsList"
|
|
29
|
+
FunctionSetAssignments = "FunctionSetAssignments"
|
|
30
|
+
|
|
31
|
+
DERProgramList = "DERProgramList"
|
|
32
|
+
DERProgram = "DERProgram"
|
|
33
|
+
|
|
34
|
+
DERControlList = "DERControlList"
|
|
35
|
+
DERControl = "DERControl"
|
|
36
|
+
|
|
37
|
+
DefaultDERControl = "DefaultDERControl"
|
|
38
|
+
|
|
39
|
+
DERList = "DERList"
|
|
40
|
+
DER = "DER"
|
|
41
|
+
DERCapability = "DERCapability"
|
|
42
|
+
DERSettings = "DERSettings"
|
|
43
|
+
DERStatus = "DERStatus"
|
|
44
|
+
|
|
45
|
+
SubscriptionList = "SubscriptionList"
|
|
46
|
+
Subscription = "Subscription"
|
|
47
|
+
|
|
48
|
+
Notification = "Notification" # A Notification isn't normally discoverable - it's received via pub/sub webhook
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CSIPAusReadingLocation(StrEnum):
|
|
52
|
+
Site = "Site" # The reading is measured at the site's connection point
|
|
53
|
+
Device = "Device" # The reading is measured at the actual device (behind the meter)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CSIPAusReadingType(StrEnum):
|
|
57
|
+
"""A non exhaustive set of CSIPAus reading types / role flags that can be specified in tests"""
|
|
58
|
+
|
|
59
|
+
ActivePowerAverage = "ActivePowerAverage"
|
|
60
|
+
ActivePowerInstantaneous = "ActivePowerInstantaneous"
|
|
61
|
+
ActivePowerMaximum = "ActivePowerMaximum"
|
|
62
|
+
ActivePowerMinimum = "ActivePowerMinimum"
|
|
63
|
+
|
|
64
|
+
ReactivePowerAverage = "ReactivePowerAverage"
|
|
65
|
+
ReactivePowerInstantaneous = "ReactivePowerInstantaneous"
|
|
66
|
+
ReactivePowerMaximum = "ReactivePowerMaximum"
|
|
67
|
+
ReactivePowerMinimum = "ReactivePowerMinimum"
|
|
68
|
+
|
|
69
|
+
FrequencyAverage = "FrequencyAverage"
|
|
70
|
+
FrequencyInstantaneous = "FrequencyInstantaneous"
|
|
71
|
+
FrequencyMaximum = "FrequencyMaximum"
|
|
72
|
+
FrequencyMinimum = "FrequencyMinimum"
|
|
73
|
+
|
|
74
|
+
VoltageSinglePhaseAverage = "VoltageSinglePhaseAverage"
|
|
75
|
+
VoltageSinglePhaseInstantaneous = "VoltageSinglePhaseInstantaneous"
|
|
76
|
+
VoltageSinglePhaseMaximum = "VoltageSinglePhaseMaximum"
|
|
77
|
+
VoltageSinglePhaseMinimum = "VoltageSinglePhaseMinimum"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def is_list_resource(resource: CSIPAusResource) -> bool:
|
|
81
|
+
"""Returns true if the specified resource is classified as a list resource (i.e. it supports list query params) and
|
|
82
|
+
will return an entity with list attributes (eg 'all')"""
|
|
83
|
+
return resource.name.endswith("List") # This is a really simple method but it should work
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class TestProcedureDefinitionError(Exception):
|
|
2
|
+
__test__ = False # Prevent pytest from picking up this class
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class UnresolvableVariableError(Exception):
|
|
6
|
+
"""Raised whenever a NamedVariable cannot be resolved at test execution time (eg: database doesn't have the
|
|
7
|
+
requisite information)"""
|
|
8
|
+
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UnparseableVariableExpressionError(Exception):
|
|
13
|
+
"""Raised whenever a raw string cannot parse to a NamedVariable/Expression"""
|
|
14
|
+
|
|
15
|
+
pass
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from enum import IntEnum, auto
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from cactus_test_definitions.csipaus import (
|
|
8
|
+
CSIPAusReadingLocation,
|
|
9
|
+
CSIPAusReadingType,
|
|
10
|
+
CSIPAusResource,
|
|
11
|
+
)
|
|
12
|
+
from cactus_test_definitions.errors import TestProcedureDefinitionError
|
|
13
|
+
from cactus_test_definitions.variable_expressions import is_resolvable_variable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ParameterType(IntEnum):
|
|
17
|
+
"""The various "basic" types that can be set in YAML for a parameter. Rudimentary type checking
|
|
18
|
+
will try to enforce these but they can't be guaranteed"""
|
|
19
|
+
|
|
20
|
+
String = auto()
|
|
21
|
+
Integer = auto()
|
|
22
|
+
Float = auto()
|
|
23
|
+
Boolean = auto()
|
|
24
|
+
DateTime = auto() # TZ aware datetime
|
|
25
|
+
ListString = auto() # List of strings
|
|
26
|
+
HexBinary = auto()
|
|
27
|
+
CSIPAusResource = auto() # Member of cactus_test_definitions.csipaus.CSIPAusResource
|
|
28
|
+
ListCSIPAusResource = auto() # List where each member is a cactus_test_definitions.csipaus.CSIPAusResource
|
|
29
|
+
CSIPAusReadingType = auto() # Member of cactus_test_definitions.csipaus.CSIPAusReadingType
|
|
30
|
+
ListCSIPAusReadingType = auto() # List where each member is a cactus_test_definitions.csipaus.CSIPAusReadingType
|
|
31
|
+
CSIPAusReadingLocation = auto() # Member of cactus_test_definitions.csipaus.CSIPAusReadingLocation
|
|
32
|
+
ReadingTypeValues = auto() # A dict of type dict[CSIPAusReadingType, list[float]], each list has the same length
|
|
33
|
+
UnsignedInteger = auto()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class ParameterSchema:
|
|
38
|
+
"""What parameters can be passed to a given action/check. Describes a single optional/mandatory field"""
|
|
39
|
+
|
|
40
|
+
mandatory: bool # If this parameter required
|
|
41
|
+
expected_type: ParameterType
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_valid_parameter_type(expected_type: ParameterType, value: Any) -> bool:
|
|
45
|
+
"""Returns true if the specified value "passes" as the expected type. Only performs rudimentary checks to try
|
|
46
|
+
and catch obvious misconfigurations"""
|
|
47
|
+
if value is None:
|
|
48
|
+
return True # We currently allow None to pass to params. Make it a runtime concern
|
|
49
|
+
|
|
50
|
+
if is_resolvable_variable(value):
|
|
51
|
+
return True # Too hard to validate variable expressions. Make it a runtime concern
|
|
52
|
+
|
|
53
|
+
match expected_type:
|
|
54
|
+
case ParameterType.String:
|
|
55
|
+
return isinstance(value, str)
|
|
56
|
+
case ParameterType.Integer:
|
|
57
|
+
if isinstance(value, int):
|
|
58
|
+
return True
|
|
59
|
+
else:
|
|
60
|
+
# Floats/decimals can pass through so long as they have 0 decimal places
|
|
61
|
+
try:
|
|
62
|
+
return int(value) == value
|
|
63
|
+
except Exception:
|
|
64
|
+
return False
|
|
65
|
+
case ParameterType.UnsignedInteger:
|
|
66
|
+
# Integer that is greater than or equal to 0
|
|
67
|
+
return is_valid_parameter_type(ParameterType.Integer, value) and value >= 0
|
|
68
|
+
case ParameterType.Float:
|
|
69
|
+
return isinstance(value, float) or isinstance(value, Decimal) or isinstance(value, int)
|
|
70
|
+
case ParameterType.Boolean:
|
|
71
|
+
return isinstance(value, bool)
|
|
72
|
+
case ParameterType.DateTime:
|
|
73
|
+
return isinstance(value, datetime)
|
|
74
|
+
case ParameterType.ListString:
|
|
75
|
+
return isinstance(value, list) and all((isinstance(e, str) for e in value))
|
|
76
|
+
case ParameterType.HexBinary:
|
|
77
|
+
try:
|
|
78
|
+
int(value, 16)
|
|
79
|
+
return True
|
|
80
|
+
except Exception:
|
|
81
|
+
return False
|
|
82
|
+
case ParameterType.CSIPAusResource:
|
|
83
|
+
try:
|
|
84
|
+
return CSIPAusResource(value) == value
|
|
85
|
+
except Exception:
|
|
86
|
+
return False
|
|
87
|
+
case ParameterType.ListCSIPAusResource:
|
|
88
|
+
return isinstance(value, list) and all(
|
|
89
|
+
(is_valid_parameter_type(ParameterType.CSIPAusResource, e) for e in value)
|
|
90
|
+
)
|
|
91
|
+
case ParameterType.CSIPAusReadingType:
|
|
92
|
+
try:
|
|
93
|
+
return CSIPAusReadingType(value) == value
|
|
94
|
+
except Exception:
|
|
95
|
+
return False
|
|
96
|
+
case ParameterType.ListCSIPAusReadingType:
|
|
97
|
+
return isinstance(value, list) and all(
|
|
98
|
+
(is_valid_parameter_type(ParameterType.CSIPAusReadingType, e) for e in value)
|
|
99
|
+
)
|
|
100
|
+
case ParameterType.CSIPAusReadingLocation:
|
|
101
|
+
try:
|
|
102
|
+
return CSIPAusReadingLocation(value) == value
|
|
103
|
+
except Exception:
|
|
104
|
+
return False
|
|
105
|
+
case ParameterType.ReadingTypeValues:
|
|
106
|
+
if not value or not isinstance(value, dict):
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
last_length: int | None = None
|
|
110
|
+
for reading_type, reading_vals in value.items():
|
|
111
|
+
if (
|
|
112
|
+
not is_valid_parameter_type(ParameterType.CSIPAusReadingType, reading_type)
|
|
113
|
+
or not isinstance(reading_vals, list)
|
|
114
|
+
or not all((is_valid_parameter_type(ParameterType.Float, rv) for rv in reading_vals))
|
|
115
|
+
):
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
if last_length is None:
|
|
119
|
+
last_length = len(reading_vals)
|
|
120
|
+
elif last_length != len(reading_vals):
|
|
121
|
+
return False
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
raise TestProcedureDefinitionError(f"Unexpected ParameterType: {ParameterType}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def validate_parameters(location: str, parameters: dict[str, Any], valid_schema: dict[str, ParameterSchema]) -> None:
|
|
128
|
+
"""Validates parameters against valid_schema for the specified location label.
|
|
129
|
+
|
|
130
|
+
location: Label to decorate error messages (eg TestProcedureName.Step.Action)
|
|
131
|
+
parameters: The parameters dict to validate
|
|
132
|
+
valid_schema: The schema to validate parameters against. Keys will be the parameter names, value will be the schema
|
|
133
|
+
|
|
134
|
+
raises TestProcedureDefinitionError if parameters is invalid"""
|
|
135
|
+
|
|
136
|
+
# Check the supplied parameters match the schema definition
|
|
137
|
+
for param_name, param_value in parameters.items():
|
|
138
|
+
param_schema = valid_schema.get(param_name, None)
|
|
139
|
+
if param_schema is None:
|
|
140
|
+
raise TestProcedureDefinitionError(
|
|
141
|
+
f"{location} doesn't have a parameter {param_name}. Valid params are {set(valid_schema.keys())}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Check the type
|
|
145
|
+
if not is_valid_parameter_type(param_schema.expected_type, param_value):
|
|
146
|
+
raise TestProcedureDefinitionError(
|
|
147
|
+
f"{location} has parameter {param_name} expecting {param_schema.expected_type} but got {param_value}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Check all mandatory parameters are set
|
|
151
|
+
for param_name, param_schema in valid_schema.items():
|
|
152
|
+
if param_schema.mandatory and param_name not in parameters:
|
|
153
|
+
raise TestProcedureDefinitionError(f"{location} is missing mandatory parameter {param_name}")
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UniqueKeyLoader(yaml.SafeLoader):
|
|
5
|
+
"""Originally sourced from https://gist.github.com/pypt/94d747fe5180851196eb
|
|
6
|
+
Prevents duplicate keys from overwriting eachother instead of raising a ValueError.
|
|
7
|
+
|
|
8
|
+
eg - consider the following YAML, it will parse OK but should be treated as an error:
|
|
9
|
+
|
|
10
|
+
my_class:
|
|
11
|
+
key1: abc
|
|
12
|
+
key1: def
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def construct_mapping(self, node, deep=False):
|
|
16
|
+
mapping = set()
|
|
17
|
+
for key_node, _ in node.value:
|
|
18
|
+
key = self.construct_object(key_node, deep=deep)
|
|
19
|
+
if key in mapping:
|
|
20
|
+
raise ValueError(f"Duplicate {key!r} key found in YAML.")
|
|
21
|
+
mapping.add(key)
|
|
22
|
+
return super().construct_mapping(node, deep)
|