specmatic 2.24.0__tar.gz → 2.29.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.
Potentially problematic release.
This version of specmatic might be problematic. Click here for more details.
- {specmatic-2.24.0/specmatic.egg-info → specmatic-2.29.0}/PKG-INFO +73 -62
- {specmatic-2.24.0 → specmatic-2.29.0}/README.md +62 -60
- {specmatic-2.24.0 → specmatic-2.29.0}/setup.py +1 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/core/decorators.py +14 -14
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/core/specmatic.jar +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/core/specmatic.py +20 -20
- specmatic-2.24.0/specmatic/core/specmatic_stub.py → specmatic-2.29.0/specmatic/core/specmatic_mock.py +28 -28
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/servers/asgi_app_server.py +2 -2
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/servers/wsgi_app_server.py +2 -2
- specmatic-2.29.0/specmatic/version.py +2 -0
- {specmatic-2.24.0 → specmatic-2.29.0/specmatic.egg-info}/PKG-INFO +73 -62
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic.egg-info/SOURCES.txt +1 -1
- specmatic-2.24.0/specmatic/version.py +0 -2
- {specmatic-2.24.0 → specmatic-2.29.0}/MANIFEST.in +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/setup.cfg +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/__init__.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/build_utils.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/core/__init__.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/core/specmatic_base.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/core/specmatic_test.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/__init__.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/app_route_adapter.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/coverage_route.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/fastapi_app_route_adapter.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/flask_app_route_adapter.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/sanic_app_route_adapter.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/servers/__init__.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/servers/coverage_server.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/servers/fastapi_app_coverage_server.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/servers/flask_app_coverage_server.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/servers/sanic_app_coverage_server.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/generators/__init__.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/generators/pytest_generator.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/generators/test_generator_base.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/generators/unittest_generator.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/servers/__init__.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/servers/app_server.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/servers/wsgi_server_thread.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic/utils.py +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic.egg-info/dependency_links.txt +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic.egg-info/requires.txt +0 -0
- {specmatic-2.24.0 → specmatic-2.29.0}/specmatic.egg-info/top_level.txt +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: specmatic
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.29.0
|
|
4
4
|
Summary: A Python module for using the Specmatic Library.
|
|
5
5
|
Home-page: https://github.com/specmatic/specmatic-python-extensions
|
|
6
6
|
Author: Specmatic Builders
|
|
7
7
|
Author-email: info@core.in
|
|
8
|
+
Requires-Python: >=3.11
|
|
8
9
|
Description-Content-Type: text/markdown
|
|
9
10
|
Requires-Dist: pytest>=7.3.1
|
|
10
11
|
Requires-Dist: requests>=2.26.0
|
|
@@ -13,6 +14,14 @@ Requires-Dist: uvicorn>=0.18.0
|
|
|
13
14
|
Requires-Dist: fastapi>=0.70.0
|
|
14
15
|
Requires-Dist: flask>=2.2.5
|
|
15
16
|
Requires-Dist: sanic>=22.12.0
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: home-page
|
|
22
|
+
Dynamic: requires-dist
|
|
23
|
+
Dynamic: requires-python
|
|
24
|
+
Dynamic: summary
|
|
16
25
|
|
|
17
26
|
# Specmatic Python
|
|
18
27
|
This is a Python library to run [Specmatic](https://specmatic.io).
|
|
@@ -23,7 +32,7 @@ Specmatic is a contract driven development tool that allows us to turn OpenAPI c
|
|
|
23
32
|
The specmatic python library provides three main functions:
|
|
24
33
|
- The ability to start and stop a python web app like flask/sanic.
|
|
25
34
|
- The ability to run specmatic in test mode against an open api contract/spec.
|
|
26
|
-
- The ability to
|
|
35
|
+
- The ability to mock out an api dependency using the specmatic mock feature.
|
|
27
36
|
|
|
28
37
|
#### Running Contract Tests
|
|
29
38
|
A contract test validates an open api specification against a running api service.
|
|
@@ -46,7 +55,7 @@ The open api specification can be present either locally or in a [Central Contra
|
|
|
46
55
|
|
|
47
56
|
#### How does it work
|
|
48
57
|
- Specmatic uses the TestContract class defined above to inject tests dynamically into it when you run it via PyTest or UnitTest.
|
|
49
|
-
- The Specmatic Python package, invokes the Specmatic executable jar (via command line) in a separate process to start
|
|
58
|
+
- The Specmatic Python package, invokes the Specmatic executable jar (via command line) in a separate process to start mocks and run tests.
|
|
50
59
|
- It is the specmatic jar which runs the contract tests and generates a JUnit test summary report.
|
|
51
60
|
- The Specmatic Python package ingests the JUnit test summary report and generates test methods corresponding to every contract test.
|
|
52
61
|
- These dynamic test methods are added to the ```TestContract``` class and hence we seem them reported seamlessly by PyTest/Unittest like this:
|
|
@@ -60,23 +69,22 @@ test/test_contract_with_coverage.py::TestContract::test_Scenario: GET /products
|
|
|
60
69
|
|
|
61
70
|
## WSGI Apps
|
|
62
71
|
|
|
63
|
-
#### To run contract tests with a
|
|
72
|
+
#### To run contract tests with a mock for a wsgi app (like Flask):
|
|
64
73
|
|
|
65
74
|
``````python
|
|
66
75
|
class TestContract:
|
|
67
76
|
pass
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Specmatic()
|
|
71
|
-
.with_project_root(PROJECT_ROOT)
|
|
72
|
-
.
|
|
73
|
-
.with_wsgi_app(app, app_host, app_port)
|
|
74
|
-
.test(TestContract)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
Specmatic()
|
|
80
|
+
.with_project_root(PROJECT_ROOT)
|
|
81
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
82
|
+
.with_wsgi_app(app, app_host, app_port)
|
|
83
|
+
.test(TestContract)
|
|
75
84
|
.run()
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
|
|
78
86
|
if __name__ == '__main__':
|
|
79
|
-
pytest.main()
|
|
87
|
+
pytest.main()
|
|
80
88
|
``````
|
|
81
89
|
|
|
82
90
|
- In this, we are passing:
|
|
@@ -84,16 +92,16 @@ pytest.main()
|
|
|
84
92
|
- app_host and app_port. If they are not specified, the app will be started on a random available port on 127.0.0.1.
|
|
85
93
|
- You would need a [specmatic config](https://specmatic.io/documentation/specmatic_json.html) file to be present in the root directory of your project.
|
|
86
94
|
- an empty test class.
|
|
87
|
-
-
|
|
88
|
-
The
|
|
89
|
-
If they are not supplied, the
|
|
90
|
-
[Click here](https://specmatic.io/documentation/service_virtualization_tutorial.html) to learn more about
|
|
95
|
+
- mock_host, mock_port, optional list of json files to set expectations on the mock.
|
|
96
|
+
The mock_host, mock_port will be used to run the specmatic mock server.
|
|
97
|
+
If they are not supplied, the mock will be started on a random available port on 127.0.0.1.
|
|
98
|
+
[Click here](https://specmatic.io/documentation/service_virtualization_tutorial.html) to learn more about mocking/service virtualization.
|
|
91
99
|
- You can run this test from either your IDE or command line by pointing pytest to your test folder:
|
|
92
100
|
``````pytest test -v -s``````
|
|
93
101
|
- NOTE: Please ensure that you set the '-v' and '-s' flags while running pytest as otherwise pytest may swallow up the console output.
|
|
94
102
|
|
|
95
103
|
|
|
96
|
-
#### To run contract tests without a
|
|
104
|
+
#### To run contract tests without a mock:
|
|
97
105
|
|
|
98
106
|
``````python
|
|
99
107
|
class TestContract:
|
|
@@ -108,33 +116,35 @@ Specmatic() \
|
|
|
108
116
|
|
|
109
117
|
## ASGI Apps
|
|
110
118
|
|
|
111
|
-
#### To run contract tests with a
|
|
119
|
+
#### To run contract tests with a mock for an asgi app (like sanic):
|
|
112
120
|
- If you are using an asgi app like sanic, fastapi, use the ``````with_asgi_app`````` function and pass it a string in the 'module:app' format.
|
|
121
|
+
|
|
113
122
|
``````python
|
|
114
123
|
class TestContract:
|
|
115
124
|
pass
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Specmatic()
|
|
119
|
-
.with_project_root(PROJECT_ROOT)
|
|
120
|
-
.
|
|
121
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
122
|
-
.test(TestContract)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
Specmatic()
|
|
128
|
+
.with_project_root(PROJECT_ROOT)
|
|
129
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
130
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
131
|
+
.test(TestContract)
|
|
123
132
|
.run()
|
|
124
133
|
``````
|
|
125
134
|
|
|
126
|
-
### Passing extra arguments to
|
|
135
|
+
### Passing extra arguments to mock/test
|
|
127
136
|
- To pass arguments like '--strict', '--testBaseUrl', pass them as a list to the 'args' parameter:
|
|
137
|
+
|
|
128
138
|
``````python
|
|
129
139
|
class TestContract:
|
|
130
140
|
pass
|
|
131
141
|
|
|
132
142
|
|
|
133
|
-
Specmatic()
|
|
134
|
-
.with_project_root(PROJECT_ROOT)
|
|
135
|
-
.
|
|
136
|
-
.with_wsgi_app(app, port=app_port)
|
|
137
|
-
.test(TestContract, args=['--testBaseURL=http://localhost:5000'])
|
|
143
|
+
Specmatic()
|
|
144
|
+
.with_project_root(PROJECT_ROOT)
|
|
145
|
+
.with_mock(mock_host, mock_port, [expectation_json_file], ['--strict'])
|
|
146
|
+
.with_wsgi_app(app, port=app_port)
|
|
147
|
+
.test(TestContract, args=['--testBaseURL=http://localhost:5000'])
|
|
138
148
|
.run()
|
|
139
149
|
``````
|
|
140
150
|
|
|
@@ -148,11 +158,11 @@ class TestContract:
|
|
|
148
158
|
pass
|
|
149
159
|
|
|
150
160
|
|
|
151
|
-
Specmatic()
|
|
152
|
-
.with_project_root(PROJECT_ROOT)
|
|
153
|
-
.
|
|
154
|
-
.with_wsgi_app(app, app_host, app_port)
|
|
155
|
-
.test_with_api_coverage_for_flask_app(TestContract, app)
|
|
161
|
+
Specmatic()
|
|
162
|
+
.with_project_root(PROJECT_ROOT)
|
|
163
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
164
|
+
.with_wsgi_app(app, app_host, app_port)
|
|
165
|
+
.test_with_api_coverage_for_flask_app(TestContract, app)
|
|
156
166
|
.run()
|
|
157
167
|
``````
|
|
158
168
|
|
|
@@ -163,11 +173,11 @@ class TestContract:
|
|
|
163
173
|
pass
|
|
164
174
|
|
|
165
175
|
|
|
166
|
-
Specmatic()
|
|
167
|
-
.with_project_root(PROJECT_ROOT)
|
|
168
|
-
.
|
|
169
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
170
|
-
.test_with_api_coverage_for_sanic_app(TestContract, app)
|
|
176
|
+
Specmatic()
|
|
177
|
+
.with_project_root(PROJECT_ROOT)
|
|
178
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
179
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
180
|
+
.test_with_api_coverage_for_sanic_app(TestContract, app)
|
|
171
181
|
.run()
|
|
172
182
|
``````
|
|
173
183
|
|
|
@@ -178,11 +188,11 @@ class TestContract:
|
|
|
178
188
|
pass
|
|
179
189
|
|
|
180
190
|
|
|
181
|
-
Specmatic()
|
|
182
|
-
.with_project_root(PROJECT_ROOT)
|
|
183
|
-
.
|
|
184
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
185
|
-
.test_with_api_coverage_for_fastapi_app(TestContract, app)
|
|
191
|
+
Specmatic()
|
|
192
|
+
.with_project_root(PROJECT_ROOT)
|
|
193
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
194
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
195
|
+
.test_with_api_coverage_for_fastapi_app(TestContract, app)
|
|
186
196
|
.run()
|
|
187
197
|
``````
|
|
188
198
|
|
|
@@ -193,14 +203,14 @@ The ``````CoverageRoute`````` class has two properties:
|
|
|
193
203
|
``````url`````` : This represents your route url in this format: `````` /orders/{order_id}``````
|
|
194
204
|
``````method`````` : A list of HTTP methods supported on the route, for instance : ``````['GET', 'POST']``````
|
|
195
205
|
|
|
196
|
-
You can then enable coverage by passing your adapter like this:
|
|
206
|
+
You can then enable coverage by passing your adapter like this:
|
|
197
207
|
|
|
198
208
|
``````python
|
|
199
|
-
Specmatic()
|
|
200
|
-
.with_project_root(PROJECT_ROOT)
|
|
201
|
-
.
|
|
202
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
203
|
-
.test_with_api_coverage(TestContract, MyAppRouteAdapter(app))
|
|
209
|
+
Specmatic()
|
|
210
|
+
.with_project_root(PROJECT_ROOT)
|
|
211
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
212
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
213
|
+
.test_with_api_coverage(TestContract, MyAppRouteAdapter(app))
|
|
204
214
|
.run()
|
|
205
215
|
``````
|
|
206
216
|
|
|
@@ -215,7 +225,8 @@ You can also easily implement your own coverage server if you have written a cus
|
|
|
215
225
|
The only point to remember in mind is that the EndPointsApi url should return a list of routes in the format used buy Spring Actuator's ```````/actuator/mappings``````` endpoint
|
|
216
226
|
as described [here](https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/#mappings).
|
|
217
227
|
|
|
218
|
-
Here's an example where we start both our FastApi app and coverage server outside the specmatic api call.
|
|
228
|
+
Here's an example where we start both our FastApi app and coverage server outside the specmatic api call.
|
|
229
|
+
|
|
219
230
|
``````python
|
|
220
231
|
app_server = ASGIAppServer('test.apps.fast_api:app', app_host, app_port)
|
|
221
232
|
coverage_server = FastApiAppCoverageServer(app)
|
|
@@ -228,11 +239,11 @@ class TestContract:
|
|
|
228
239
|
pass
|
|
229
240
|
|
|
230
241
|
|
|
231
|
-
Specmatic()
|
|
232
|
-
.with_project_root(PROJECT_ROOT)
|
|
233
|
-
.
|
|
234
|
-
.with_endpoints_api(coverage_server.endpoints_api)
|
|
235
|
-
.test(TestContract, app_host, app_port)
|
|
242
|
+
Specmatic()
|
|
243
|
+
.with_project_root(PROJECT_ROOT)
|
|
244
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
245
|
+
.with_endpoints_api(coverage_server.endpoints_api)
|
|
246
|
+
.test(TestContract, app_host, app_port)
|
|
236
247
|
.run()
|
|
237
248
|
|
|
238
249
|
app_server.stop()
|
|
@@ -253,7 +264,7 @@ coverage_server.stop()
|
|
|
253
264
|
## Sample Projects
|
|
254
265
|
- [Check out the Specmatic Order BFF Python repo](https://github.com/specmatic/specmatic-order-bff-python/) to see more examples of how to use specmatic with a Flask app.
|
|
255
266
|
- [Check out the Specmatic Order BFF Python Sanic repo](https://github.com/specmatic/specmatic-order-bff-python-sanic/) to see more examples of how to use specmatic with a Sanic app.
|
|
256
|
-
- [Check out the Specmatic Order API Python repo](https://github.com/specmatic/specmatic-order-api-python/) to see an examples of how to just run tests without using a
|
|
267
|
+
- [Check out the Specmatic Order API Python repo](https://github.com/specmatic/specmatic-order-api-python/) to see an examples of how to just run tests without using a mock.
|
|
257
268
|
|
|
258
269
|
|
|
259
270
|
|
|
@@ -7,7 +7,7 @@ Specmatic is a contract driven development tool that allows us to turn OpenAPI c
|
|
|
7
7
|
The specmatic python library provides three main functions:
|
|
8
8
|
- The ability to start and stop a python web app like flask/sanic.
|
|
9
9
|
- The ability to run specmatic in test mode against an open api contract/spec.
|
|
10
|
-
- The ability to
|
|
10
|
+
- The ability to mock out an api dependency using the specmatic mock feature.
|
|
11
11
|
|
|
12
12
|
#### Running Contract Tests
|
|
13
13
|
A contract test validates an open api specification against a running api service.
|
|
@@ -30,7 +30,7 @@ The open api specification can be present either locally or in a [Central Contra
|
|
|
30
30
|
|
|
31
31
|
#### How does it work
|
|
32
32
|
- Specmatic uses the TestContract class defined above to inject tests dynamically into it when you run it via PyTest or UnitTest.
|
|
33
|
-
- The Specmatic Python package, invokes the Specmatic executable jar (via command line) in a separate process to start
|
|
33
|
+
- The Specmatic Python package, invokes the Specmatic executable jar (via command line) in a separate process to start mocks and run tests.
|
|
34
34
|
- It is the specmatic jar which runs the contract tests and generates a JUnit test summary report.
|
|
35
35
|
- The Specmatic Python package ingests the JUnit test summary report and generates test methods corresponding to every contract test.
|
|
36
36
|
- These dynamic test methods are added to the ```TestContract``` class and hence we seem them reported seamlessly by PyTest/Unittest like this:
|
|
@@ -44,23 +44,22 @@ test/test_contract_with_coverage.py::TestContract::test_Scenario: GET /products
|
|
|
44
44
|
|
|
45
45
|
## WSGI Apps
|
|
46
46
|
|
|
47
|
-
#### To run contract tests with a
|
|
47
|
+
#### To run contract tests with a mock for a wsgi app (like Flask):
|
|
48
48
|
|
|
49
49
|
``````python
|
|
50
50
|
class TestContract:
|
|
51
51
|
pass
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Specmatic()
|
|
55
|
-
.with_project_root(PROJECT_ROOT)
|
|
56
|
-
.
|
|
57
|
-
.with_wsgi_app(app, app_host, app_port)
|
|
58
|
-
.test(TestContract)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
Specmatic()
|
|
55
|
+
.with_project_root(PROJECT_ROOT)
|
|
56
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
57
|
+
.with_wsgi_app(app, app_host, app_port)
|
|
58
|
+
.test(TestContract)
|
|
59
59
|
.run()
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
|
|
62
61
|
if __name__ == '__main__':
|
|
63
|
-
pytest.main()
|
|
62
|
+
pytest.main()
|
|
64
63
|
``````
|
|
65
64
|
|
|
66
65
|
- In this, we are passing:
|
|
@@ -68,16 +67,16 @@ pytest.main()
|
|
|
68
67
|
- app_host and app_port. If they are not specified, the app will be started on a random available port on 127.0.0.1.
|
|
69
68
|
- You would need a [specmatic config](https://specmatic.io/documentation/specmatic_json.html) file to be present in the root directory of your project.
|
|
70
69
|
- an empty test class.
|
|
71
|
-
-
|
|
72
|
-
The
|
|
73
|
-
If they are not supplied, the
|
|
74
|
-
[Click here](https://specmatic.io/documentation/service_virtualization_tutorial.html) to learn more about
|
|
70
|
+
- mock_host, mock_port, optional list of json files to set expectations on the mock.
|
|
71
|
+
The mock_host, mock_port will be used to run the specmatic mock server.
|
|
72
|
+
If they are not supplied, the mock will be started on a random available port on 127.0.0.1.
|
|
73
|
+
[Click here](https://specmatic.io/documentation/service_virtualization_tutorial.html) to learn more about mocking/service virtualization.
|
|
75
74
|
- You can run this test from either your IDE or command line by pointing pytest to your test folder:
|
|
76
75
|
``````pytest test -v -s``````
|
|
77
76
|
- NOTE: Please ensure that you set the '-v' and '-s' flags while running pytest as otherwise pytest may swallow up the console output.
|
|
78
77
|
|
|
79
78
|
|
|
80
|
-
#### To run contract tests without a
|
|
79
|
+
#### To run contract tests without a mock:
|
|
81
80
|
|
|
82
81
|
``````python
|
|
83
82
|
class TestContract:
|
|
@@ -92,33 +91,35 @@ Specmatic() \
|
|
|
92
91
|
|
|
93
92
|
## ASGI Apps
|
|
94
93
|
|
|
95
|
-
#### To run contract tests with a
|
|
94
|
+
#### To run contract tests with a mock for an asgi app (like sanic):
|
|
96
95
|
- If you are using an asgi app like sanic, fastapi, use the ``````with_asgi_app`````` function and pass it a string in the 'module:app' format.
|
|
96
|
+
|
|
97
97
|
``````python
|
|
98
98
|
class TestContract:
|
|
99
99
|
pass
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Specmatic()
|
|
103
|
-
.with_project_root(PROJECT_ROOT)
|
|
104
|
-
.
|
|
105
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
106
|
-
.test(TestContract)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
Specmatic()
|
|
103
|
+
.with_project_root(PROJECT_ROOT)
|
|
104
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
105
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
106
|
+
.test(TestContract)
|
|
107
107
|
.run()
|
|
108
108
|
``````
|
|
109
109
|
|
|
110
|
-
### Passing extra arguments to
|
|
110
|
+
### Passing extra arguments to mock/test
|
|
111
111
|
- To pass arguments like '--strict', '--testBaseUrl', pass them as a list to the 'args' parameter:
|
|
112
|
+
|
|
112
113
|
``````python
|
|
113
114
|
class TestContract:
|
|
114
115
|
pass
|
|
115
116
|
|
|
116
117
|
|
|
117
|
-
Specmatic()
|
|
118
|
-
.with_project_root(PROJECT_ROOT)
|
|
119
|
-
.
|
|
120
|
-
.with_wsgi_app(app, port=app_port)
|
|
121
|
-
.test(TestContract, args=['--testBaseURL=http://localhost:5000'])
|
|
118
|
+
Specmatic()
|
|
119
|
+
.with_project_root(PROJECT_ROOT)
|
|
120
|
+
.with_mock(mock_host, mock_port, [expectation_json_file], ['--strict'])
|
|
121
|
+
.with_wsgi_app(app, port=app_port)
|
|
122
|
+
.test(TestContract, args=['--testBaseURL=http://localhost:5000'])
|
|
122
123
|
.run()
|
|
123
124
|
``````
|
|
124
125
|
|
|
@@ -132,11 +133,11 @@ class TestContract:
|
|
|
132
133
|
pass
|
|
133
134
|
|
|
134
135
|
|
|
135
|
-
Specmatic()
|
|
136
|
-
.with_project_root(PROJECT_ROOT)
|
|
137
|
-
.
|
|
138
|
-
.with_wsgi_app(app, app_host, app_port)
|
|
139
|
-
.test_with_api_coverage_for_flask_app(TestContract, app)
|
|
136
|
+
Specmatic()
|
|
137
|
+
.with_project_root(PROJECT_ROOT)
|
|
138
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
139
|
+
.with_wsgi_app(app, app_host, app_port)
|
|
140
|
+
.test_with_api_coverage_for_flask_app(TestContract, app)
|
|
140
141
|
.run()
|
|
141
142
|
``````
|
|
142
143
|
|
|
@@ -147,11 +148,11 @@ class TestContract:
|
|
|
147
148
|
pass
|
|
148
149
|
|
|
149
150
|
|
|
150
|
-
Specmatic()
|
|
151
|
-
.with_project_root(PROJECT_ROOT)
|
|
152
|
-
.
|
|
153
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
154
|
-
.test_with_api_coverage_for_sanic_app(TestContract, app)
|
|
151
|
+
Specmatic()
|
|
152
|
+
.with_project_root(PROJECT_ROOT)
|
|
153
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
154
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
155
|
+
.test_with_api_coverage_for_sanic_app(TestContract, app)
|
|
155
156
|
.run()
|
|
156
157
|
``````
|
|
157
158
|
|
|
@@ -162,11 +163,11 @@ class TestContract:
|
|
|
162
163
|
pass
|
|
163
164
|
|
|
164
165
|
|
|
165
|
-
Specmatic()
|
|
166
|
-
.with_project_root(PROJECT_ROOT)
|
|
167
|
-
.
|
|
168
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
169
|
-
.test_with_api_coverage_for_fastapi_app(TestContract, app)
|
|
166
|
+
Specmatic()
|
|
167
|
+
.with_project_root(PROJECT_ROOT)
|
|
168
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
169
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
170
|
+
.test_with_api_coverage_for_fastapi_app(TestContract, app)
|
|
170
171
|
.run()
|
|
171
172
|
``````
|
|
172
173
|
|
|
@@ -177,14 +178,14 @@ The ``````CoverageRoute`````` class has two properties:
|
|
|
177
178
|
``````url`````` : This represents your route url in this format: `````` /orders/{order_id}``````
|
|
178
179
|
``````method`````` : A list of HTTP methods supported on the route, for instance : ``````['GET', 'POST']``````
|
|
179
180
|
|
|
180
|
-
You can then enable coverage by passing your adapter like this:
|
|
181
|
+
You can then enable coverage by passing your adapter like this:
|
|
181
182
|
|
|
182
183
|
``````python
|
|
183
|
-
Specmatic()
|
|
184
|
-
.with_project_root(PROJECT_ROOT)
|
|
185
|
-
.
|
|
186
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
187
|
-
.test_with_api_coverage(TestContract, MyAppRouteAdapter(app))
|
|
184
|
+
Specmatic()
|
|
185
|
+
.with_project_root(PROJECT_ROOT)
|
|
186
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
187
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
188
|
+
.test_with_api_coverage(TestContract, MyAppRouteAdapter(app))
|
|
188
189
|
.run()
|
|
189
190
|
``````
|
|
190
191
|
|
|
@@ -199,7 +200,8 @@ You can also easily implement your own coverage server if you have written a cus
|
|
|
199
200
|
The only point to remember in mind is that the EndPointsApi url should return a list of routes in the format used buy Spring Actuator's ```````/actuator/mappings``````` endpoint
|
|
200
201
|
as described [here](https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/#mappings).
|
|
201
202
|
|
|
202
|
-
Here's an example where we start both our FastApi app and coverage server outside the specmatic api call.
|
|
203
|
+
Here's an example where we start both our FastApi app and coverage server outside the specmatic api call.
|
|
204
|
+
|
|
203
205
|
``````python
|
|
204
206
|
app_server = ASGIAppServer('test.apps.fast_api:app', app_host, app_port)
|
|
205
207
|
coverage_server = FastApiAppCoverageServer(app)
|
|
@@ -212,11 +214,11 @@ class TestContract:
|
|
|
212
214
|
pass
|
|
213
215
|
|
|
214
216
|
|
|
215
|
-
Specmatic()
|
|
216
|
-
.with_project_root(PROJECT_ROOT)
|
|
217
|
-
.
|
|
218
|
-
.with_endpoints_api(coverage_server.endpoints_api)
|
|
219
|
-
.test(TestContract, app_host, app_port)
|
|
217
|
+
Specmatic()
|
|
218
|
+
.with_project_root(PROJECT_ROOT)
|
|
219
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
220
|
+
.with_endpoints_api(coverage_server.endpoints_api)
|
|
221
|
+
.test(TestContract, app_host, app_port)
|
|
220
222
|
.run()
|
|
221
223
|
|
|
222
224
|
app_server.stop()
|
|
@@ -237,7 +239,7 @@ coverage_server.stop()
|
|
|
237
239
|
## Sample Projects
|
|
238
240
|
- [Check out the Specmatic Order BFF Python repo](https://github.com/specmatic/specmatic-order-bff-python/) to see more examples of how to use specmatic with a Flask app.
|
|
239
241
|
- [Check out the Specmatic Order BFF Python Sanic repo](https://github.com/specmatic/specmatic-order-bff-python-sanic/) to see more examples of how to use specmatic with a Sanic app.
|
|
240
|
-
- [Check out the Specmatic Order API Python repo](https://github.com/specmatic/specmatic-order-api-python/) to see an examples of how to just run tests without using a
|
|
242
|
+
- [Check out the Specmatic Order API Python repo](https://github.com/specmatic/specmatic-order-api-python/) to see an examples of how to just run tests without using a mock.
|
|
241
243
|
|
|
242
244
|
|
|
243
245
|
|
|
@@ -14,6 +14,7 @@ long_description = (this_directory / "README.md").read_text()
|
|
|
14
14
|
|
|
15
15
|
setup(
|
|
16
16
|
name='specmatic',
|
|
17
|
+
python_requires='>=3.11',
|
|
17
18
|
version=version['__version__'],
|
|
18
19
|
description='A Python module for using the Specmatic Library.',
|
|
19
20
|
long_description=long_description,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
|
|
3
|
-
from specmatic.core.
|
|
3
|
+
from specmatic.core.specmatic_mock import SpecmaticMock
|
|
4
4
|
from specmatic.core.specmatic_test import SpecmaticTest
|
|
5
5
|
from specmatic.coverage.app_route_adapter import AppRouteAdapter
|
|
6
6
|
from specmatic.coverage.servers.coverage_server import CoverageServer
|
|
@@ -11,15 +11,15 @@ from specmatic.servers.wsgi_app_server import WSGIAppServer
|
|
|
11
11
|
from specmatic.utils import get_junit_report_file_path
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def
|
|
14
|
+
def specmatic_mock(host: str = '127.0.0.1', port: int = 0, project_root: str = '', expectations=None,
|
|
15
15
|
specmatic_config_file_path: str = ''):
|
|
16
16
|
def decorator(cls):
|
|
17
17
|
try:
|
|
18
|
-
cls.
|
|
19
|
-
cls.
|
|
18
|
+
cls.mock = SpecmaticMock(host, port, project_root, specmatic_config_file_path)
|
|
19
|
+
cls.mock.set_expectations(expectations)
|
|
20
20
|
except Exception as e:
|
|
21
|
-
if hasattr(cls, '
|
|
22
|
-
cls.
|
|
21
|
+
if hasattr(cls, 'mock'):
|
|
22
|
+
cls.mock.stop()
|
|
23
23
|
if hasattr(cls, 'app'):
|
|
24
24
|
cls.app.stop()
|
|
25
25
|
print(f"Error: {e}")
|
|
@@ -60,15 +60,15 @@ def specmatic_contract_test(host: str = '127.0.0,1', port: int = 0,
|
|
|
60
60
|
except Exception as e:
|
|
61
61
|
if hasattr(cls, 'app'):
|
|
62
62
|
cls.app.stop()
|
|
63
|
-
if hasattr(cls, '
|
|
64
|
-
cls.
|
|
63
|
+
if hasattr(cls, 'mock'):
|
|
64
|
+
cls.mock.stop()
|
|
65
65
|
if hasattr(cls, 'coverage_server'):
|
|
66
66
|
cls.coverage_server.stop()
|
|
67
67
|
print(f"Error: {e}")
|
|
68
68
|
raise e
|
|
69
69
|
finally:
|
|
70
|
-
if hasattr(cls, '
|
|
71
|
-
cls.
|
|
70
|
+
if hasattr(cls, 'mock'):
|
|
71
|
+
cls.mock.stop()
|
|
72
72
|
if hasattr(cls, 'app'):
|
|
73
73
|
cls.app.stop()
|
|
74
74
|
if hasattr(cls, 'coverage_server'):
|
|
@@ -84,8 +84,8 @@ def start_wsgi_app(app, host: str = '127.0.0.1', port: int = 0):
|
|
|
84
84
|
cls.app.start()
|
|
85
85
|
return cls
|
|
86
86
|
except Exception as e:
|
|
87
|
-
if hasattr(cls, '
|
|
88
|
-
cls.
|
|
87
|
+
if hasattr(cls, 'mock'):
|
|
88
|
+
cls.mock.stop()
|
|
89
89
|
if hasattr(cls, 'app'):
|
|
90
90
|
cls.app.stop()
|
|
91
91
|
print(f"Error: {e}")
|
|
@@ -101,8 +101,8 @@ def start_asgi_app(app_module: str, host: str = '127.0.0.1', port: int = 0):
|
|
|
101
101
|
cls.app.start()
|
|
102
102
|
return cls
|
|
103
103
|
except Exception as e:
|
|
104
|
-
if hasattr(cls, '
|
|
105
|
-
cls.
|
|
104
|
+
if hasattr(cls, 'mock'):
|
|
105
|
+
cls.mock.stop()
|
|
106
106
|
if hasattr(cls, 'app'):
|
|
107
107
|
cls.app.stop()
|
|
108
108
|
print(f"Error: {e}")
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
|
|
3
3
|
from specmatic.coverage.app_route_adapter import AppRouteAdapter
|
|
4
|
-
from specmatic.core.
|
|
4
|
+
from specmatic.core.specmatic_mock import SpecmaticMock
|
|
5
5
|
from specmatic.core.specmatic_test import SpecmaticTest
|
|
6
6
|
from specmatic.generators.pytest_generator import PyTestGenerator
|
|
7
7
|
from specmatic.generators.unittest_generator import UnitTestGenerator
|
|
@@ -25,11 +25,11 @@ class Specmatic:
|
|
|
25
25
|
self.set_app_config_func = None
|
|
26
26
|
self.app_server = None
|
|
27
27
|
|
|
28
|
-
self.
|
|
29
|
-
self.
|
|
28
|
+
self.mock_host = None
|
|
29
|
+
self.mock_port = None
|
|
30
30
|
self.expectations = None
|
|
31
|
-
self.
|
|
32
|
-
self.
|
|
31
|
+
self.mock_args = None
|
|
32
|
+
self.mock = None
|
|
33
33
|
|
|
34
34
|
self.test_class = None
|
|
35
35
|
self.test_host = None
|
|
@@ -39,7 +39,7 @@ class Specmatic:
|
|
|
39
39
|
self.project_root = ''
|
|
40
40
|
self.specmatic_config_file_path = ''
|
|
41
41
|
|
|
42
|
-
self.
|
|
42
|
+
self.run_mock = False
|
|
43
43
|
self.run_app = False
|
|
44
44
|
self.run_tests = False
|
|
45
45
|
|
|
@@ -54,12 +54,12 @@ class Specmatic:
|
|
|
54
54
|
self.specmatic_config_file_path = specmatic_config_file_path
|
|
55
55
|
return self
|
|
56
56
|
|
|
57
|
-
def
|
|
58
|
-
self.
|
|
59
|
-
self.
|
|
60
|
-
self.
|
|
57
|
+
def with_mock(self, mock_host: str = '127.0.0.1', mock_port: int = 0, expectations=None, args=None):
|
|
58
|
+
self.mock_host = mock_host
|
|
59
|
+
self.mock_port = mock_port
|
|
60
|
+
self.run_mock = True
|
|
61
61
|
self.expectations = expectations
|
|
62
|
-
self.
|
|
62
|
+
self.mock_args = args
|
|
63
63
|
return self
|
|
64
64
|
|
|
65
65
|
def app(self, app_server: AppServer):
|
|
@@ -136,13 +136,13 @@ class Specmatic:
|
|
|
136
136
|
self.app_server = ASGIAppServer(self.app_module, self.app_host, self.app_port, self.set_app_config_func,
|
|
137
137
|
self.reset_app_config_func)
|
|
138
138
|
|
|
139
|
-
def
|
|
140
|
-
if self.
|
|
141
|
-
self.
|
|
142
|
-
self.
|
|
143
|
-
self.
|
|
139
|
+
def __start_mock(self):
|
|
140
|
+
if self.run_mock:
|
|
141
|
+
self.mock = SpecmaticMock(self.mock_host, self.mock_port, self.project_root, self.specmatic_config_file_path,
|
|
142
|
+
self.mock_args)
|
|
143
|
+
self.mock.set_expectations(self.expectations)
|
|
144
144
|
if self.app_server is not None:
|
|
145
|
-
self.app_server.set_app_config(self.
|
|
145
|
+
self.app_server.set_app_config(self.mock.host, self.mock.port)
|
|
146
146
|
|
|
147
147
|
def __execute_tests(self):
|
|
148
148
|
if self.run_tests:
|
|
@@ -169,7 +169,7 @@ class Specmatic:
|
|
|
169
169
|
def run(self):
|
|
170
170
|
try:
|
|
171
171
|
self.__init_app_server()
|
|
172
|
-
self.
|
|
172
|
+
self.__start_mock()
|
|
173
173
|
self.__execute_tests()
|
|
174
174
|
except Exception as e:
|
|
175
175
|
print(f"Error: {e}")
|
|
@@ -180,5 +180,5 @@ class Specmatic:
|
|
|
180
180
|
if self.app_server is not None:
|
|
181
181
|
self.app_server.stop()
|
|
182
182
|
self.app_server.reset_app_config()
|
|
183
|
-
if self.
|
|
184
|
-
self.
|
|
183
|
+
if self.mock is not None:
|
|
184
|
+
self.mock.stop()
|
|
@@ -11,25 +11,25 @@ import requests
|
|
|
11
11
|
from specmatic.core.specmatic_base import SpecmaticBase
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class
|
|
14
|
+
class SpecmaticMock(SpecmaticBase):
|
|
15
15
|
|
|
16
16
|
def __init__(self, host: str = '127.0.0.1', port: int = 0, project_root: str = '',
|
|
17
17
|
specmatic_config_file_path: str = '', args=None):
|
|
18
18
|
super().__init__(host, port, project_root, specmatic_config_file_path, args)
|
|
19
|
-
self.
|
|
19
|
+
self.__mock_started_event = None
|
|
20
20
|
self.__process = None
|
|
21
21
|
self.__free_port_message = 'Free port found'
|
|
22
|
-
self.
|
|
22
|
+
self.__mock_serving_message = 'serving endpoints from specs'
|
|
23
23
|
self.__error_queue = Queue()
|
|
24
24
|
self.__start()
|
|
25
25
|
|
|
26
26
|
def __start(self):
|
|
27
27
|
try:
|
|
28
|
-
self.
|
|
29
|
-
self.
|
|
30
|
-
self.
|
|
31
|
-
self.
|
|
32
|
-
self.
|
|
28
|
+
self.__mock_started_event = threading.Event()
|
|
29
|
+
self.__start_specmatic_mock_in_subprocess()
|
|
30
|
+
self.__start_reading_mock_output()
|
|
31
|
+
self.__start_reading_mock_error_output()
|
|
32
|
+
self.__wait_till_mock_has_started()
|
|
33
33
|
except Exception as e:
|
|
34
34
|
self.stop()
|
|
35
35
|
print(f"Error: {e}")
|
|
@@ -37,13 +37,13 @@ class SpecmaticStub(SpecmaticBase):
|
|
|
37
37
|
|
|
38
38
|
def stop(self):
|
|
39
39
|
if self.__process is not None:
|
|
40
|
-
print(f"\n Shutting down specmatic
|
|
40
|
+
print(f"\n Shutting down specmatic mock server on {self.host}:{self.port}, please wait ...")
|
|
41
41
|
self.__process.kill()
|
|
42
42
|
|
|
43
43
|
def set_expectations(self, file_paths: list[str], port: int|None = None):
|
|
44
44
|
port = port or self.port
|
|
45
45
|
if port is None:
|
|
46
|
-
raise Exception("The port needs to be specified, as the
|
|
46
|
+
raise Exception("The port needs to be specified, as the mock started on a random port and extraction failed from logs")
|
|
47
47
|
|
|
48
48
|
if file_paths is None:
|
|
49
49
|
file_paths = []
|
|
@@ -68,49 +68,49 @@ class SpecmaticStub(SpecmaticBase):
|
|
|
68
68
|
def __get_expectations_api_url(self, port):
|
|
69
69
|
return f'http://{self.host}:{port}/_specmatic/expectations'
|
|
70
70
|
|
|
71
|
-
def
|
|
72
|
-
|
|
71
|
+
def __start_specmatic_mock_in_subprocess(self):
|
|
72
|
+
mock_command = self.__create_mock_process_command()
|
|
73
73
|
if self.host != '' and self.port != 0:
|
|
74
|
-
print(f"\n Starting specmatic
|
|
75
|
-
self.__process = subprocess.Popen(
|
|
74
|
+
print(f"\n Starting specmatic mock server on {self.host}:{self.port}")
|
|
75
|
+
self.__process = subprocess.Popen(mock_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
76
76
|
|
|
77
|
-
def
|
|
77
|
+
def __start_reading_mock_output(self):
|
|
78
78
|
stdout_reader = threading.Thread(target=self.__read_process_output, daemon=True)
|
|
79
79
|
stdout_reader.start()
|
|
80
80
|
|
|
81
|
-
def
|
|
81
|
+
def __start_reading_mock_error_output(self):
|
|
82
82
|
error_reader = threading.Thread(target=self.__read_process_error_output, daemon=True)
|
|
83
83
|
error_reader.start()
|
|
84
84
|
|
|
85
|
-
def
|
|
86
|
-
self.
|
|
85
|
+
def __wait_till_mock_has_started(self):
|
|
86
|
+
self.__mock_started_event.wait()
|
|
87
87
|
if not self.__error_queue.empty():
|
|
88
88
|
error = self.__error_queue.get()
|
|
89
|
-
raise Exception(f"An exception occurred while starting the
|
|
89
|
+
raise Exception(f"An exception occurred while starting the mock: {error}")
|
|
90
90
|
|
|
91
91
|
def __read_process_output(self):
|
|
92
|
-
def
|
|
92
|
+
def signal_event_if_mock_has_started(line):
|
|
93
93
|
if self.__free_port_message in line and self.port == 0:
|
|
94
94
|
self.port = line.split(self.__free_port_message + ":")[1].strip()
|
|
95
95
|
|
|
96
|
-
if self.
|
|
96
|
+
if self.__mock_serving_message in line:
|
|
97
97
|
if self.port == 0:
|
|
98
|
-
self.port = self._extract_port(line.split(self.
|
|
99
|
-
self.
|
|
98
|
+
self.port = self._extract_port(line.split(self.__mock_serving_message)[0])
|
|
99
|
+
self.__mock_started_event.set()
|
|
100
100
|
|
|
101
101
|
def read_and_print_output_line_by_line():
|
|
102
102
|
for line in iter(self.__process.stdout.readline, ''):
|
|
103
103
|
if line:
|
|
104
104
|
line = line.decode().rstrip()
|
|
105
105
|
print(line)
|
|
106
|
-
|
|
106
|
+
signal_event_if_mock_has_started(line)
|
|
107
107
|
|
|
108
108
|
try:
|
|
109
109
|
read_and_print_output_line_by_line()
|
|
110
110
|
except Exception:
|
|
111
111
|
tb = traceback.format_exc()
|
|
112
112
|
self.__error_queue.put(tb)
|
|
113
|
-
self.
|
|
113
|
+
self.__mock_started_event.set()
|
|
114
114
|
|
|
115
115
|
@staticmethod
|
|
116
116
|
def _extract_port(text: str) -> str:
|
|
@@ -135,11 +135,11 @@ class SpecmaticStub(SpecmaticBase):
|
|
|
135
135
|
if line or self.__process.poll() is not None:
|
|
136
136
|
line = line.decode().rstrip()
|
|
137
137
|
print(line)
|
|
138
|
-
raise Exception('
|
|
138
|
+
raise Exception('Mock process terminated due to an error ' + line)
|
|
139
139
|
except Exception:
|
|
140
140
|
tb = traceback.format_exc()
|
|
141
141
|
self.__error_queue.put(tb)
|
|
142
|
-
self.
|
|
142
|
+
self.__mock_started_event.set()
|
|
143
143
|
|
|
144
|
-
def
|
|
144
|
+
def __create_mock_process_command(self):
|
|
145
145
|
return self.create_command_array('stub')
|
|
@@ -69,9 +69,9 @@ class ASGIAppServer(AppServer):
|
|
|
69
69
|
def port(self):
|
|
70
70
|
return self.app_port
|
|
71
71
|
|
|
72
|
-
def set_app_config(self,
|
|
72
|
+
def set_app_config(self, mock_host: str, mock_port: int):
|
|
73
73
|
if self.set_app_config_func is not None:
|
|
74
|
-
self.set_app_config_func(
|
|
74
|
+
self.set_app_config_func(mock_host, mock_port)
|
|
75
75
|
|
|
76
76
|
def reset_app_config(self):
|
|
77
77
|
if self.reset_app_config_func is not None:
|
|
@@ -34,9 +34,9 @@ class WSGIAppServer(AppServer):
|
|
|
34
34
|
def port(self):
|
|
35
35
|
return self.app_port
|
|
36
36
|
|
|
37
|
-
def set_app_config(self,
|
|
37
|
+
def set_app_config(self, mock_host: str, mock_port: int):
|
|
38
38
|
if self.set_app_config_func is not None:
|
|
39
|
-
self.set_app_config_func(self.app,
|
|
39
|
+
self.set_app_config_func(self.app, mock_host, mock_port)
|
|
40
40
|
|
|
41
41
|
def reset_app_config(self):
|
|
42
42
|
if self.reset_app_config_func is not None:
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: specmatic
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.29.0
|
|
4
4
|
Summary: A Python module for using the Specmatic Library.
|
|
5
5
|
Home-page: https://github.com/specmatic/specmatic-python-extensions
|
|
6
6
|
Author: Specmatic Builders
|
|
7
7
|
Author-email: info@core.in
|
|
8
|
+
Requires-Python: >=3.11
|
|
8
9
|
Description-Content-Type: text/markdown
|
|
9
10
|
Requires-Dist: pytest>=7.3.1
|
|
10
11
|
Requires-Dist: requests>=2.26.0
|
|
@@ -13,6 +14,14 @@ Requires-Dist: uvicorn>=0.18.0
|
|
|
13
14
|
Requires-Dist: fastapi>=0.70.0
|
|
14
15
|
Requires-Dist: flask>=2.2.5
|
|
15
16
|
Requires-Dist: sanic>=22.12.0
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: home-page
|
|
22
|
+
Dynamic: requires-dist
|
|
23
|
+
Dynamic: requires-python
|
|
24
|
+
Dynamic: summary
|
|
16
25
|
|
|
17
26
|
# Specmatic Python
|
|
18
27
|
This is a Python library to run [Specmatic](https://specmatic.io).
|
|
@@ -23,7 +32,7 @@ Specmatic is a contract driven development tool that allows us to turn OpenAPI c
|
|
|
23
32
|
The specmatic python library provides three main functions:
|
|
24
33
|
- The ability to start and stop a python web app like flask/sanic.
|
|
25
34
|
- The ability to run specmatic in test mode against an open api contract/spec.
|
|
26
|
-
- The ability to
|
|
35
|
+
- The ability to mock out an api dependency using the specmatic mock feature.
|
|
27
36
|
|
|
28
37
|
#### Running Contract Tests
|
|
29
38
|
A contract test validates an open api specification against a running api service.
|
|
@@ -46,7 +55,7 @@ The open api specification can be present either locally or in a [Central Contra
|
|
|
46
55
|
|
|
47
56
|
#### How does it work
|
|
48
57
|
- Specmatic uses the TestContract class defined above to inject tests dynamically into it when you run it via PyTest or UnitTest.
|
|
49
|
-
- The Specmatic Python package, invokes the Specmatic executable jar (via command line) in a separate process to start
|
|
58
|
+
- The Specmatic Python package, invokes the Specmatic executable jar (via command line) in a separate process to start mocks and run tests.
|
|
50
59
|
- It is the specmatic jar which runs the contract tests and generates a JUnit test summary report.
|
|
51
60
|
- The Specmatic Python package ingests the JUnit test summary report and generates test methods corresponding to every contract test.
|
|
52
61
|
- These dynamic test methods are added to the ```TestContract``` class and hence we seem them reported seamlessly by PyTest/Unittest like this:
|
|
@@ -60,23 +69,22 @@ test/test_contract_with_coverage.py::TestContract::test_Scenario: GET /products
|
|
|
60
69
|
|
|
61
70
|
## WSGI Apps
|
|
62
71
|
|
|
63
|
-
#### To run contract tests with a
|
|
72
|
+
#### To run contract tests with a mock for a wsgi app (like Flask):
|
|
64
73
|
|
|
65
74
|
``````python
|
|
66
75
|
class TestContract:
|
|
67
76
|
pass
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Specmatic()
|
|
71
|
-
.with_project_root(PROJECT_ROOT)
|
|
72
|
-
.
|
|
73
|
-
.with_wsgi_app(app, app_host, app_port)
|
|
74
|
-
.test(TestContract)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
Specmatic()
|
|
80
|
+
.with_project_root(PROJECT_ROOT)
|
|
81
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
82
|
+
.with_wsgi_app(app, app_host, app_port)
|
|
83
|
+
.test(TestContract)
|
|
75
84
|
.run()
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
|
|
78
86
|
if __name__ == '__main__':
|
|
79
|
-
pytest.main()
|
|
87
|
+
pytest.main()
|
|
80
88
|
``````
|
|
81
89
|
|
|
82
90
|
- In this, we are passing:
|
|
@@ -84,16 +92,16 @@ pytest.main()
|
|
|
84
92
|
- app_host and app_port. If they are not specified, the app will be started on a random available port on 127.0.0.1.
|
|
85
93
|
- You would need a [specmatic config](https://specmatic.io/documentation/specmatic_json.html) file to be present in the root directory of your project.
|
|
86
94
|
- an empty test class.
|
|
87
|
-
-
|
|
88
|
-
The
|
|
89
|
-
If they are not supplied, the
|
|
90
|
-
[Click here](https://specmatic.io/documentation/service_virtualization_tutorial.html) to learn more about
|
|
95
|
+
- mock_host, mock_port, optional list of json files to set expectations on the mock.
|
|
96
|
+
The mock_host, mock_port will be used to run the specmatic mock server.
|
|
97
|
+
If they are not supplied, the mock will be started on a random available port on 127.0.0.1.
|
|
98
|
+
[Click here](https://specmatic.io/documentation/service_virtualization_tutorial.html) to learn more about mocking/service virtualization.
|
|
91
99
|
- You can run this test from either your IDE or command line by pointing pytest to your test folder:
|
|
92
100
|
``````pytest test -v -s``````
|
|
93
101
|
- NOTE: Please ensure that you set the '-v' and '-s' flags while running pytest as otherwise pytest may swallow up the console output.
|
|
94
102
|
|
|
95
103
|
|
|
96
|
-
#### To run contract tests without a
|
|
104
|
+
#### To run contract tests without a mock:
|
|
97
105
|
|
|
98
106
|
``````python
|
|
99
107
|
class TestContract:
|
|
@@ -108,33 +116,35 @@ Specmatic() \
|
|
|
108
116
|
|
|
109
117
|
## ASGI Apps
|
|
110
118
|
|
|
111
|
-
#### To run contract tests with a
|
|
119
|
+
#### To run contract tests with a mock for an asgi app (like sanic):
|
|
112
120
|
- If you are using an asgi app like sanic, fastapi, use the ``````with_asgi_app`````` function and pass it a string in the 'module:app' format.
|
|
121
|
+
|
|
113
122
|
``````python
|
|
114
123
|
class TestContract:
|
|
115
124
|
pass
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Specmatic()
|
|
119
|
-
.with_project_root(PROJECT_ROOT)
|
|
120
|
-
.
|
|
121
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
122
|
-
.test(TestContract)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
Specmatic()
|
|
128
|
+
.with_project_root(PROJECT_ROOT)
|
|
129
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
130
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
131
|
+
.test(TestContract)
|
|
123
132
|
.run()
|
|
124
133
|
``````
|
|
125
134
|
|
|
126
|
-
### Passing extra arguments to
|
|
135
|
+
### Passing extra arguments to mock/test
|
|
127
136
|
- To pass arguments like '--strict', '--testBaseUrl', pass them as a list to the 'args' parameter:
|
|
137
|
+
|
|
128
138
|
``````python
|
|
129
139
|
class TestContract:
|
|
130
140
|
pass
|
|
131
141
|
|
|
132
142
|
|
|
133
|
-
Specmatic()
|
|
134
|
-
.with_project_root(PROJECT_ROOT)
|
|
135
|
-
.
|
|
136
|
-
.with_wsgi_app(app, port=app_port)
|
|
137
|
-
.test(TestContract, args=['--testBaseURL=http://localhost:5000'])
|
|
143
|
+
Specmatic()
|
|
144
|
+
.with_project_root(PROJECT_ROOT)
|
|
145
|
+
.with_mock(mock_host, mock_port, [expectation_json_file], ['--strict'])
|
|
146
|
+
.with_wsgi_app(app, port=app_port)
|
|
147
|
+
.test(TestContract, args=['--testBaseURL=http://localhost:5000'])
|
|
138
148
|
.run()
|
|
139
149
|
``````
|
|
140
150
|
|
|
@@ -148,11 +158,11 @@ class TestContract:
|
|
|
148
158
|
pass
|
|
149
159
|
|
|
150
160
|
|
|
151
|
-
Specmatic()
|
|
152
|
-
.with_project_root(PROJECT_ROOT)
|
|
153
|
-
.
|
|
154
|
-
.with_wsgi_app(app, app_host, app_port)
|
|
155
|
-
.test_with_api_coverage_for_flask_app(TestContract, app)
|
|
161
|
+
Specmatic()
|
|
162
|
+
.with_project_root(PROJECT_ROOT)
|
|
163
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
164
|
+
.with_wsgi_app(app, app_host, app_port)
|
|
165
|
+
.test_with_api_coverage_for_flask_app(TestContract, app)
|
|
156
166
|
.run()
|
|
157
167
|
``````
|
|
158
168
|
|
|
@@ -163,11 +173,11 @@ class TestContract:
|
|
|
163
173
|
pass
|
|
164
174
|
|
|
165
175
|
|
|
166
|
-
Specmatic()
|
|
167
|
-
.with_project_root(PROJECT_ROOT)
|
|
168
|
-
.
|
|
169
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
170
|
-
.test_with_api_coverage_for_sanic_app(TestContract, app)
|
|
176
|
+
Specmatic()
|
|
177
|
+
.with_project_root(PROJECT_ROOT)
|
|
178
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
179
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
180
|
+
.test_with_api_coverage_for_sanic_app(TestContract, app)
|
|
171
181
|
.run()
|
|
172
182
|
``````
|
|
173
183
|
|
|
@@ -178,11 +188,11 @@ class TestContract:
|
|
|
178
188
|
pass
|
|
179
189
|
|
|
180
190
|
|
|
181
|
-
Specmatic()
|
|
182
|
-
.with_project_root(PROJECT_ROOT)
|
|
183
|
-
.
|
|
184
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
185
|
-
.test_with_api_coverage_for_fastapi_app(TestContract, app)
|
|
191
|
+
Specmatic()
|
|
192
|
+
.with_project_root(PROJECT_ROOT)
|
|
193
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
194
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
195
|
+
.test_with_api_coverage_for_fastapi_app(TestContract, app)
|
|
186
196
|
.run()
|
|
187
197
|
``````
|
|
188
198
|
|
|
@@ -193,14 +203,14 @@ The ``````CoverageRoute`````` class has two properties:
|
|
|
193
203
|
``````url`````` : This represents your route url in this format: `````` /orders/{order_id}``````
|
|
194
204
|
``````method`````` : A list of HTTP methods supported on the route, for instance : ``````['GET', 'POST']``````
|
|
195
205
|
|
|
196
|
-
You can then enable coverage by passing your adapter like this:
|
|
206
|
+
You can then enable coverage by passing your adapter like this:
|
|
197
207
|
|
|
198
208
|
``````python
|
|
199
|
-
Specmatic()
|
|
200
|
-
.with_project_root(PROJECT_ROOT)
|
|
201
|
-
.
|
|
202
|
-
.with_asgi_app('main:app', app_host, app_port)
|
|
203
|
-
.test_with_api_coverage(TestContract, MyAppRouteAdapter(app))
|
|
209
|
+
Specmatic()
|
|
210
|
+
.with_project_root(PROJECT_ROOT)
|
|
211
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
212
|
+
.with_asgi_app('main:app', app_host, app_port)
|
|
213
|
+
.test_with_api_coverage(TestContract, MyAppRouteAdapter(app))
|
|
204
214
|
.run()
|
|
205
215
|
``````
|
|
206
216
|
|
|
@@ -215,7 +225,8 @@ You can also easily implement your own coverage server if you have written a cus
|
|
|
215
225
|
The only point to remember in mind is that the EndPointsApi url should return a list of routes in the format used buy Spring Actuator's ```````/actuator/mappings``````` endpoint
|
|
216
226
|
as described [here](https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/#mappings).
|
|
217
227
|
|
|
218
|
-
Here's an example where we start both our FastApi app and coverage server outside the specmatic api call.
|
|
228
|
+
Here's an example where we start both our FastApi app and coverage server outside the specmatic api call.
|
|
229
|
+
|
|
219
230
|
``````python
|
|
220
231
|
app_server = ASGIAppServer('test.apps.fast_api:app', app_host, app_port)
|
|
221
232
|
coverage_server = FastApiAppCoverageServer(app)
|
|
@@ -228,11 +239,11 @@ class TestContract:
|
|
|
228
239
|
pass
|
|
229
240
|
|
|
230
241
|
|
|
231
|
-
Specmatic()
|
|
232
|
-
.with_project_root(PROJECT_ROOT)
|
|
233
|
-
.
|
|
234
|
-
.with_endpoints_api(coverage_server.endpoints_api)
|
|
235
|
-
.test(TestContract, app_host, app_port)
|
|
242
|
+
Specmatic()
|
|
243
|
+
.with_project_root(PROJECT_ROOT)
|
|
244
|
+
.with_mock(mock_host, mock_port, [expectation_json_file])
|
|
245
|
+
.with_endpoints_api(coverage_server.endpoints_api)
|
|
246
|
+
.test(TestContract, app_host, app_port)
|
|
236
247
|
.run()
|
|
237
248
|
|
|
238
249
|
app_server.stop()
|
|
@@ -253,7 +264,7 @@ coverage_server.stop()
|
|
|
253
264
|
## Sample Projects
|
|
254
265
|
- [Check out the Specmatic Order BFF Python repo](https://github.com/specmatic/specmatic-order-bff-python/) to see more examples of how to use specmatic with a Flask app.
|
|
255
266
|
- [Check out the Specmatic Order BFF Python Sanic repo](https://github.com/specmatic/specmatic-order-bff-python-sanic/) to see more examples of how to use specmatic with a Sanic app.
|
|
256
|
-
- [Check out the Specmatic Order API Python repo](https://github.com/specmatic/specmatic-order-api-python/) to see an examples of how to just run tests without using a
|
|
267
|
+
- [Check out the Specmatic Order API Python repo](https://github.com/specmatic/specmatic-order-api-python/) to see an examples of how to just run tests without using a mock.
|
|
257
268
|
|
|
258
269
|
|
|
259
270
|
|
|
@@ -15,7 +15,7 @@ specmatic/core/decorators.py
|
|
|
15
15
|
specmatic/core/specmatic.jar
|
|
16
16
|
specmatic/core/specmatic.py
|
|
17
17
|
specmatic/core/specmatic_base.py
|
|
18
|
-
specmatic/core/
|
|
18
|
+
specmatic/core/specmatic_mock.py
|
|
19
19
|
specmatic/core/specmatic_test.py
|
|
20
20
|
specmatic/coverage/__init__.py
|
|
21
21
|
specmatic/coverage/app_route_adapter.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/servers/fastapi_app_coverage_server.py
RENAMED
|
File without changes
|
{specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/servers/flask_app_coverage_server.py
RENAMED
|
File without changes
|
{specmatic-2.24.0 → specmatic-2.29.0}/specmatic/coverage/servers/sanic_app_coverage_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|