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,149 @@
|
|
|
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
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class ParameterSchema:
|
|
37
|
+
"""What parameters can be passed to a given action/check. Describes a single optional/mandatory field"""
|
|
38
|
+
|
|
39
|
+
mandatory: bool # If this parameter required
|
|
40
|
+
expected_type: ParameterType
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_valid_parameter_type(expected_type: ParameterType, value: Any) -> bool:
|
|
44
|
+
"""Returns true if the specified value "passes" as the expected type. Only performs rudimentary checks to try
|
|
45
|
+
and catch obvious misconfigurations"""
|
|
46
|
+
if value is None:
|
|
47
|
+
return True # We currently allow None to pass to params. Make it a runtime concern
|
|
48
|
+
|
|
49
|
+
if is_resolvable_variable(value):
|
|
50
|
+
return True # Too hard to validate variable expressions. Make it a runtime concern
|
|
51
|
+
|
|
52
|
+
match expected_type:
|
|
53
|
+
case ParameterType.String:
|
|
54
|
+
return isinstance(value, str)
|
|
55
|
+
case ParameterType.Integer:
|
|
56
|
+
if isinstance(value, int):
|
|
57
|
+
return True
|
|
58
|
+
else:
|
|
59
|
+
# Floats/decimals can pass through so long as they have 0 decimal places
|
|
60
|
+
try:
|
|
61
|
+
return int(value) == value
|
|
62
|
+
except Exception:
|
|
63
|
+
return False
|
|
64
|
+
case ParameterType.Float:
|
|
65
|
+
return isinstance(value, float) or isinstance(value, Decimal) or isinstance(value, int)
|
|
66
|
+
case ParameterType.Boolean:
|
|
67
|
+
return isinstance(value, bool)
|
|
68
|
+
case ParameterType.DateTime:
|
|
69
|
+
return isinstance(value, datetime)
|
|
70
|
+
case ParameterType.ListString:
|
|
71
|
+
return isinstance(value, list) and all((isinstance(e, str) for e in value))
|
|
72
|
+
case ParameterType.HexBinary:
|
|
73
|
+
try:
|
|
74
|
+
int(value, 16)
|
|
75
|
+
return True
|
|
76
|
+
except Exception:
|
|
77
|
+
return False
|
|
78
|
+
case ParameterType.CSIPAusResource:
|
|
79
|
+
try:
|
|
80
|
+
return CSIPAusResource(value) == value
|
|
81
|
+
except Exception:
|
|
82
|
+
return False
|
|
83
|
+
case ParameterType.ListCSIPAusResource:
|
|
84
|
+
return isinstance(value, list) and all(
|
|
85
|
+
(is_valid_parameter_type(ParameterType.CSIPAusResource, e) for e in value)
|
|
86
|
+
)
|
|
87
|
+
case ParameterType.CSIPAusReadingType:
|
|
88
|
+
try:
|
|
89
|
+
return CSIPAusReadingType(value) == value
|
|
90
|
+
except Exception:
|
|
91
|
+
return False
|
|
92
|
+
case ParameterType.ListCSIPAusReadingType:
|
|
93
|
+
return isinstance(value, list) and all(
|
|
94
|
+
(is_valid_parameter_type(ParameterType.CSIPAusReadingType, e) for e in value)
|
|
95
|
+
)
|
|
96
|
+
case ParameterType.CSIPAusReadingLocation:
|
|
97
|
+
try:
|
|
98
|
+
return CSIPAusReadingLocation(value) == value
|
|
99
|
+
except Exception:
|
|
100
|
+
return False
|
|
101
|
+
case ParameterType.ReadingTypeValues:
|
|
102
|
+
if not value or not isinstance(value, dict):
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
last_length: int | None = None
|
|
106
|
+
for reading_type, reading_vals in value.items():
|
|
107
|
+
if (
|
|
108
|
+
not is_valid_parameter_type(ParameterType.CSIPAusReadingType, reading_type)
|
|
109
|
+
or not isinstance(reading_vals, list)
|
|
110
|
+
or not all((is_valid_parameter_type(ParameterType.Float, rv) for rv in reading_vals))
|
|
111
|
+
):
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
if last_length is None:
|
|
115
|
+
last_length = len(reading_vals)
|
|
116
|
+
elif last_length != len(reading_vals):
|
|
117
|
+
return False
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
raise TestProcedureDefinitionError(f"Unexpected ParameterType: {ParameterType}")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def validate_parameters(location: str, parameters: dict[str, Any], valid_schema: dict[str, ParameterSchema]) -> None:
|
|
124
|
+
"""Validates parameters against valid_schema for the specified location label.
|
|
125
|
+
|
|
126
|
+
location: Label to decorate error messages (eg TestProcedureName.Step.Action)
|
|
127
|
+
parameters: The parameters dict to validate
|
|
128
|
+
valid_schema: The schema to validate parameters against. Keys will be the parameter names, value will be the schema
|
|
129
|
+
|
|
130
|
+
raises TestProcedureDefinitionError if parameters is invalid"""
|
|
131
|
+
|
|
132
|
+
# Check the supplied parameters match the schema definition
|
|
133
|
+
for param_name, param_value in parameters.items():
|
|
134
|
+
param_schema = valid_schema.get(param_name, None)
|
|
135
|
+
if param_schema is None:
|
|
136
|
+
raise TestProcedureDefinitionError(
|
|
137
|
+
f"{location} doesn't have a parameter {param_name}. Valid params are {set(valid_schema.keys())}"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Check the type
|
|
141
|
+
if not is_valid_parameter_type(param_schema.expected_type, param_value):
|
|
142
|
+
raise TestProcedureDefinitionError(
|
|
143
|
+
f"{location} has parameter {param_name} expecting {param_schema.expected_type} but got {param_value}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Check all mandatory parameters are set
|
|
147
|
+
for param_name, param_schema in valid_schema.items():
|
|
148
|
+
if param_schema.mandatory and param_name not in parameters:
|
|
149
|
+
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)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
|
|
2
|
+
# Server Test Schema
|
|
3
|
+
|
|
4
|
+
This is for the **SERVER** test procedures.
|
|
5
|
+
|
|
6
|
+
At its most basic level, a server test is a series of actions by a virtual "client" that will probe the server and look for unexpected behaviour/responses.
|
|
7
|
+
|
|
8
|
+
## Clients and Context
|
|
9
|
+
|
|
10
|
+
Each test will define one or more "virtual clients" as a precondition. These can be restricted to a specific client type (eg Aggregator or Device) or left unspecified.
|
|
11
|
+
|
|
12
|
+
At the beginning of each test, the client will only know it's LFDI, PEN, PIN and certificate details. Through running actions it will discover resources and populate a local "context" which represents the last seen CSIP-Aus resources. Each client's context is seperate, so if Client A performs discovery, those resources will be invisible to Client B.
|
|
13
|
+
|
|
14
|
+
## Steps Schema
|
|
15
|
+
|
|
16
|
+
The most basic building block of a server `TestProcedure` is a `Step`. Each `Step` will always define a single `Action` which dictates the behaviour of a virtual client (eg sending a particular request) which is then followed by a series of `Check` objects to evaluate. In order for a step to pass it must:
|
|
17
|
+
|
|
18
|
+
1) Execute with any errors (eg - able to successfully make a HTTP request to the utility server)
|
|
19
|
+
2) Have all checks return passed
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
Steps:
|
|
23
|
+
- id: DISCOVERY
|
|
24
|
+
action:
|
|
25
|
+
type: discovery
|
|
26
|
+
parameters:
|
|
27
|
+
resources:
|
|
28
|
+
- DeviceCapability
|
|
29
|
+
- Time
|
|
30
|
+
- MirrorUsagePointList
|
|
31
|
+
- EndDevice
|
|
32
|
+
- DER
|
|
33
|
+
checks:
|
|
34
|
+
- type: discovered
|
|
35
|
+
parameters:
|
|
36
|
+
resources:
|
|
37
|
+
- DeviceCapability
|
|
38
|
+
- Time
|
|
39
|
+
- MirrorUsagePointList
|
|
40
|
+
- EndDevice
|
|
41
|
+
- DER
|
|
42
|
+
links:
|
|
43
|
+
- ConnectionPoint
|
|
44
|
+
- Registration
|
|
45
|
+
- DERCapability
|
|
46
|
+
- DERSettings
|
|
47
|
+
- DERStatus
|
|
48
|
+
- type: end-device
|
|
49
|
+
parameters:
|
|
50
|
+
matches_client: true
|
|
51
|
+
- type: time-synced
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
Step Schema:
|
|
55
|
+
```
|
|
56
|
+
Steps:
|
|
57
|
+
- DESCRIPTIVE_TITLE_OF_STEP: # This is used for display
|
|
58
|
+
action: #
|
|
59
|
+
type: # string identifier of the action type - see table below
|
|
60
|
+
parameters: # Any parameters to modify the default behaviour of the action - see table below
|
|
61
|
+
checks: # A list of Check definitions that will need to be true for this event to trigger - see section on Checks below
|
|
62
|
+
- type: # string identifier of the check type - see table below
|
|
63
|
+
parameters: # Any parameters to modify the default behaviour of the check - see table below
|
|
64
|
+
|
|
65
|
+
client: # The string descriptor of the "Required Client" in the preconditions that will execute this step
|
|
66
|
+
# (Defaults to the first required client)
|
|
67
|
+
|
|
68
|
+
use_client_context: # The string descriptor of the "Required Client" in the preconditions whose context/memory
|
|
69
|
+
# of discovered resources will be used (for testing cross client authentication issues)
|
|
70
|
+
instructions: # List of text strings to render while this test is executing
|
|
71
|
+
repeat_until_pass: # Most steps if failed will abort the test, setting this to true will repeat this step regularly
|
|
72
|
+
# until a pass is recorded (eg - use it to prompt a server to inject a DERControl)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
### Actions
|
|
77
|
+
|
|
78
|
+
These are the currently defined `Action` elements that can be included in a test.
|
|
79
|
+
|
|
80
|
+
This is an example of an `Action` elements that trigger a client to requests links from the device capability URI until all nominated resources are reached.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
action:
|
|
84
|
+
type: discovery
|
|
85
|
+
parameters:
|
|
86
|
+
resources:
|
|
87
|
+
- Time
|
|
88
|
+
- MirrorUsagePointList
|
|
89
|
+
- EndDevice
|
|
90
|
+
- DER
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
| **name** | **params** | **description** |
|
|
95
|
+
| -------- | ---------- | --------------- |
|
|
96
|
+
| `discovery` | `resources: list[CSIPAusResource]` `next_polling_window: bool/None` | Performs a full discovery / refresh of the client's context from DeviceCapability downwards, looking to discover the specific resources. Can be delayed until the next polling window. |
|
|
97
|
+
| `notifications` | `collect: bool` `disable: bool` | If `collect`, consumes subscription notifications and inserts them into the current context, if `disable` causes the subscription notification webhook to simulate an outage (return HTTP 5XX) |
|
|
98
|
+
| `wait` | `duration_seconds: int` | Performs no action for the nominated period of time |
|
|
99
|
+
| `refresh-resource` | `resource: CSIPAusResource` `expect_rejection: bool/None` `expect_rejection_or_empty: bool/None` | Forces a particular resource to be refreshed (using existing hrefs in context). Can be set to expect a HTTP 4XX ErrorResponse and/or an empty list resource (if appropriate). |
|
|
100
|
+
| `insert-end-device` | `force_lfdi: str/None` `expect_rejection: bool/None` | Causes the client to submit a new EndDevice registration and resolves the returned Location header |
|
|
101
|
+
| `upsert-connection-point` | `connectionPointId: str` `expect_rejection: bool/None` | Causes the client to submit a new ConnectionPoint with ID for the client's EndDevice |
|
|
102
|
+
| `upsert-mup` | `mup_id: str` `location: CSIPAusReadingLocation` `reading_types: list[CSIPAusReadingType]` `mmr_mrids: list[str]/None` `pow10_multiplier: int/None` `expect_rejection: bool/None` | Submits a MirrorUsagePoint with MirrorMeterReading's. Will ensure stable MRID values for the same sets of parameters (unless overridden with mmr_mrids). `mup_id` will alias this MirrorUsagePoint for future action calls. |
|
|
103
|
+
| `insert-readings` | `mup_id: str` `values: ReadingTypeValues` `expect_rejection: bool/None` | Begins the submission of readings (at MUP post rate, to an earlier call to `upsert-mup` with the same `mup_id`). Will interleave transmission with subsequent steps (non blocking) if multiple sets of values are specified |
|
|
104
|
+
| `upsert-der-status` | `genConnectStatus: int/None` `operationalModeStatus: int/None` `alarmStatus: int/None` `expect_rejection: bool/None` | Sends DERStatus - validates that the server persisted the values correctly |
|
|
105
|
+
| `upsert-der-capability` | `type: int` `rtgMaxW: int` `modesSupported: int` `doeModesSupported: int` | Sends DERCapability - validates that the server persisted the values correctly |
|
|
106
|
+
| `upsert-der-settings` | `type: int` `setMaxW: int` `setGradW: int` `modesEnabled: int` `doeModesEnabled: int` | Sends DERSettings - validates that the server persisted the values correctly |
|
|
107
|
+
| `send-malformed-der-settings` | `updatedTime_missing: bool` `modesEnabled_int: bool` | Sends a malformed DERSettings - expects a failure and that the server will NOT change anything |
|
|
108
|
+
| `send-malformed-response` | `mrid_unknown: bool` `endDeviceLFDI_unknown: bool` `response_invalid: bool` | Sends a malformed Response (using the most recent DERControl replyTo) - expects a failure response |
|
|
109
|
+
| `create-subscription` | `sub_id: str` `resource: CSIPAusResource` | Sends a new Subscription - validates that the server persisted the values correctly via Location. `sub_id` will alias this subscription for future action calls. |
|
|
110
|
+
| `delete-subscription` | `sub_id: str` | Sends a deletion for a previously created Subscription. |
|
|
111
|
+
| `respond-der-controls` | None | Enumerates all known DERControls and sends a Response for any that require it. |
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
### Checks
|
|
115
|
+
|
|
116
|
+
A `Check` is a boolean test of what the client has in its current context. They are typically defined as a success/failure condition to be run at the end of a `Step`.
|
|
117
|
+
|
|
118
|
+
| **name** | **params** | **description** |
|
|
119
|
+
| -------- | ---------- | --------------- |
|
|
120
|
+
| `discovered` | `resources: list[CSIPAusResource]` `links: list[CSIPAusResource]` | Does the client's context have the nominated resources (or the parent resource with an appropriate link). |
|
|
121
|
+
| `time-sync` | None | Does the client have a TimeResponse and does it closely map to the client's local time. |
|
|
122
|
+
| `end-device` | `matches_client: bool` `matches_pin` | Is there an EndDevice that matches the client's LFDI (can be negatively asserted) and does it have a specific Registration PIN |
|
|
123
|
+
| `der-program` | `minimum_count: int/None` `maximum_count: int/None` `primacy: int/None` `fsa_index: int/None` | Are there enough DERProgram(s) that satisfy the filter criteria? `fsa_index` matches DERPrograms that belong to the nth (0 based) FunctionSetAssignment |
|
|
124
|
+
| `der-control` | `minimum_count: int/None` `maximum_count: int/None` `latest: bool/None` `opModImpLimW: float/None` `opModExpLimW: float/None` `opModLoadLimW: float/None` `opModGenLimW: float/None` `opModEnergize: bool/None` `opModConnect: bool/None` `opModFixedW: float/None` `rampTms: int/None` `randomizeStart: int/None` `event_status: int/None` `responseRequired: int/None` `derp_primacy: int/None` | Are there enough DERProgram(s) that satisfy the filter criteria? `latest` will ONLY match the most recent DERControl. |
|
|
125
|
+
| `default-der-control` | `opModExpLimW: float/None` `opModLoadLimW: float/None` `opModGenLimW: float/None` `setGradW: int/None` | matches any DefaultDERControl with the specified values |
|
|
126
|
+
| `mirror-usage-point` | `matches: bool` `location: CSIPAusReadingLocation/None` `reading_types: list[CSIPAusReadingType]/None` `mmr_mrids: list[str]/None` `post_rate_seconds: int/None` | Does a MirrorUsagePoint exist with the specified values (or not exist if `matches` is false). Only asserts specified values. |
|
|
127
|
+
| `subscription` | `matches: bool` `resource: CSIPAusResource`| Does a Subscription exist for the specified resource (or not exist if `matches` is false). |
|
|
128
|
+
| `poll-rate` | `resource: CSIPAusResource` `poll_rate_seconds: int` | Does the nominated resource have the specified poll rate. |
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
### Parameter Variable Resolution
|
|
132
|
+
|
|
133
|
+
Any `parameter` element expects a series of name/value pairs to pass to the "parent" `Action` or `Check` . For example:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
parameters:
|
|
137
|
+
number_param: 123
|
|
138
|
+
text_param: Text Content
|
|
139
|
+
date_param: 2020-01-02 03:04:05Z
|
|
140
|
+
csip_aus_resource: EndDeviceList
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
But placeholder variables may also be used to reference things that aren't known until the test is underway. For example, the following would instead set `number_param` to the current setMaxW supplied by the client while `date_param` would be set to the moment in time that the `Action`, `Check` or `Event` is being evaluated.
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
parameters:
|
|
148
|
+
number_param: $setMaxW
|
|
149
|
+
text_param: Text Content
|
|
150
|
+
date_param: $now
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The following are all the `NamedVariable` types currently implemented (these are distinct from the named variables
|
|
155
|
+
defined in the client test procedures)
|
|
156
|
+
|
|
157
|
+
| **name** | **description** |
|
|
158
|
+
| -------- | --------------- |
|
|
159
|
+
| `$now` | Resolves to the current moment in time (timezone aware). Returns a datetime |
|
|
160
|
+
| `$setMaxW` | Resolves to the current client configuration value for `DERSetting.setMaxW` as a number. |
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
Expressions are also supported.
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
parameters:
|
|
167
|
+
number_param: $(setMaxW / 2)
|
|
168
|
+
text_param: Text Content
|
|
169
|
+
date_param: $(now - '5 mins')
|
|
170
|
+
```
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from cactus_test_definitions.errors import TestProcedureDefinitionError
|
|
5
|
+
from cactus_test_definitions.parameters import (
|
|
6
|
+
ParameterSchema,
|
|
7
|
+
ParameterType,
|
|
8
|
+
validate_parameters,
|
|
9
|
+
)
|
|
10
|
+
from cactus_test_definitions.variable_expressions import (
|
|
11
|
+
parse_variable_expression_body,
|
|
12
|
+
try_extract_variable_expression,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Action:
|
|
18
|
+
type: str
|
|
19
|
+
client: str | None = None # use the client with this id to execute this action. If None, use the 0th client
|
|
20
|
+
parameters: dict[str, Any] = None # type: ignore # This will be forced in __post_init__
|
|
21
|
+
|
|
22
|
+
def __post_init__(self):
|
|
23
|
+
"""Some parameter values might contain variable expressions (eg: a string "$now") that needs to be replaced
|
|
24
|
+
with an parsed Expression object instead."""
|
|
25
|
+
if self.parameters is None:
|
|
26
|
+
self.parameters = {}
|
|
27
|
+
|
|
28
|
+
for k, v in self.parameters.items():
|
|
29
|
+
variable_expr = try_extract_variable_expression(v)
|
|
30
|
+
if variable_expr:
|
|
31
|
+
self.parameters[k] = parse_variable_expression_body(variable_expr, k)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# The parameter schema for each action, keyed by the action name
|
|
35
|
+
ACTION_PARAMETER_SCHEMA: dict[str, dict[str, ParameterSchema]] = {
|
|
36
|
+
"discovery": {
|
|
37
|
+
"resources": ParameterSchema(True, ParameterType.ListCSIPAusResource), # What resources to try and resolve?
|
|
38
|
+
"next_polling_window": ParameterSchema(
|
|
39
|
+
False, ParameterType.Boolean
|
|
40
|
+
), # If set - delay this until the upcoming polling window (eg- wait for the next whole minute)
|
|
41
|
+
}, # Performs a full discovery / refresh of the client's context from DeviceCapability downwards
|
|
42
|
+
"notifications": {
|
|
43
|
+
"collect": ParameterSchema(
|
|
44
|
+
False, ParameterType.Boolean
|
|
45
|
+
), # Collects latest subscription notifications into context
|
|
46
|
+
"disable": ParameterSchema(False, ParameterType.Boolean), # Simulates HTTP 5XX outage at the endpoint
|
|
47
|
+
},
|
|
48
|
+
"wait": {
|
|
49
|
+
"duration_seconds": ParameterSchema(True, ParameterType.Integer)
|
|
50
|
+
}, # Waits (doing nothing - blocking other step actions) until the specified time period has passed
|
|
51
|
+
"comms-status": {
|
|
52
|
+
"notifications_enabled": ParameterSchema(True, ParameterType.Boolean) # Enable/Disble notification webhook
|
|
53
|
+
}, # Enables or disables certain communications
|
|
54
|
+
"refresh-resource": {
|
|
55
|
+
"resource": ParameterSchema(True, ParameterType.CSIPAusResource),
|
|
56
|
+
"expect_rejection": ParameterSchema(False, ParameterType.Boolean), # if set - expect 4XX and ErrorPayload
|
|
57
|
+
"expect_rejection_or_empty": ParameterSchema(
|
|
58
|
+
False, ParameterType.Boolean
|
|
59
|
+
), # Similar to expect_rejection but also allow en empty list (if it's a list resource)
|
|
60
|
+
}, # Force an existing resource (in the client's context) to be re-fetched via href. Updates context on success
|
|
61
|
+
"insert-end-device": {
|
|
62
|
+
"force_lfdi": ParameterSchema(False, ParameterType.String), # Forces the use of this LFDI
|
|
63
|
+
"expect_rejection": ParameterSchema(False, ParameterType.Boolean), # If set - expect 4XX and ErrorPayload
|
|
64
|
+
}, # Inserts an EndDevice and then validates the returned Location header
|
|
65
|
+
"upsert-connection-point": {
|
|
66
|
+
"connectionPointId": ParameterSchema(True, ParameterType.String),
|
|
67
|
+
"expect_rejection": ParameterSchema(False, ParameterType.Boolean), # If set - expect ErrorPayload reasonCode 1
|
|
68
|
+
},
|
|
69
|
+
"upsert-mup": {
|
|
70
|
+
"mup_id": ParameterSchema(True, ParameterType.String), # Used to alias the returned MUP ID
|
|
71
|
+
"location": ParameterSchema(True, ParameterType.CSIPAusReadingLocation),
|
|
72
|
+
"reading_types": ParameterSchema(True, ParameterType.ListCSIPAusReadingType),
|
|
73
|
+
"expect_rejection": ParameterSchema(False, ParameterType.Boolean), # If set - expect 4XX and ErrorPayload
|
|
74
|
+
"mmr_mrids": ParameterSchema(
|
|
75
|
+
False, ParameterType.ListString
|
|
76
|
+
), # Must correspond 1-1 with reading_types. Used for forcing specific mrid values
|
|
77
|
+
"pow10_multiplier": ParameterSchema(
|
|
78
|
+
False, ParameterType.Integer
|
|
79
|
+
), # Force the use a particular pow10. Defaults to 0 otherwise
|
|
80
|
+
}, # Register a MUP with the specified values. MMR's based on hash of current client / reading types
|
|
81
|
+
"insert-readings": {
|
|
82
|
+
"mup_id": ParameterSchema(True, ParameterType.String), # Must be previously defined with register-mup
|
|
83
|
+
"values": ParameterSchema(
|
|
84
|
+
True, ParameterType.ReadingTypeValues
|
|
85
|
+
), # The sequences of values to send at the MUP post rate
|
|
86
|
+
"expect_rejection": ParameterSchema(False, ParameterType.Boolean), # If set - expect 4XX and ErrorPayload
|
|
87
|
+
}, # Sends readings - validates that the telemetry is parsed correctly by the server
|
|
88
|
+
"upsert-der-status": {
|
|
89
|
+
"genConnectStatus": ParameterSchema(False, ParameterType.Integer),
|
|
90
|
+
"operationalModeStatus": ParameterSchema(False, ParameterType.Integer),
|
|
91
|
+
"alarmStatus": ParameterSchema(False, ParameterType.Integer),
|
|
92
|
+
"expect_rejection": ParameterSchema(False, ParameterType.Boolean), # If set - expect 4XX and ErrorPayload
|
|
93
|
+
}, # Sends DERStatus - validates that the server persisted the values correctly
|
|
94
|
+
"upsert-der-capability": {
|
|
95
|
+
"type": ParameterSchema(True, ParameterType.Integer),
|
|
96
|
+
"rtgMaxW": ParameterSchema(True, ParameterType.Integer),
|
|
97
|
+
"modesSupported": ParameterSchema(True, ParameterType.Integer),
|
|
98
|
+
"doeModesSupported": ParameterSchema(True, ParameterType.Integer),
|
|
99
|
+
}, # Sends DERCapability - validates that the server persisted the values correctly
|
|
100
|
+
"upsert-der-settings": {
|
|
101
|
+
"setMaxW": ParameterSchema(True, ParameterType.Integer),
|
|
102
|
+
"setGradW": ParameterSchema(True, ParameterType.Integer),
|
|
103
|
+
"modesEnabled": ParameterSchema(True, ParameterType.Integer),
|
|
104
|
+
"doeModesEnabled": ParameterSchema(True, ParameterType.Integer),
|
|
105
|
+
}, # Sends DERSettings - validates that the server persisted the values correctly
|
|
106
|
+
"send-malformed-der-settings": {
|
|
107
|
+
"updatedTime_missing": ParameterSchema(True, ParameterType.Boolean), # If true - updatedTime will be stripped
|
|
108
|
+
"modesEnabled_int": ParameterSchema(True, ParameterType.Boolean), # If true - modesEnabled will send an int
|
|
109
|
+
}, # Sends a malformed DERSettings - expects a failure and that the server will NOT change anything
|
|
110
|
+
"send-malformed-response": {
|
|
111
|
+
"mrid_unknown": ParameterSchema(True, ParameterType.Boolean), # If true - mrid will be random
|
|
112
|
+
"endDeviceLFDI_unknown": ParameterSchema(True, ParameterType.Boolean), # If true - endDeviceLfdi will be random
|
|
113
|
+
"response_invalid": ParameterSchema(True, ParameterType.Boolean), # If true - response will be a reserved value
|
|
114
|
+
}, # Sends a malformed Response (using the most recent DERControl replyTo) - expects a failure response
|
|
115
|
+
"create-subscription": {
|
|
116
|
+
"sub_id": ParameterSchema(True, ParameterType.String), # Used to alias the returned subscription ID
|
|
117
|
+
"resource": ParameterSchema(True, ParameterType.CSIPAusResource),
|
|
118
|
+
}, # Sends a new Subscription - validates that the server persisted the values correctly via Location
|
|
119
|
+
"delete-subscription": {
|
|
120
|
+
"sub_id": ParameterSchema(True, ParameterType.String), # Must match a previously
|
|
121
|
+
}, # Sends a Subscription deletion
|
|
122
|
+
"respond-der-controls": {}, # Enumerates all known DERControls and sends a Response for any that require it
|
|
123
|
+
}
|
|
124
|
+
VALID_ACTION_NAMES: set[str] = set(ACTION_PARAMETER_SCHEMA.keys())
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def validate_action_parameters(procedure_name: str, step_name: int, action: Action) -> None:
|
|
128
|
+
"""Validates the action parameters for the parent TestProcedure based on the ACTION_PARAMETER_SCHEMA
|
|
129
|
+
|
|
130
|
+
raises TestProcedureDefinitionError on failure"""
|
|
131
|
+
location = f"{procedure_name}.step[{step_name}]" # Descriptive location
|
|
132
|
+
|
|
133
|
+
parameter_schema = ACTION_PARAMETER_SCHEMA.get(action.type, None)
|
|
134
|
+
if parameter_schema is None:
|
|
135
|
+
raise TestProcedureDefinitionError(
|
|
136
|
+
f"{location} has an invalid action name '{action.type}'. Valid Names: {VALID_ACTION_NAMES}"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
validate_parameters(location, action.parameters, parameter_schema)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from cactus_test_definitions.errors import TestProcedureDefinitionError
|
|
5
|
+
from cactus_test_definitions.parameters import (
|
|
6
|
+
ParameterSchema,
|
|
7
|
+
ParameterType,
|
|
8
|
+
validate_parameters,
|
|
9
|
+
)
|
|
10
|
+
from cactus_test_definitions.variable_expressions import (
|
|
11
|
+
parse_variable_expression_body,
|
|
12
|
+
try_extract_variable_expression,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Check:
|
|
18
|
+
"""A check represents some validation logic that runs during a Test Step and provides a pass/fail result with a
|
|
19
|
+
description. It will typically inspect the state of the client based on what it has seen from the server
|
|
20
|
+
|
|
21
|
+
eg: Ensuring that the client was able to see an EndDevice registration"""
|
|
22
|
+
|
|
23
|
+
type: str
|
|
24
|
+
parameters: dict[str, Any] = None # type: ignore # This will be forced in __post_init__
|
|
25
|
+
|
|
26
|
+
def __post_init__(self):
|
|
27
|
+
"""Some parameter values might contain variable expressions (eg: a string "$now") that needs to be replaced
|
|
28
|
+
with an parsed Expression object instead."""
|
|
29
|
+
if self.parameters is None:
|
|
30
|
+
self.parameters = {}
|
|
31
|
+
for k, v in self.parameters.items():
|
|
32
|
+
variable_expr = try_extract_variable_expression(v)
|
|
33
|
+
if variable_expr:
|
|
34
|
+
self.parameters[k] = parse_variable_expression_body(variable_expr, k)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# The parameter schema for each action, keyed by the action name
|
|
38
|
+
CHECK_PARAMETER_SCHEMA: dict[str, dict[str, ParameterSchema]] = {
|
|
39
|
+
"discovered": {
|
|
40
|
+
"resources": ParameterSchema(False, ParameterType.ListCSIPAusResource),
|
|
41
|
+
"links": ParameterSchema(False, ParameterType.ListCSIPAusResource),
|
|
42
|
+
},
|
|
43
|
+
"time-synced": {}, # Passes if the current Time resource is synced with this client's date/time
|
|
44
|
+
"end-device": {
|
|
45
|
+
"matches_client": ParameterSchema(
|
|
46
|
+
True, ParameterType.Boolean
|
|
47
|
+
), # assert the existence / non existence of an EndDevice for the current client
|
|
48
|
+
"matches_pin": ParameterSchema(
|
|
49
|
+
False, ParameterType.Boolean
|
|
50
|
+
), # if set - The matches_client criteria will ALSO check the registration PIN for the EndDevice. Default False
|
|
51
|
+
},
|
|
52
|
+
"der-program": {
|
|
53
|
+
"minimum_count": ParameterSchema(False, ParameterType.Integer), # Needs at least this many derps to pass
|
|
54
|
+
"maximum_count": ParameterSchema(False, ParameterType.Integer), # Needs at most this many derps to pass
|
|
55
|
+
"primacy": ParameterSchema(False, ParameterType.Integer), # Filters derps based on this primacy value
|
|
56
|
+
"fsa_index": ParameterSchema(
|
|
57
|
+
False, ParameterType.Integer
|
|
58
|
+
), # Filters derps that belong to the nth (0 based) FunctionSetAssignment index
|
|
59
|
+
},
|
|
60
|
+
"der-control": {
|
|
61
|
+
"minimum_count": ParameterSchema(False, ParameterType.Integer), # Needs at least this many controls to pass
|
|
62
|
+
"maximum_count": ParameterSchema(False, ParameterType.Integer), # Needs at most this many controls to pass
|
|
63
|
+
"latest": ParameterSchema(False, ParameterType.Boolean), # forces filter checks against the most recent control
|
|
64
|
+
"opModImpLimW": ParameterSchema(False, ParameterType.Float), # Filters controls based on this value
|
|
65
|
+
"opModExpLimW": ParameterSchema(False, ParameterType.Float), # Filters controls based on this value
|
|
66
|
+
"opModLoadLimW": ParameterSchema(False, ParameterType.Float), # Filters controls based on this value
|
|
67
|
+
"opModGenLimW": ParameterSchema(False, ParameterType.Float), # Filters controls based on this value
|
|
68
|
+
"opModEnergize": ParameterSchema(False, ParameterType.Boolean), # Filters controls based on this value
|
|
69
|
+
"opModConnect": ParameterSchema(False, ParameterType.Boolean), # Filters controls based on this value
|
|
70
|
+
"opModFixedW": ParameterSchema(False, ParameterType.Float), # Filters controls based on this value
|
|
71
|
+
"rampTms": ParameterSchema(False, ParameterType.Integer), # Filter on this val. 0 means negative assertion
|
|
72
|
+
"randomizeStart": ParameterSchema(False, ParameterType.Integer), # Filter on this val (in seconds)
|
|
73
|
+
"event_status": ParameterSchema(False, ParameterType.Integer), # Filter on Event.status value
|
|
74
|
+
"responseRequired": ParameterSchema(False, ParameterType.Integer), # Filter on responseRequired value
|
|
75
|
+
"derp_primacy": ParameterSchema(
|
|
76
|
+
False, ParameterType.Integer
|
|
77
|
+
), # Filter to control's belonging to a DERProgram with this primacy value
|
|
78
|
+
}, # Matches many DERControls (specified by minimum_count) against additional other filter criteria
|
|
79
|
+
"default-der-control": {
|
|
80
|
+
"opModImpLimW": ParameterSchema(False, ParameterType.Float),
|
|
81
|
+
"opModExpLimW": ParameterSchema(False, ParameterType.Float),
|
|
82
|
+
"opModGenLimW": ParameterSchema(False, ParameterType.Float),
|
|
83
|
+
"opModLoadLimW": ParameterSchema(False, ParameterType.Float),
|
|
84
|
+
"setGradW": ParameterSchema(False, ParameterType.Integer), # Hundredths of a percent / second
|
|
85
|
+
}, # matches any DefaultDERControl with the specified values
|
|
86
|
+
"mirror-usage-point": {
|
|
87
|
+
"matches": ParameterSchema(True, ParameterType.Boolean), # True for positive assert, False for negative assert
|
|
88
|
+
"location": ParameterSchema(False, ParameterType.CSIPAusReadingLocation), # If not specified - match anything
|
|
89
|
+
"reading_types": ParameterSchema(False, ParameterType.ListCSIPAusReadingType), # If not specified - match all
|
|
90
|
+
"mmr_mrids": ParameterSchema(
|
|
91
|
+
False, ParameterType.ListString
|
|
92
|
+
), # Must correspond 1-1 with reading_types. Used for forcing specific mrid values
|
|
93
|
+
"post_rate_seconds": ParameterSchema(False, ParameterType.Integer), # Only asserted if specified
|
|
94
|
+
}, # True if the matches assertion finds a MirrorUsagePoint with the specified parameters (requires exact match)
|
|
95
|
+
"subscription": {
|
|
96
|
+
"matches": ParameterSchema(True, ParameterType.Boolean), # True for positive assert, False for negative assert
|
|
97
|
+
"resource": ParameterSchema(True, ParameterType.CSIPAusResource),
|
|
98
|
+
}, # Matches the existence/nonexistence of a subscription for the specified resource
|
|
99
|
+
"poll-rate": {
|
|
100
|
+
"resource": ParameterSchema(True, ParameterType.CSIPAusResource),
|
|
101
|
+
"poll_rate_seconds": ParameterSchema(True, ParameterType.Integer),
|
|
102
|
+
}, # Asserts a specific poll rate value
|
|
103
|
+
}
|
|
104
|
+
VALID_CHECK_NAMES: set[str] = set(CHECK_PARAMETER_SCHEMA.keys())
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def validate_check_parameters(procedure_name: str, check: Check) -> None:
|
|
108
|
+
"""Validates the check parameters for the parent TestProcedure based on the CHECK_PARAMETER_SCHEMA
|
|
109
|
+
|
|
110
|
+
raises TestProcedureDefinitionError on failure"""
|
|
111
|
+
location = f"{procedure_name} Check: {check.type}" # Descriptive location of this action being validated
|
|
112
|
+
|
|
113
|
+
parameter_schema = CHECK_PARAMETER_SCHEMA.get(check.type, None)
|
|
114
|
+
if parameter_schema is None:
|
|
115
|
+
raise TestProcedureDefinitionError(f"{location} not a valid action name. {VALID_CHECK_NAMES}")
|
|
116
|
+
|
|
117
|
+
validate_parameters(location, check.parameters, parameter_schema)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Description: Discovery with Out-Of-Band registration
|
|
2
|
+
Category: Registration
|
|
3
|
+
Classes:
|
|
4
|
+
- A
|
|
5
|
+
|
|
6
|
+
TargetVersions:
|
|
7
|
+
- v1.2
|
|
8
|
+
|
|
9
|
+
Preconditions:
|
|
10
|
+
required_clients:
|
|
11
|
+
- id: client
|
|
12
|
+
|
|
13
|
+
Steps:
|
|
14
|
+
- id: DISCOVERY
|
|
15
|
+
action:
|
|
16
|
+
type: discovery
|
|
17
|
+
parameters:
|
|
18
|
+
resources:
|
|
19
|
+
- DeviceCapability
|
|
20
|
+
- Time
|
|
21
|
+
- MirrorUsagePointList
|
|
22
|
+
- EndDevice
|
|
23
|
+
- DER
|
|
24
|
+
checks:
|
|
25
|
+
- type: discovered
|
|
26
|
+
parameters:
|
|
27
|
+
resources:
|
|
28
|
+
- DeviceCapability
|
|
29
|
+
- Time
|
|
30
|
+
- MirrorUsagePointList
|
|
31
|
+
- EndDevice
|
|
32
|
+
- DER
|
|
33
|
+
links:
|
|
34
|
+
- ConnectionPoint
|
|
35
|
+
- Registration
|
|
36
|
+
- DERCapability
|
|
37
|
+
- DERSettings
|
|
38
|
+
- DERStatus
|
|
39
|
+
- type: end-device
|
|
40
|
+
parameters:
|
|
41
|
+
matches_client: true
|
|
42
|
+
- type: time-synced
|