robotframework-schemathesislibrary 2.3.3__tar.gz → 2.5.0__tar.gz
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.
- {robotframework_schemathesislibrary-2.3.3/src/robotframework_schemathesislibrary.egg-info → robotframework_schemathesislibrary-2.5.0}/PKG-INFO +26 -3
- {robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/README.md +23 -0
- {robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/pyproject.toml +12 -11
- robotframework_schemathesislibrary-2.5.0/src/SchemathesisLibrary/__init__.py +432 -0
- {robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0/src/robotframework_schemathesislibrary.egg-info}/PKG-INFO +26 -3
- robotframework_schemathesislibrary-2.5.0/src/robotframework_schemathesislibrary.egg-info/requires.txt +4 -0
- robotframework_schemathesislibrary-2.3.3/src/SchemathesisLibrary/__init__.py +0 -370
- robotframework_schemathesislibrary-2.3.3/src/robotframework_schemathesislibrary.egg-info/requires.txt +0 -4
- {robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/LICENSE +0 -0
- {robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/setup.cfg +0 -0
- {robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/src/SchemathesisLibrary/schemathesisreader.py +0 -0
- {robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/src/robotframework_schemathesislibrary.egg-info/SOURCES.txt +0 -0
- {robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/src/robotframework_schemathesislibrary.egg-info/dependency_links.txt +0 -0
- {robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/src/robotframework_schemathesislibrary.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robotframework-schemathesislibrary
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.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
|
|
@@ -10,8 +10,8 @@ Description-Content-Type: text/markdown
|
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: robotframework>=7.2.2
|
|
12
12
|
Requires-Dist: robotframework-datadriver>=1.11.2
|
|
13
|
-
Requires-Dist: robotframework-pythonlibcore>=4.
|
|
14
|
-
Requires-Dist: schemathesis>=4.
|
|
13
|
+
Requires-Dist: robotframework-pythonlibcore>=4.6.0
|
|
14
|
+
Requires-Dist: schemathesis>=4.24.3
|
|
15
15
|
Dynamic: license-file
|
|
16
16
|
|
|
17
17
|
# Robot Framework SchemathesisLibrary
|
|
@@ -175,3 +175,26 @@ def filter_query(ctx, query) -> bool:
|
|
|
175
175
|
return True
|
|
176
176
|
```
|
|
177
177
|
Then only two test with `PUT` request are generated.
|
|
178
|
+
|
|
179
|
+
## Pabot Support
|
|
180
|
+
|
|
181
|
+
[Pabot](https://pabot.org/) is a parallel executor for Robot Framework tests, and
|
|
182
|
+
`SchemathesisLibrary` supports it.
|
|
183
|
+
|
|
184
|
+
However, there's a crucial point to consider. Because `SchemathesisLibrary` uses
|
|
185
|
+
[DataDriver](https://github.com/Snooz82/robotframework-datadriver) to generate
|
|
186
|
+
tests dynamically, you cannot use Schemathesis's built-in parallel execution
|
|
187
|
+
feature (`--workers` option).
|
|
188
|
+
|
|
189
|
+
When using Pabot, you are responsible for organizing your test suites to avoid
|
|
190
|
+
generating the same tests in multiple parallel processes. A good strategy might be to
|
|
191
|
+
split your test into different suites, where each suite targets a specific scenario.
|
|
192
|
+
|
|
193
|
+
For example, you could create separate suites for:
|
|
194
|
+
- A user that is not found in the system.
|
|
195
|
+
- A user that is found and has valid credentials.
|
|
196
|
+
- A user that is found but has invalid credentials.
|
|
197
|
+
|
|
198
|
+
This approach ensures that each Pabot process works on a unique set of tests. Since
|
|
199
|
+
every API and system is different, a universal rule for structuring suites is not
|
|
200
|
+
possible, but this scenario-based division might be good starting point.
|
{robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/README.md
RENAMED
|
@@ -159,3 +159,26 @@ def filter_query(ctx, query) -> bool:
|
|
|
159
159
|
return True
|
|
160
160
|
```
|
|
161
161
|
Then only two test with `PUT` request are generated.
|
|
162
|
+
|
|
163
|
+
## Pabot Support
|
|
164
|
+
|
|
165
|
+
[Pabot](https://pabot.org/) is a parallel executor for Robot Framework tests, and
|
|
166
|
+
`SchemathesisLibrary` supports it.
|
|
167
|
+
|
|
168
|
+
However, there's a crucial point to consider. Because `SchemathesisLibrary` uses
|
|
169
|
+
[DataDriver](https://github.com/Snooz82/robotframework-datadriver) to generate
|
|
170
|
+
tests dynamically, you cannot use Schemathesis's built-in parallel execution
|
|
171
|
+
feature (`--workers` option).
|
|
172
|
+
|
|
173
|
+
When using Pabot, you are responsible for organizing your test suites to avoid
|
|
174
|
+
generating the same tests in multiple parallel processes. A good strategy might be to
|
|
175
|
+
split your test into different suites, where each suite targets a specific scenario.
|
|
176
|
+
|
|
177
|
+
For example, you could create separate suites for:
|
|
178
|
+
- A user that is not found in the system.
|
|
179
|
+
- A user that is found and has valid credentials.
|
|
180
|
+
- A user that is found but has invalid credentials.
|
|
181
|
+
|
|
182
|
+
This approach ensures that each Pabot process works on a unique set of tests. Since
|
|
183
|
+
every API and system is different, a universal rule for structuring suites is not
|
|
184
|
+
possible, but this scenario-based division might be good starting point.
|
{robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/pyproject.toml
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "robotframework-schemathesislibrary"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.5.0"
|
|
4
4
|
description = "Robot Framework SchemathesisLibrary to automatically create test cases from API specifications."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name="Tatu Aalto", email="aalto.tatu@gmail.com" },
|
|
@@ -12,22 +12,23 @@ keywords = ["Robot Framework", "Schemathesis", "API Testing", "Test Automation"]
|
|
|
12
12
|
dependencies = [
|
|
13
13
|
"robotframework>=7.2.2",
|
|
14
14
|
"robotframework-datadriver>=1.11.2",
|
|
15
|
-
"robotframework-pythonlibcore>=4.
|
|
16
|
-
"schemathesis>=4.
|
|
15
|
+
"robotframework-pythonlibcore>=4.6.0",
|
|
16
|
+
"schemathesis>=4.24.3",
|
|
17
17
|
]
|
|
18
18
|
|
|
19
19
|
[dependency-groups]
|
|
20
20
|
dev = [
|
|
21
|
-
"coverage>=7.
|
|
22
|
-
"fastapi[standard]>=0.
|
|
21
|
+
"coverage>=7.15.2",
|
|
22
|
+
"fastapi[standard]>=0.140.0",
|
|
23
23
|
"invoke>=3.0.3",
|
|
24
|
-
"mypy>=1.
|
|
25
|
-
"pytest>=9.
|
|
26
|
-
"requests>=2.
|
|
24
|
+
"mypy>=2.1.0",
|
|
25
|
+
"pytest>=9.1.1",
|
|
26
|
+
"requests>=2.34.2",
|
|
27
|
+
"robotframework-pabot>=5.2.2",
|
|
27
28
|
"robotframework-requests>=0.9.7",
|
|
28
|
-
"robotframework-robocop>=8.2
|
|
29
|
-
"ruff==0.
|
|
30
|
-
"
|
|
29
|
+
"robotframework-robocop>=8.3.2",
|
|
30
|
+
"ruff==0.16.0",
|
|
31
|
+
"markdown>=3.10.2",
|
|
31
32
|
]
|
|
32
33
|
|
|
33
34
|
[tool.semantic_release.settings]
|
|
@@ -0,0 +1,432 @@
|
|
|
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 typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
from DataDriver import DataDriver # type: ignore
|
|
17
|
+
from requests.sessions import Session
|
|
18
|
+
from robot.api import logger
|
|
19
|
+
from robot.api.deco import keyword
|
|
20
|
+
from robot.result.model import TestCase as ResultTestCase # type: ignore
|
|
21
|
+
from robot.result.model import TestSuite as ResultTestSuite # type: ignore
|
|
22
|
+
from robot.running.model import TestCase, TestSuite # type: ignore
|
|
23
|
+
from robot.utils.dotdict import DotDict # type: ignore
|
|
24
|
+
from robotlibcore import DynamicCore # type: ignore
|
|
25
|
+
from schemathesis import Case
|
|
26
|
+
from schemathesis.core import NotSet
|
|
27
|
+
from schemathesis.core.transport import Response
|
|
28
|
+
|
|
29
|
+
from .schemathesisreader import Options, SchemathesisReader
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
__version__ = "2.5.0"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SchemathesisLibrary(DynamicCore):
|
|
38
|
+
"""SchemathesisLibrary is a library for validating API schema using Schemathesis tool.
|
|
39
|
+
|
|
40
|
+
Example usage of the library and the [Call And Validate] keyword
|
|
41
|
+
|
|
42
|
+
Library must be imported with the ``url`` or ``path`` argument to specify the
|
|
43
|
+
OpenAPI schema. The library uses
|
|
44
|
+
[DataDriver](https://github.com/Snooz82/robotframework-datadriver) to generate
|
|
45
|
+
test cases from the OpenAPI schema by using
|
|
46
|
+
[Schemathesis](https://github.com/schemathesis/schemathesis). The library
|
|
47
|
+
creates test cases that takes one argument, ``${case}``, which is a
|
|
48
|
+
Schemathesis
|
|
49
|
+
[Case](https://schemathesis.readthedocs.io/en/stable/reference/python/#schemathesis.Case)
|
|
50
|
+
object. The `Call And Validate` keyword can be used to call and validate
|
|
51
|
+
the case. The keyword will log the request and response details.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
```robotframework
|
|
55
|
+
*** Settings ***
|
|
56
|
+
Library SchemathesisLibrary url=http://127.0.0.1/openapi.json
|
|
57
|
+
|
|
58
|
+
Test Template Wrapper
|
|
59
|
+
|
|
60
|
+
*** Test Cases ***
|
|
61
|
+
All Tests # This test is deleted by DataDriver
|
|
62
|
+
Wrapper test_case_1
|
|
63
|
+
|
|
64
|
+
*** Keywords ***
|
|
65
|
+
Wrapper
|
|
66
|
+
[Arguments] ${case}
|
|
67
|
+
Call And Validate ${case}
|
|
68
|
+
```
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
ROBOT_LIBRARY_VERSION = __version__
|
|
72
|
+
ROBOT_LISTENER_API_VERSION = 3
|
|
73
|
+
ROBOT_LIBRARY_SCOPE = "TEST SUITE"
|
|
74
|
+
ROBOT_LIBRARY_DOC_FORMAT = "Markdown"
|
|
75
|
+
|
|
76
|
+
def __init__( # noqa: PLR0913
|
|
77
|
+
self,
|
|
78
|
+
*,
|
|
79
|
+
headers: "dict[str, Any]|None" = None,
|
|
80
|
+
max_examples: int = 100,
|
|
81
|
+
path: "Path|None" = None,
|
|
82
|
+
url: "str|None" = None,
|
|
83
|
+
auth: str | None = None,
|
|
84
|
+
hook: str | None = None,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""
|
|
87
|
+
Arguments:
|
|
88
|
+
headers:
|
|
89
|
+
Optional HTTP headers to be used when schema is downloaded from ``url``.
|
|
90
|
+
Argument is only needed when the schema is downloaded from a URL and there is need to pass example
|
|
91
|
+
authentication headers to the endpoint. The ``headers`` is not used in the API calls are made during
|
|
92
|
+
test execution.
|
|
93
|
+
max_examples:
|
|
94
|
+
Maximum number of examples to generate for each operation. Default is 5.
|
|
95
|
+
path:
|
|
96
|
+
Path to the OpenAPI schema file. Using either ``path`` or ``url`` is mandatory.
|
|
97
|
+
url:
|
|
98
|
+
URL where the OpenAPI schema can be downloaded.
|
|
99
|
+
auth:
|
|
100
|
+
Optional authentication class to be used passed for Schemathesis authentication when test cases are executed.
|
|
101
|
+
Argument is used to create Schemathesis
|
|
102
|
+
[dynamic token](https://schemathesis.readthedocs.io/en/stable/guides/auth/#dynamic-token-authentication)
|
|
103
|
+
authentication for the test cases. The dynamic token generation class should follow the Schemathesis
|
|
104
|
+
documentation. The only addition is the import and importing the class must follow the Robot Framework library
|
|
105
|
+
[import rules](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#specifying-library-to-import).
|
|
106
|
+
Example if importing with filename, filename much match to the class name. See example from
|
|
107
|
+
[README.md](https://github.com/aaltat/robotframework-schemathesis?tab=readme-ov-file##dynamic-token-authentication)
|
|
108
|
+
file
|
|
109
|
+
hook:
|
|
110
|
+
Optional Schemathesis [hook](https://schemathesis.readthedocs.io/en/stable/guides/extending/). The
|
|
111
|
+
argument can be used to specify Schemathesis hooks to modify the generated test cases. The hooks
|
|
112
|
+
implementation must follow the Schemathesis documentation:
|
|
113
|
+
[Hooks](https://schemathesis.readthedocs.io/en/stable/guides/extending/) importing follows same
|
|
114
|
+
rules as importing
|
|
115
|
+
[test libraries](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#specifying-library-to-import)
|
|
116
|
+
in Robot Framework. Multiple hooks can be specified by separating them with semicolon (;).
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
``path`` and ``url`` are mutually exclusive, only one of them should be used to specify the OpenAPI schema location.
|
|
120
|
+
|
|
121
|
+
# Configuration File
|
|
122
|
+
|
|
123
|
+
SchemathesisLibrary automatically discovers and loads configuration from a ``schemathesis.toml`` file if present
|
|
124
|
+
in the project directory. The configuration file allows you to customize test data generation and other Schemathesis
|
|
125
|
+
settings without modifying the library initialization.
|
|
126
|
+
|
|
127
|
+
The library uses Schemathesis's
|
|
128
|
+
[configuration file discovery](https://schemathesis.readthedocs.io/en/stable/reference/configuration/)
|
|
129
|
+
mechanism to locate ``schemathesis.toml`` in the current directory or parent directories (stopping at .git folder
|
|
130
|
+
or filesystem root).
|
|
131
|
+
|
|
132
|
+
Configuration options that are automatically applied:
|
|
133
|
+
|
|
134
|
+
- ``max-examples``: If specified in ``[project.generation]``, overrides the ``max_examples`` parameter passed to the library
|
|
135
|
+
- ``mode``: Generation mode (``positive``, ``negative``, or ``all``) controls whether valid or invalid test data is generated
|
|
136
|
+
|
|
137
|
+
Example schemathesis.toml:
|
|
138
|
+
```toml
|
|
139
|
+
[[project]]
|
|
140
|
+
title = "My API"
|
|
141
|
+
|
|
142
|
+
[project.generation]
|
|
143
|
+
mode = "positive" # Generate only valid test cases
|
|
144
|
+
max-examples = 5 # Generate 5 test cases per operation
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
For complete configuration options, see schemathesis
|
|
148
|
+
[configuration](https://schemathesis.readthedocs.io/en/stable/reference/configuration/)
|
|
149
|
+
documentation.
|
|
150
|
+
|
|
151
|
+
If no configuration file is found, the library uses default values (POSITIVE mode and max_examples from
|
|
152
|
+
library initialization.
|
|
153
|
+
"""
|
|
154
|
+
self.ROBOT_LIBRARY_LISTENER = self
|
|
155
|
+
SchemathesisReader.options = Options(
|
|
156
|
+
headers=headers,
|
|
157
|
+
max_examples=max_examples,
|
|
158
|
+
path=path,
|
|
159
|
+
url=url,
|
|
160
|
+
auth=auth,
|
|
161
|
+
hook=hook,
|
|
162
|
+
)
|
|
163
|
+
self.data_driver = DataDriver(reader_class=SchemathesisReader)
|
|
164
|
+
DynamicCore.__init__(self, [])
|
|
165
|
+
|
|
166
|
+
def _start_suite(self, data: TestSuite, result: ResultTestSuite) -> None:
|
|
167
|
+
self.data_driver._start_suite(data, result)
|
|
168
|
+
|
|
169
|
+
def _start_test(self, data: TestCase, result: ResultTestCase) -> None:
|
|
170
|
+
self.data_driver._start_test(data, result)
|
|
171
|
+
|
|
172
|
+
@keyword
|
|
173
|
+
def call_and_validate(
|
|
174
|
+
self,
|
|
175
|
+
case: Case,
|
|
176
|
+
*,
|
|
177
|
+
base_url: str | None = None,
|
|
178
|
+
headers: dict[str, Any] | None = None,
|
|
179
|
+
auth: tuple[str, str] | Any | None = None,
|
|
180
|
+
session: Session | None = None,
|
|
181
|
+
) -> Response:
|
|
182
|
+
"""Call and validate a Schemathesis case.
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
```robotframework
|
|
186
|
+
*** Settings ***
|
|
187
|
+
Library SchemathesisLibrary
|
|
188
|
+
... url=http://127.0.0.1/openapi.json
|
|
189
|
+
Test Template Wrapper
|
|
190
|
+
|
|
191
|
+
*** Test Cases ***
|
|
192
|
+
All Tests
|
|
193
|
+
Wrapper test_case_1 # Test is replaced by DataDriver with generated test cases
|
|
194
|
+
|
|
195
|
+
*** Keywords ***
|
|
196
|
+
Wrapper
|
|
197
|
+
${response} = Call And Validate ${case}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Arguments:
|
|
201
|
+
case:
|
|
202
|
+
The Schemathesis
|
|
203
|
+
[Case](https://schemathesis.readthedocs.io/en/stable/reference/python/#schemathesis.Case)
|
|
204
|
+
to be called and validated.
|
|
205
|
+
base_url:
|
|
206
|
+
Optional base URL to override the one defined in the schema. Need mostly when schema is read from
|
|
207
|
+
a file.
|
|
208
|
+
headers:
|
|
209
|
+
Optional HTTP headers to be added when calling the API endpoint.
|
|
210
|
+
auth:
|
|
211
|
+
Optional authentication to be used when calling the case. Argument can be any authentication
|
|
212
|
+
supported by [httpx authentication](https://www.python-httpx.org/advanced/authentication/).
|
|
213
|
+
Example a tuple containing username and password for basic authentication or an instance of
|
|
214
|
+
[Digest authentication](https://www.python-httpx.org/advanced/authentication/#digest-authentication)
|
|
215
|
+
session:
|
|
216
|
+
Optional request session to be used when calling the case. Argument can be used to pass
|
|
217
|
+
a pre-configured HTTP session and it must be an instance of
|
|
218
|
+
[requests.session.Session](https://requests.readthedocs.io/en/latest/user/advanced/#session-objects)
|
|
219
|
+
. If, example, session is created by using `RequestsLibrary`, then current session can be obtained
|
|
220
|
+
by following way from python code:
|
|
221
|
+
```python
|
|
222
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
223
|
+
|
|
224
|
+
def get_request_session():
|
|
225
|
+
request_library = BuiltIn().get_library_instance("RequestsLibrary")
|
|
226
|
+
return request_library._cache.current
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Then this can be used in test case like:
|
|
230
|
+
```robotframework
|
|
231
|
+
*** Settings ***
|
|
232
|
+
Library RequestsLibrary
|
|
233
|
+
Library SchemathesisLibrary
|
|
234
|
+
... url=http://127.0.0.1/openapi.json
|
|
235
|
+
Library ${CURDIR}/request_session.py
|
|
236
|
+
Test Template Wrapper
|
|
237
|
+
|
|
238
|
+
*** Test Cases ***
|
|
239
|
+
All Tests
|
|
240
|
+
Wrapper test_case_1
|
|
241
|
+
|
|
242
|
+
*** Keywords ***
|
|
243
|
+
Wrapper
|
|
244
|
+
[Arguments] ${case}
|
|
245
|
+
Create Session
|
|
246
|
+
... session http://127.0.0.1/
|
|
247
|
+
${session} = Get Request Session
|
|
248
|
+
Call And Validate
|
|
249
|
+
... ${case}
|
|
250
|
+
... headers=&{BASIC_AUTH_HEADERS}
|
|
251
|
+
... session=${session}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
[Schemathesis Response](https://schemathesis.readthedocs.io/en/stable/reference/python/#schemathesis.Response)
|
|
256
|
+
object. Can be used to further validate the response, example check specific headers or response
|
|
257
|
+
body.
|
|
258
|
+
"""
|
|
259
|
+
logger.info(f"auth: {auth} {type(auth)}")
|
|
260
|
+
headers = self._dot_dict_to_dict(headers) if headers else None
|
|
261
|
+
self.info(f"Case: {case.path} | {case.method} | {case.path_parameters}")
|
|
262
|
+
self._log_case(case, headers)
|
|
263
|
+
if session:
|
|
264
|
+
self.info("Using provided session for the request.")
|
|
265
|
+
response = case.call_and_validate(base_url=base_url, headers=headers, auth=auth, session=session)
|
|
266
|
+
self._log_request(response)
|
|
267
|
+
self.debug(f"Response: {response.headers} | {response.status_code} | {response.text}")
|
|
268
|
+
return response
|
|
269
|
+
|
|
270
|
+
@keyword
|
|
271
|
+
def call(
|
|
272
|
+
self,
|
|
273
|
+
case: Case,
|
|
274
|
+
*,
|
|
275
|
+
base_url: str | None = None,
|
|
276
|
+
headers: dict[str, Any] | None = None,
|
|
277
|
+
auth: tuple[str, str] | Any | None = None,
|
|
278
|
+
session: Session | None = None,
|
|
279
|
+
) -> Response:
|
|
280
|
+
"""Call a Schemathesis case without validation.
|
|
281
|
+
|
|
282
|
+
The `Call` and `Validate Response` keywords can be used together
|
|
283
|
+
to call a case and validate the response.
|
|
284
|
+
|
|
285
|
+
```robotframework
|
|
286
|
+
*** Settings ***
|
|
287
|
+
Library SchemathesisLibrary
|
|
288
|
+
... url=http://127.0.0.1/openapi.json
|
|
289
|
+
Test Template Wrapper
|
|
290
|
+
|
|
291
|
+
*** Test Cases ***
|
|
292
|
+
All Tests
|
|
293
|
+
Wrapper test_case_1 # Test is replaced by DataDriver with generated test cases
|
|
294
|
+
|
|
295
|
+
*** Keywords ***
|
|
296
|
+
Wrapper
|
|
297
|
+
${response} = Call ${case}
|
|
298
|
+
Validate Response
|
|
299
|
+
... ${case} ${response}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Arguments:
|
|
303
|
+
case:
|
|
304
|
+
The Schemathesis
|
|
305
|
+
[Case](https://schemathesis.readthedocs.io/en/stable/reference/python/#schemathesis.Case|Case)
|
|
306
|
+
to be called and validated.
|
|
307
|
+
base_url:
|
|
308
|
+
Optional base URL to override the one defined in the schema. Need mostly when schema is read
|
|
309
|
+
from a file.
|
|
310
|
+
headers:
|
|
311
|
+
Optional HTTP headers to be added when calling the API endpoint.
|
|
312
|
+
auth:
|
|
313
|
+
Optional authentication to be used when calling the case. Argument can be any authentication
|
|
314
|
+
supported by [httpx authentication](https://www.python-httpx.org/advanced/authentication/).
|
|
315
|
+
Example a tuple containing username and password for basic authentication or an instance of
|
|
316
|
+
[Digest authentication](https://www.python-httpx.org/advanced/authentication/#digest-authentication)
|
|
317
|
+
session:
|
|
318
|
+
Optional request session to be used when calling the case. See [Call And Validate] keyword
|
|
319
|
+
for example of using ``session`` argument.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
[Schemathesis Response](https://schemathesis.readthedocs.io/en/stable/reference/python/#schemathesis.Response)
|
|
323
|
+
object. Can be used to further validate the response, example check specific headers
|
|
324
|
+
or response body.
|
|
325
|
+
"""
|
|
326
|
+
headers = self._dot_dict_to_dict(headers) if headers else None
|
|
327
|
+
self.info(f"Calling case: {case.path} | {case.method} | {case.path_parameters}")
|
|
328
|
+
self._log_case(case)
|
|
329
|
+
if session:
|
|
330
|
+
self.info("Using provided session for the request.")
|
|
331
|
+
response = case.call(base_url=base_url, headers=headers, auth=auth, session=session)
|
|
332
|
+
self._log_request(response)
|
|
333
|
+
return response
|
|
334
|
+
|
|
335
|
+
@keyword
|
|
336
|
+
def validate_response(
|
|
337
|
+
self,
|
|
338
|
+
case: Case,
|
|
339
|
+
response: Response,
|
|
340
|
+
headers: dict[str, Any] | None = None,
|
|
341
|
+
auth: tuple[str, str] | Any | None = None,
|
|
342
|
+
) -> None:
|
|
343
|
+
"""Validate a Schemathesis response.
|
|
344
|
+
|
|
345
|
+
The response is validated against the case's expectations.
|
|
346
|
+
The [Call] and [Validate Response] keywords can be used together
|
|
347
|
+
to call a case and validate the response. See the example from
|
|
348
|
+
[Call] keyword documentation.
|
|
349
|
+
|
|
350
|
+
Arguments:
|
|
351
|
+
case:
|
|
352
|
+
The Schemathesis case to be validated against.
|
|
353
|
+
response:
|
|
354
|
+
The Schemathesis response to be validated. Returned by `Call` keyword.
|
|
355
|
+
headers:
|
|
356
|
+
Optional HTTP headers to be added when calling the API endpoint.
|
|
357
|
+
auth:
|
|
358
|
+
Optional authentication to be used when calling the case. Argument can be any authentication
|
|
359
|
+
supported by [httpx authentication](https://www.python-httpx.org/advanced/authentication/).
|
|
360
|
+
Example a tuple containing username and password for basic authentication or an instance of
|
|
361
|
+
[Digest authentication](https://www.python-httpx.org/advanced/authentication/#digest-authentication)
|
|
362
|
+
"""
|
|
363
|
+
self.info(f"Validating response: {response.status_code} | {response.headers}")
|
|
364
|
+
transport_kwargs: dict[str, Any] = {}
|
|
365
|
+
if auth:
|
|
366
|
+
transport_kwargs["auth"] = auth
|
|
367
|
+
if headers:
|
|
368
|
+
transport_kwargs["headers"] = headers
|
|
369
|
+
case.validate_response(response=response, transport_kwargs=transport_kwargs)
|
|
370
|
+
self.info("Response validation passed.")
|
|
371
|
+
|
|
372
|
+
@keyword(name="As cURL")
|
|
373
|
+
def as_curl(self, case: Case) -> str:
|
|
374
|
+
"""Convert a Schemathesis case to a cURL command.
|
|
375
|
+
|
|
376
|
+
Example:
|
|
377
|
+
```robotframework
|
|
378
|
+
*** Settings ***
|
|
379
|
+
Library SchemathesisLibrary
|
|
380
|
+
... url=http://127.0.0.1/openapi.json
|
|
381
|
+
Test Template Wrapper
|
|
382
|
+
|
|
383
|
+
*** Test Cases ***
|
|
384
|
+
All Tests
|
|
385
|
+
Wrapper test_case_1 # Test is replaced by DataDriver with generated test cases
|
|
386
|
+
|
|
387
|
+
*** Keywords ***
|
|
388
|
+
Wrapper
|
|
389
|
+
${curl_command} = `As cURL` ${case}
|
|
390
|
+
Log ${curl_command}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Arguments:
|
|
394
|
+
case: The Schemathesis case to be converted.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
Returns a string containing the cURL command that can be used to execute the same request as the case.
|
|
398
|
+
"""
|
|
399
|
+
self.info(f"Converting case to cURL: {case.path} | {case.method} | {case.path_parameters}")
|
|
400
|
+
self._log_case(case)
|
|
401
|
+
curl_command = case.as_curl_command()
|
|
402
|
+
self.debug(f"Generated cURL command: {curl_command}")
|
|
403
|
+
return curl_command
|
|
404
|
+
|
|
405
|
+
def info(self, message: str) -> None:
|
|
406
|
+
logger.info(message)
|
|
407
|
+
|
|
408
|
+
def debug(self, message: str) -> None:
|
|
409
|
+
logger.debug(message)
|
|
410
|
+
|
|
411
|
+
def _log_case(
|
|
412
|
+
self,
|
|
413
|
+
case: Case,
|
|
414
|
+
headers: "dict[str, Any]|None" = None,
|
|
415
|
+
) -> None:
|
|
416
|
+
body = case.body if not isinstance(case.body, NotSet) else "Not set"
|
|
417
|
+
case_headers = headers or case.headers
|
|
418
|
+
self.debug(
|
|
419
|
+
f"Case headers {case_headers!r} body {body!r} "
|
|
420
|
+
f"cookies {case.cookies!r} path parameters {case.path_parameters!r}"
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
def _log_request(self, resposen: Response) -> None:
|
|
424
|
+
self.debug(
|
|
425
|
+
f"Request: {resposen.request.method} {resposen.request.url} "
|
|
426
|
+
f"headers: {resposen.request.headers!r} body: {resposen.request.body!r}"
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
def _dot_dict_to_dict(self, dot_dict: dict[str, Any]) -> dict[str, Any]:
|
|
430
|
+
if isinstance(dot_dict, DotDict):
|
|
431
|
+
return dict(dot_dict)
|
|
432
|
+
return dot_dict
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robotframework-schemathesislibrary
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.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
|
|
@@ -10,8 +10,8 @@ Description-Content-Type: text/markdown
|
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: robotframework>=7.2.2
|
|
12
12
|
Requires-Dist: robotframework-datadriver>=1.11.2
|
|
13
|
-
Requires-Dist: robotframework-pythonlibcore>=4.
|
|
14
|
-
Requires-Dist: schemathesis>=4.
|
|
13
|
+
Requires-Dist: robotframework-pythonlibcore>=4.6.0
|
|
14
|
+
Requires-Dist: schemathesis>=4.24.3
|
|
15
15
|
Dynamic: license-file
|
|
16
16
|
|
|
17
17
|
# Robot Framework SchemathesisLibrary
|
|
@@ -175,3 +175,26 @@ def filter_query(ctx, query) -> bool:
|
|
|
175
175
|
return True
|
|
176
176
|
```
|
|
177
177
|
Then only two test with `PUT` request are generated.
|
|
178
|
+
|
|
179
|
+
## Pabot Support
|
|
180
|
+
|
|
181
|
+
[Pabot](https://pabot.org/) is a parallel executor for Robot Framework tests, and
|
|
182
|
+
`SchemathesisLibrary` supports it.
|
|
183
|
+
|
|
184
|
+
However, there's a crucial point to consider. Because `SchemathesisLibrary` uses
|
|
185
|
+
[DataDriver](https://github.com/Snooz82/robotframework-datadriver) to generate
|
|
186
|
+
tests dynamically, you cannot use Schemathesis's built-in parallel execution
|
|
187
|
+
feature (`--workers` option).
|
|
188
|
+
|
|
189
|
+
When using Pabot, you are responsible for organizing your test suites to avoid
|
|
190
|
+
generating the same tests in multiple parallel processes. A good strategy might be to
|
|
191
|
+
split your test into different suites, where each suite targets a specific scenario.
|
|
192
|
+
|
|
193
|
+
For example, you could create separate suites for:
|
|
194
|
+
- A user that is not found in the system.
|
|
195
|
+
- A user that is found and has valid credentials.
|
|
196
|
+
- A user that is found but has invalid credentials.
|
|
197
|
+
|
|
198
|
+
This approach ensures that each Pabot process works on a unique set of tests. Since
|
|
199
|
+
every API and system is different, a universal rule for structuring suites is not
|
|
200
|
+
possible, but this scenario-based division might be good starting point.
|
|
@@ -1,370 +0,0 @@
|
|
|
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 typing import TYPE_CHECKING, Any
|
|
15
|
-
|
|
16
|
-
from DataDriver import DataDriver # type: ignore
|
|
17
|
-
from requests.sessions import Session
|
|
18
|
-
from robot.api import logger
|
|
19
|
-
from robot.api.deco import keyword
|
|
20
|
-
from robot.result.model import TestCase as ResultTestCase # type: ignore
|
|
21
|
-
from robot.result.model import TestSuite as ResultTestSuite # type: ignore
|
|
22
|
-
from robot.running.model import TestCase, TestSuite # type: ignore
|
|
23
|
-
from robot.utils.dotdict import DotDict # type: ignore
|
|
24
|
-
from robotlibcore import DynamicCore # type: ignore
|
|
25
|
-
from schemathesis import Case
|
|
26
|
-
from schemathesis.core import NotSet
|
|
27
|
-
from schemathesis.core.transport import Response
|
|
28
|
-
|
|
29
|
-
from .schemathesisreader import Options, SchemathesisReader
|
|
30
|
-
|
|
31
|
-
if TYPE_CHECKING:
|
|
32
|
-
from pathlib import Path
|
|
33
|
-
|
|
34
|
-
__version__ = "2.3.3"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class SchemathesisLibrary(DynamicCore):
|
|
38
|
-
"""SchemathesisLibrary is a library for validating API schema using Schemathesis.
|
|
39
|
-
|
|
40
|
-
%TOC%
|
|
41
|
-
|
|
42
|
-
Example usage of the library and the `Call And Validate` keyword
|
|
43
|
-
|
|
44
|
-
Library must be imported with the ``url`` or ``path`` argument to specify the
|
|
45
|
-
OpenAPI schema. The library uses
|
|
46
|
-
[https://github.com/Snooz82/robotframework-datadriver|DataDriver] to generate
|
|
47
|
-
test cases from the OpenAPI schema by using
|
|
48
|
-
[https://github.com/schemathesis/schemathesis|Schemathesis]. The library
|
|
49
|
-
creates test cases that takes one argument, ``${case}``, which is a
|
|
50
|
-
Schemathesis
|
|
51
|
-
[https://schemathesis.readthedocs.io/en/stable/reference/python/#schemathesis.Case|Case]
|
|
52
|
-
object. The `Call And Validate` keyword can be used to call and validate
|
|
53
|
-
the case. The keyword will log the request and response details.
|
|
54
|
-
|
|
55
|
-
Example:
|
|
56
|
-
| *** Settings ***
|
|
57
|
-
| Library SchemathesisLibrary url=http://127.0.0.1/openapi.json
|
|
58
|
-
|
|
|
59
|
-
| Test Template Wrapper
|
|
60
|
-
|
|
|
61
|
-
| *** Test Cases ***
|
|
62
|
-
| All Tests # This test is deleted by DataDriver
|
|
63
|
-
| Wrapper test_case_1
|
|
64
|
-
|
|
|
65
|
-
| *** Keywords ***
|
|
66
|
-
| Wrapper
|
|
67
|
-
| [Arguments] ${case}
|
|
68
|
-
| Call And Validate ${case}
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
|
-
ROBOT_LIBRARY_VERSION = __version__
|
|
72
|
-
ROBOT_LISTENER_API_VERSION = 3
|
|
73
|
-
ROBOT_LIBRARY_SCOPE = "TEST SUITE"
|
|
74
|
-
|
|
75
|
-
def __init__( # noqa: PLR0913
|
|
76
|
-
self,
|
|
77
|
-
*,
|
|
78
|
-
headers: "dict[str, Any]|None" = None,
|
|
79
|
-
max_examples: int = 100,
|
|
80
|
-
path: "Path|None" = None,
|
|
81
|
-
url: "str|None" = None,
|
|
82
|
-
auth: str | None = None,
|
|
83
|
-
hook: str | None = None,
|
|
84
|
-
) -> None:
|
|
85
|
-
"""The SchemathesisLibrary can be initialized with the following arguments:
|
|
86
|
-
|
|
87
|
-
| =Argument= | =Description= |
|
|
88
|
-
| ``headers`` | Optional HTTP headers to be used when schema is downloaded from ``url``. |
|
|
89
|
-
| ``max_examples`` | Maximum number of examples to generate for each operation. Default is 5. |
|
|
90
|
-
| ``path`` | Path to the OpenAPI schema file. Using either ``path`` or ``url`` is mandatory. |
|
|
91
|
-
| ``url`` | URL where the OpenAPI schema can be downloaded. |
|
|
92
|
-
| ``auth`` | Optional authentication class to be used passed for Schemathesis authentication when test cases are executed. |
|
|
93
|
-
| ``hook`` | Optional Schemathesis hook https://schemathesis.readthedocs.io/en/stable/guides/extending/. |
|
|
94
|
-
|
|
95
|
-
The ``headers`` argument is only needed when the schema is downloaded from a URL and there is need to pass example
|
|
96
|
-
authentication headers to the endpoint. The ``headers`` is not used in the API calls are made during test execution.
|
|
97
|
-
|
|
98
|
-
``path`` and ``url`` are mutually exclusive, only one of them should be used to specify the OpenAPI schema location.
|
|
99
|
-
|
|
100
|
-
``auth`` can be used create Schemathesis
|
|
101
|
-
[https://schemathesis.readthedocs.io/en/stable/guides/auth/#dynamic-token-authentication|dynamic token]
|
|
102
|
-
authentication for the test cases. The dynamic token generation class should
|
|
103
|
-
follow the Schemathesis documentation. The only addition is the import and importing
|
|
104
|
-
the class must follow the Robot Framework library
|
|
105
|
-
[https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#specifying-library-to-import|import rules]
|
|
106
|
-
. Example if importing with filename, filename much match to the class name.
|
|
107
|
-
See example from
|
|
108
|
-
[https://github.com/aaltat/robotframework-schemathesis?tab=readme-ov-file##dynamic-token-authentication|README.md]
|
|
109
|
-
file
|
|
110
|
-
|
|
111
|
-
The ``hook`` argument can be used to specify Schemathesis hooks to modify the generated test cases. The hooks
|
|
112
|
-
implementation must follow the Schemathesis documentation:
|
|
113
|
-
https://schemathesis.readthedocs.io/en/stable/guides/extending/ Hooks importing follows same rules as importing
|
|
114
|
-
[https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#specifying-library-to-import|test libraries]
|
|
115
|
-
in Robot Framework. Multiple hooks can be specified by separating them with semicolon (;).
|
|
116
|
-
|
|
117
|
-
= Configuration File =
|
|
118
|
-
|
|
119
|
-
SchemathesisLibrary automatically discovers and loads configuration from a ``schemathesis.toml`` file if present
|
|
120
|
-
in the project directory. The configuration file allows you to customize test data generation and other Schemathesis
|
|
121
|
-
settings without modifying the library initialization.
|
|
122
|
-
|
|
123
|
-
The library uses Schemathesis's
|
|
124
|
-
[https://schemathesis.readthedocs.io/en/stable/reference/configuration/|configuration file discovery]
|
|
125
|
-
mechanism to locate ``schemathesis.toml`` in the current directory or parent directories (stopping at .git folder
|
|
126
|
-
or filesystem root).
|
|
127
|
-
|
|
128
|
-
Configuration options that are automatically applied:
|
|
129
|
-
|
|
130
|
-
- ``max-examples``: If specified in ``[project.generation]``, overrides the ``max_examples`` parameter passed to the library
|
|
131
|
-
- ``mode``: Generation mode (``positive``, ``negative``, or ``all``) controls whether valid or invalid test data is generated
|
|
132
|
-
|
|
133
|
-
Example ``schemathesis.toml``:
|
|
134
|
-
| [[project]]
|
|
135
|
-
| title = "My API"
|
|
136
|
-
|
|
|
137
|
-
| [project.generation]
|
|
138
|
-
| mode = "positive" # Generate only valid test cases
|
|
139
|
-
| max-examples = 5 # Generate 5 test cases per operation
|
|
140
|
-
|
|
141
|
-
For complete configuration options, see:
|
|
142
|
-
https://schemathesis.readthedocs.io/en/stable/reference/configuration/
|
|
143
|
-
|
|
144
|
-
If no configuration file is found, the library uses default values (POSITIVE mode and max_examples from library initialization).
|
|
145
|
-
"""
|
|
146
|
-
self.ROBOT_LIBRARY_LISTENER = self
|
|
147
|
-
SchemathesisReader.options = Options(
|
|
148
|
-
headers=headers,
|
|
149
|
-
max_examples=max_examples,
|
|
150
|
-
path=path,
|
|
151
|
-
url=url,
|
|
152
|
-
auth=auth,
|
|
153
|
-
hook=hook,
|
|
154
|
-
)
|
|
155
|
-
self.data_driver = DataDriver(reader_class=SchemathesisReader)
|
|
156
|
-
DynamicCore.__init__(self, [])
|
|
157
|
-
|
|
158
|
-
def _start_suite(self, data: TestSuite, result: ResultTestSuite) -> None:
|
|
159
|
-
self.data_driver._start_suite(data, result)
|
|
160
|
-
|
|
161
|
-
def _start_test(self, data: TestCase, result: ResultTestCase) -> None:
|
|
162
|
-
self.data_driver._start_test(data, result)
|
|
163
|
-
|
|
164
|
-
@keyword
|
|
165
|
-
def call_and_validate(
|
|
166
|
-
self,
|
|
167
|
-
case: Case,
|
|
168
|
-
*,
|
|
169
|
-
base_url: str | None = None,
|
|
170
|
-
headers: dict[str, Any] | None = None,
|
|
171
|
-
auth: tuple[str, str] | Any | None = None,
|
|
172
|
-
session: Session | None = None,
|
|
173
|
-
) -> Response:
|
|
174
|
-
"""Call and validate a Schemathesis case.
|
|
175
|
-
|
|
176
|
-
| Argument | Description |
|
|
177
|
-
| ``case`` | The Schemathesis [https://schemathesis.readthedocs.io/en/stable/reference/python/#schemathesis.Case|Case] to be called and validated. |
|
|
178
|
-
| ``base_url`` | Optional base URL to override the one defined in the schema. Need mostly when schema is read from a file. |
|
|
179
|
-
| ``headers`` | Optional HTTP headers to be added when calling the API endpoint. |
|
|
180
|
-
| ``auth`` | Optional authentication to be used when calling the case. |
|
|
181
|
-
| ``session`` | Optional request session to be used when calling the case. |
|
|
182
|
-
``auth`` can be any authentication supported by
|
|
183
|
-
[https://www.python-httpx.org/advanced/authentication/|httpx authentication].
|
|
184
|
-
Example a tuple containing username and password for basic authentication or
|
|
185
|
-
and instance of
|
|
186
|
-
[https://www.python-httpx.org/advanced/authentication/#digest-authentication|Digest authentication]
|
|
187
|
-
|
|
188
|
-
``session`` can be used to pass a pre-configured HTTP session and it must be an instance of
|
|
189
|
-
[https://requests.readthedocs.io/en/latest/user/advanced/#session-objects|requests.session.Session].
|
|
190
|
-
If, example, session is created by using `RequestsLibrary`, then current session can be obtained
|
|
191
|
-
by following way from python code:
|
|
192
|
-
| from robot.libraries.BuiltIn import BuiltIn
|
|
193
|
-
|
|
|
194
|
-
| def get_request_session():
|
|
195
|
-
| request_library = BuiltIn().get_library_instance("RequestsLibrary")
|
|
196
|
-
| return request_library._cache.current
|
|
197
|
-
|
|
198
|
-
Then this can be used in test case like:
|
|
199
|
-
| *** Settings ***
|
|
200
|
-
| Library RequestsLibrary
|
|
201
|
-
| Library SchemathesisLibrary
|
|
202
|
-
| ... url=http://127.0.0.1/openapi.json
|
|
203
|
-
| Library ${CURDIR}/request_session.py
|
|
204
|
-
| Test Template Wrapper
|
|
205
|
-
|
|
|
206
|
-
| *** Test Cases ***
|
|
207
|
-
| All Tests
|
|
208
|
-
| Wrapper test_case_1
|
|
209
|
-
|
|
|
210
|
-
| *** Keywords ***
|
|
211
|
-
| Wrapper
|
|
212
|
-
| [Arguments] ${case}
|
|
213
|
-
| Create Session session http://127.0.0.1/
|
|
214
|
-
| ${session} = Get Request Session
|
|
215
|
-
| Call And Validate ${case} headers=&{BASIC_AUTH_HEADERS} session=${session}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
Returns a
|
|
219
|
-
[https://schemathesis.readthedocs.io/en/stable/reference/python/#schemathesis.Response|Schemathesis Response]
|
|
220
|
-
object. Can be used to further validate the response, example check specific headers
|
|
221
|
-
or response body.
|
|
222
|
-
|
|
223
|
-
Example:
|
|
224
|
-
| ${response} = `Call And Validate` ${case}
|
|
225
|
-
| VAR ${body} ${response.json()}
|
|
226
|
-
| Should Be True ${body} # Or whatever validation is needed by users
|
|
227
|
-
"""
|
|
228
|
-
logger.info(f"auth: {auth} {type(auth)}")
|
|
229
|
-
headers = self._dot_dict_to_dict(headers) if headers else None
|
|
230
|
-
self.info(f"Case: {case.path} | {case.method} | {case.path_parameters}")
|
|
231
|
-
self._log_case(case, headers)
|
|
232
|
-
if session:
|
|
233
|
-
self.info("Using provided session for the request.")
|
|
234
|
-
response = case.call_and_validate(base_url=base_url, headers=headers, auth=auth, session=session)
|
|
235
|
-
self._log_request(response)
|
|
236
|
-
self.debug(f"Response: {response.headers} | {response.status_code} | {response.text}")
|
|
237
|
-
return response
|
|
238
|
-
|
|
239
|
-
@keyword
|
|
240
|
-
def call(
|
|
241
|
-
self,
|
|
242
|
-
case: Case,
|
|
243
|
-
*,
|
|
244
|
-
base_url: str | None = None,
|
|
245
|
-
headers: dict[str, Any] | None = None,
|
|
246
|
-
auth: tuple[str, str] | Any | None = None,
|
|
247
|
-
session: Session | None = None,
|
|
248
|
-
) -> Response:
|
|
249
|
-
"""Call a Schemathesis case without validation.
|
|
250
|
-
|
|
251
|
-
| =Argument= | =Description= |
|
|
252
|
-
| ``case`` | The Schemathesis [https://schemathesis.readthedocs.io/en/stable/reference/python/#schemathesis.Case|Case] to be called and validated. |
|
|
253
|
-
| ``base_url`` | Optional base URL to override the one defined in the schema. Need mostly when schema is read from a file. |
|
|
254
|
-
| ``headers`` | Optional HTTP headers to be added when calling the API endpoint. |
|
|
255
|
-
| ``auth`` | Optional authentication to be used when calling the case. |
|
|
256
|
-
| ``session`` | Optional request session to be used when calling the case. |
|
|
257
|
-
|
|
258
|
-
``auth`` can be any authentication supported by
|
|
259
|
-
[https://www.python-httpx.org/advanced/authentication/|httpx authentication].
|
|
260
|
-
Example a tuple containing username and password for basic authentication or
|
|
261
|
-
and instance of
|
|
262
|
-
[https://www.python-httpx.org/advanced/authentication/#digest-authentication|Digest authentication]
|
|
263
|
-
|
|
264
|
-
See `Call And Validate` keyword for example of using ``session`` argument.
|
|
265
|
-
|
|
266
|
-
Returns a
|
|
267
|
-
[https://schemathesis.readthedocs.io/en/stable/reference/python/#schemathesis.Response|Schemathesis Response]
|
|
268
|
-
object. Can be used to further validate the response, example check specific headers
|
|
269
|
-
or response body.
|
|
270
|
-
|
|
271
|
-
The `Call` and `Validate Response` keywords can be used together
|
|
272
|
-
to call a case and validate the response.
|
|
273
|
-
|
|
274
|
-
Example:
|
|
275
|
-
| ${response} = `Call` ${case}
|
|
276
|
-
| `Validate Response` ${case} ${response}
|
|
277
|
-
|
|
278
|
-
"""
|
|
279
|
-
headers = self._dot_dict_to_dict(headers) if headers else None
|
|
280
|
-
self.info(f"Calling case: {case.path} | {case.method} | {case.path_parameters}")
|
|
281
|
-
self._log_case(case)
|
|
282
|
-
if session:
|
|
283
|
-
self.info("Using provided session for the request.")
|
|
284
|
-
response = case.call(base_url=base_url, headers=headers, auth=auth, session=session)
|
|
285
|
-
self._log_request(response)
|
|
286
|
-
return response
|
|
287
|
-
|
|
288
|
-
@keyword
|
|
289
|
-
def validate_response(
|
|
290
|
-
self,
|
|
291
|
-
case: Case,
|
|
292
|
-
response: Response,
|
|
293
|
-
headers: dict[str, Any] | None = None,
|
|
294
|
-
auth: tuple[str, str] | Any | None = None,
|
|
295
|
-
) -> None:
|
|
296
|
-
"""Validate a Schemathesis response.
|
|
297
|
-
|
|
298
|
-
| Argument | Description |
|
|
299
|
-
| ``case`` | The Schemathesis case to be validated against. |
|
|
300
|
-
| ``response`` | The Schemathesis response to be validated. Returned by ``Call`` keyword. |
|
|
301
|
-
| ``headers`` | Optional HTTP headers to be added when calling the API endpoint. |
|
|
302
|
-
| ``auth`` | Optional authentication to be used when calling the case. |
|
|
303
|
-
|
|
304
|
-
``auth`` can be any authentication supported by
|
|
305
|
-
[https://www.python-httpx.org/advanced/authentication/|httpx authentication].
|
|
306
|
-
Example a tuple containing username and password for basic authentication or
|
|
307
|
-
and instance of
|
|
308
|
-
[https://www.python-httpx.org/advanced/authentication/#digest-authentication|Digest authentication]
|
|
309
|
-
|
|
310
|
-
The response is validated against the case's expectations.
|
|
311
|
-
The `Call` and `Validate Response` keywords can be used together
|
|
312
|
-
to call a case and validate the response. See the example from
|
|
313
|
-
`Call` keyword documentation.
|
|
314
|
-
"""
|
|
315
|
-
self.info(f"Validating response: {response.status_code} | {response.headers}")
|
|
316
|
-
transport_kwargs: dict[str, Any] = {}
|
|
317
|
-
if auth:
|
|
318
|
-
transport_kwargs["auth"] = auth
|
|
319
|
-
if headers:
|
|
320
|
-
transport_kwargs["headers"] = headers
|
|
321
|
-
case.validate_response(response=response, transport_kwargs=transport_kwargs)
|
|
322
|
-
self.info("Response validation passed.")
|
|
323
|
-
|
|
324
|
-
@keyword(name="As cURL")
|
|
325
|
-
def as_curl(self, case: Case) -> str:
|
|
326
|
-
"""Convert a Schemathesis case to a cURL command.
|
|
327
|
-
|
|
328
|
-
| Argument | Description |
|
|
329
|
-
| ``case`` | The Schemathesis case to be converted. |
|
|
330
|
-
|
|
331
|
-
Returns a string containing the cURL command that can be used to execute the same request as the case.
|
|
332
|
-
|
|
333
|
-
Example:
|
|
334
|
-
| ${curl_command} = `As cURL` ${case}
|
|
335
|
-
| Log ${curl_command}
|
|
336
|
-
"""
|
|
337
|
-
self.info(f"Converting case to cURL: {case.path} | {case.method} | {case.path_parameters}")
|
|
338
|
-
self._log_case(case)
|
|
339
|
-
curl_command = case.as_curl_command()
|
|
340
|
-
self.debug(f"Generated cURL command: {curl_command}")
|
|
341
|
-
return curl_command
|
|
342
|
-
|
|
343
|
-
def info(self, message: str) -> None:
|
|
344
|
-
logger.info(message)
|
|
345
|
-
|
|
346
|
-
def debug(self, message: str) -> None:
|
|
347
|
-
logger.debug(message)
|
|
348
|
-
|
|
349
|
-
def _log_case(
|
|
350
|
-
self,
|
|
351
|
-
case: Case,
|
|
352
|
-
headers: "dict[str, Any]|None" = None,
|
|
353
|
-
) -> None:
|
|
354
|
-
body = case.body if not isinstance(case.body, NotSet) else "Not set"
|
|
355
|
-
case_headers = headers or case.headers
|
|
356
|
-
self.debug(
|
|
357
|
-
f"Case headers {case_headers!r} body {body!r} "
|
|
358
|
-
f"cookies {case.cookies!r} path parameters {case.path_parameters!r}"
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
def _log_request(self, resposen: Response) -> None:
|
|
362
|
-
self.debug(
|
|
363
|
-
f"Request: {resposen.request.method} {resposen.request.url} "
|
|
364
|
-
f"headers: {resposen.request.headers!r} body: {resposen.request.body!r}"
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
def _dot_dict_to_dict(self, dot_dict: dict[str, Any]) -> dict[str, Any]:
|
|
368
|
-
if isinstance(dot_dict, DotDict):
|
|
369
|
-
return dict(dot_dict)
|
|
370
|
-
return dot_dict
|
{robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/LICENSE
RENAMED
|
File without changes
|
{robotframework_schemathesislibrary-2.3.3 → robotframework_schemathesislibrary-2.5.0}/setup.cfg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|