robotframework-schemathesislibrary 0.52.0__py3-none-any.whl → 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 robotframework-schemathesislibrary might be problematic. Click here for more details.
- SchemathesisLibrary/__init__.py +20 -78
- SchemathesisLibrary/schemathesisreader.py +96 -0
- {robotframework_schemathesislibrary-0.52.0.dist-info → robotframework_schemathesislibrary-1.0.0.dist-info}/METADATA +1 -1
- robotframework_schemathesislibrary-1.0.0.dist-info/RECORD +7 -0
- robotframework_schemathesislibrary-0.52.0.dist-info/RECORD +0 -6
- {robotframework_schemathesislibrary-0.52.0.dist-info → robotframework_schemathesislibrary-1.0.0.dist-info}/WHEEL +0 -0
- {robotframework_schemathesislibrary-0.52.0.dist-info → robotframework_schemathesislibrary-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {robotframework_schemathesislibrary-0.52.0.dist-info → robotframework_schemathesislibrary-1.0.0.dist-info}/top_level.txt +0 -0
SchemathesisLibrary/__init__.py
CHANGED
|
@@ -11,69 +11,25 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
from
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Any
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
17
15
|
|
|
18
16
|
from DataDriver import DataDriver # type: ignore
|
|
19
|
-
from DataDriver.AbstractReaderClass import AbstractReaderClass # type: ignore
|
|
20
|
-
from DataDriver.ReaderConfig import TestCaseData # type: ignore
|
|
21
|
-
from hypothesis import HealthCheck, Phase, Verbosity, given, settings
|
|
22
|
-
from hypothesis import strategies as st
|
|
23
17
|
from robot.api import logger
|
|
24
18
|
from robot.api.deco import keyword
|
|
25
19
|
from robot.result.model import TestCase as ResultTestCase # type: ignore
|
|
26
20
|
from robot.result.model import TestSuite as ResultTestSuite # type: ignore
|
|
27
21
|
from robot.running.model import TestCase, TestSuite # type: ignore
|
|
28
22
|
from robotlibcore import DynamicCore # type: ignore
|
|
29
|
-
from schemathesis import Case
|
|
23
|
+
from schemathesis import Case
|
|
30
24
|
from schemathesis.core import NotSet
|
|
31
|
-
from schemathesis.core.result import Ok
|
|
32
25
|
from schemathesis.core.transport import Response
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
headers: dict[str, Any] | None = None
|
|
41
|
-
path: "Path|None" = None
|
|
42
|
-
url: "str|None" = None
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class SchemathesisReader(AbstractReaderClass):
|
|
46
|
-
options: "Options|None" = None
|
|
47
|
-
|
|
48
|
-
def get_data_from_source(self) -> list[TestCaseData]:
|
|
49
|
-
if not self.options:
|
|
50
|
-
raise ValueError("Options must be set before calling get_data_from_source.")
|
|
51
|
-
url = self.options.url
|
|
52
|
-
path = self.options.path
|
|
53
|
-
if path and not Path(path).is_file():
|
|
54
|
-
raise ValueError(f"Provided path '{path}' is not a valid file.")
|
|
55
|
-
if path:
|
|
56
|
-
schema = openapi.from_path(path)
|
|
57
|
-
elif url:
|
|
58
|
-
headers = self.options.headers or {}
|
|
59
|
-
schema = openapi.from_url(url, headers=headers)
|
|
60
|
-
else:
|
|
61
|
-
raise ValueError("Either 'url' or 'path' must be provided to SchemathesisLibrary.")
|
|
62
|
-
all_cases: list[TestCaseData] = []
|
|
63
|
-
for op in schema.get_all_operations():
|
|
64
|
-
if isinstance(op, Ok):
|
|
65
|
-
# NOTE: (dd): `as_strategy` also accepts GenerationMode
|
|
66
|
-
# It could be used to produce positive / negative tests
|
|
67
|
-
strategy = op.ok().as_strategy().map(from_case) # type: ignore
|
|
68
|
-
add_examples(strategy, all_cases, self.options.max_examples) # type: ignore
|
|
69
|
-
return all_cases
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def from_case(case: Case) -> TestCaseData:
|
|
73
|
-
return TestCaseData(
|
|
74
|
-
test_case_name=f"{case.operation.label} - {case.id}",
|
|
75
|
-
arguments={"${case}": case},
|
|
76
|
-
)
|
|
27
|
+
from .schemathesisreader import Options, SchemathesisReader
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
__version__ = "1.0.0"
|
|
77
33
|
|
|
78
34
|
|
|
79
35
|
class SchemathesisLibrary(DynamicCore):
|
|
@@ -121,17 +77,21 @@ class SchemathesisLibrary(DynamicCore):
|
|
|
121
77
|
max_examples: int = 5,
|
|
122
78
|
path: "Path|None" = None,
|
|
123
79
|
url: "str|None" = None,
|
|
80
|
+
auth: str | None = None,
|
|
124
81
|
) -> None:
|
|
125
82
|
"""The SchemathesisLibrary can be initialized with the following arguments:
|
|
126
83
|
|
|
127
84
|
| =Argument= | =Description= |
|
|
128
|
-
| `headers` | Optional HTTP headers to be used schema is downloaded from `url`. |
|
|
85
|
+
| `headers` | Optional HTTP headers to be used when schema is downloaded from `url`. |
|
|
129
86
|
| `max_examples` | Maximum number of examples to generate for each operation. Default is 5. |
|
|
130
87
|
| `path` | Path to the OpenAPI schema file. Using either `path` or `url` is mandatory. |
|
|
131
88
|
| `url` | URL where the OpenAPI schema can be downloaded. |
|
|
89
|
+
| `auth` | Optional authentication class to be used passed for Schemathesis authentication when test cases are executed. |
|
|
132
90
|
"""
|
|
133
91
|
self.ROBOT_LIBRARY_LISTENER = self
|
|
134
|
-
SchemathesisReader.options = Options(
|
|
92
|
+
SchemathesisReader.options = Options(
|
|
93
|
+
headers=headers, max_examples=max_examples, path=path, url=url, auth=auth
|
|
94
|
+
)
|
|
135
95
|
self.data_driver = DataDriver(reader_class=SchemathesisReader)
|
|
136
96
|
DynamicCore.__init__(self, [])
|
|
137
97
|
|
|
@@ -146,9 +106,8 @@ class SchemathesisLibrary(DynamicCore):
|
|
|
146
106
|
self,
|
|
147
107
|
case: Case,
|
|
148
108
|
*,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
headers: "dict[str, Any]|None" = None,
|
|
109
|
+
base_url: str | None = None,
|
|
110
|
+
headers: dict[str, Any] | None = None,
|
|
152
111
|
) -> Response:
|
|
153
112
|
"""Call and validate a Schemathesis case.
|
|
154
113
|
|
|
@@ -157,7 +116,7 @@ class SchemathesisLibrary(DynamicCore):
|
|
|
157
116
|
"""
|
|
158
117
|
self.info(f"Case: {case.path} | {case.method} | {case.path_parameters}")
|
|
159
118
|
self._log_case(case, headers)
|
|
160
|
-
response = case.call_and_validate(base_url=base_url, headers=headers
|
|
119
|
+
response = case.call_and_validate(base_url=base_url, headers=headers)
|
|
161
120
|
self._log_request(response)
|
|
162
121
|
self.debug(f"Response: {response.headers} | {response.status_code} | {response.text}")
|
|
163
122
|
return response
|
|
@@ -167,9 +126,8 @@ class SchemathesisLibrary(DynamicCore):
|
|
|
167
126
|
self,
|
|
168
127
|
case: Case,
|
|
169
128
|
*,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
headers: "dict[str, Any]|None" = None,
|
|
129
|
+
base_url: str | None = None,
|
|
130
|
+
headers: dict[str, Any] | None = None,
|
|
173
131
|
) -> Response:
|
|
174
132
|
"""Call a Schemathesis case without validation.
|
|
175
133
|
|
|
@@ -182,7 +140,7 @@ class SchemathesisLibrary(DynamicCore):
|
|
|
182
140
|
"""
|
|
183
141
|
self.info(f"Calling case: {case.path} | {case.method} | {case.path_parameters}")
|
|
184
142
|
self._log_case(case)
|
|
185
|
-
response = case.call(base_url=base_url, headers=headers
|
|
143
|
+
response = case.call(base_url=base_url, headers=headers)
|
|
186
144
|
self._log_request(response)
|
|
187
145
|
return response
|
|
188
146
|
|
|
@@ -222,19 +180,3 @@ class SchemathesisLibrary(DynamicCore):
|
|
|
222
180
|
f"Request: {resposen.request.method} {resposen.request.url} "
|
|
223
181
|
f"headers: {resposen.request.headers!r} body: {resposen.request.body!r}"
|
|
224
182
|
)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def add_examples(strategy: st.SearchStrategy, container: list[TestCaseData], max_examples: int) -> None:
|
|
228
|
-
@given(strategy)
|
|
229
|
-
@settings(
|
|
230
|
-
database=None,
|
|
231
|
-
max_examples=max_examples,
|
|
232
|
-
deadline=None,
|
|
233
|
-
verbosity=Verbosity.quiet,
|
|
234
|
-
phases=(Phase.generate,),
|
|
235
|
-
suppress_health_check=list(HealthCheck),
|
|
236
|
-
)
|
|
237
|
-
def example_generating_inner_function(ex: Any) -> None:
|
|
238
|
-
container.append(ex)
|
|
239
|
-
|
|
240
|
-
example_generating_inner_function()
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Copyright 2025- Tatu Aalto
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from DataDriver.AbstractReaderClass import AbstractReaderClass # type: ignore
|
|
19
|
+
from DataDriver.ReaderConfig import TestCaseData # type: ignore
|
|
20
|
+
from hypothesis import HealthCheck, Phase, Verbosity, given, settings
|
|
21
|
+
from hypothesis import strategies as st
|
|
22
|
+
from robot.api import logger
|
|
23
|
+
from robot.utils.importer import Importer # type: ignore
|
|
24
|
+
from schemathesis import Case, openapi
|
|
25
|
+
from schemathesis.core.result import Ok
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class Options:
|
|
30
|
+
max_examples: int
|
|
31
|
+
headers: dict[str, Any] | None = None
|
|
32
|
+
path: "Path|None" = None
|
|
33
|
+
url: str | None = None
|
|
34
|
+
auth: str | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SchemathesisReader(AbstractReaderClass):
|
|
38
|
+
options: "Options|None" = None
|
|
39
|
+
|
|
40
|
+
def get_data_from_source(self) -> list[TestCaseData]:
|
|
41
|
+
if not self.options:
|
|
42
|
+
raise ValueError("Options must be set before calling get_data_from_source.")
|
|
43
|
+
url = self.options.url
|
|
44
|
+
path = self.options.path
|
|
45
|
+
if path and not Path(path).is_file():
|
|
46
|
+
raise ValueError(f"Provided path '{path}' is not a valid file.")
|
|
47
|
+
if path:
|
|
48
|
+
schema = openapi.from_path(path)
|
|
49
|
+
elif url:
|
|
50
|
+
headers = self.options.headers or {}
|
|
51
|
+
schema = openapi.from_url(url, headers=headers)
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError("Either 'url' or 'path' must be provided to SchemathesisLibrary.")
|
|
54
|
+
all_cases: list[TestCaseData] = []
|
|
55
|
+
if self.options.auth:
|
|
56
|
+
import_extensions(self.options.auth)
|
|
57
|
+
logger.info(f"Using auth extension from: {self.options.auth}")
|
|
58
|
+
|
|
59
|
+
for op in schema.get_all_operations():
|
|
60
|
+
if isinstance(op, Ok):
|
|
61
|
+
# NOTE: (dd): `as_strategy` also accepts GenerationMode
|
|
62
|
+
# It could be used to produce positive / negative tests
|
|
63
|
+
strategy = op.ok().as_strategy().map(from_case) # type: ignore
|
|
64
|
+
add_examples(strategy, all_cases, self.options.max_examples) # type: ignore
|
|
65
|
+
return all_cases
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def from_case(case: Case) -> TestCaseData:
|
|
69
|
+
return TestCaseData(
|
|
70
|
+
test_case_name=f"{case.operation.label} - {case.id}",
|
|
71
|
+
arguments={"${case}": case},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def add_examples(strategy: st.SearchStrategy, container: list[TestCaseData], max_examples: int) -> None:
|
|
76
|
+
@given(strategy)
|
|
77
|
+
@settings(
|
|
78
|
+
database=None,
|
|
79
|
+
max_examples=max_examples,
|
|
80
|
+
deadline=None,
|
|
81
|
+
verbosity=Verbosity.quiet,
|
|
82
|
+
phases=(Phase.generate,),
|
|
83
|
+
suppress_health_check=list(HealthCheck),
|
|
84
|
+
)
|
|
85
|
+
def example_generating_inner_function(ex: Any) -> None:
|
|
86
|
+
container.append(ex)
|
|
87
|
+
|
|
88
|
+
example_generating_inner_function()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def import_extensions(library: str | Path) -> Any:
|
|
92
|
+
"""Import any extensions for SchemathesisLibrary."""
|
|
93
|
+
importer = Importer("test library")
|
|
94
|
+
lib = importer.import_module(library)
|
|
95
|
+
logger.info(f"Imported extension module: {lib}")
|
|
96
|
+
return lib
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robotframework-schemathesislibrary
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Robot Framework SchemathesisLibrary to automatically create test cases from API specifications.
|
|
5
5
|
Author-email: Tatu Aalto <aalto.tatu@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
SchemathesisLibrary/__init__.py,sha256=eAlV97tkZtcC4pC1XYj_z6YLjfYXX4zTiOUeweHu51Y,6991
|
|
2
|
+
SchemathesisLibrary/schemathesisreader.py,sha256=MmA0ZEtIDjIGwKUZKTyNoqYaA0Euuj7G9I_zcaKulv4,3552
|
|
3
|
+
robotframework_schemathesislibrary-1.0.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
4
|
+
robotframework_schemathesislibrary-1.0.0.dist-info/METADATA,sha256=j2nf_5CkRQYP5S_OR6Hscs1wz_Yg-QCqMXZPHbP8rTU,2669
|
|
5
|
+
robotframework_schemathesislibrary-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
robotframework_schemathesislibrary-1.0.0.dist-info/top_level.txt,sha256=bVGNag3IIuKRUVgjTtfRyDvyN3qRFYSSpmbCl5MBrUU,20
|
|
7
|
+
robotframework_schemathesislibrary-1.0.0.dist-info/RECORD,,
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
SchemathesisLibrary/__init__.py,sha256=sqLT4IpfGJ2vzqqYnXwcgXnVeQCy3JPNaERr_w9ZsmE,9116
|
|
2
|
-
robotframework_schemathesislibrary-0.52.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
3
|
-
robotframework_schemathesislibrary-0.52.0.dist-info/METADATA,sha256=8uVqXiG-GCaIzdJrl_8sSgyTLgN_-SMfLd1S_oWBCzc,2670
|
|
4
|
-
robotframework_schemathesislibrary-0.52.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
-
robotframework_schemathesislibrary-0.52.0.dist-info/top_level.txt,sha256=bVGNag3IIuKRUVgjTtfRyDvyN3qRFYSSpmbCl5MBrUU,20
|
|
6
|
-
robotframework_schemathesislibrary-0.52.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|