Skip to content

Commit 254ffc5

Browse files
Merge pull request #130 from elliottmurray/examples
Examples for python
2 parents b859443 + 3898aee commit 254ffc5

16 files changed

+411
-38
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ script:
2020
- flake8
2121
- pydocstyle pact
2222
- tox
23-
- if [[ $TRAVIS_PYTHON_VERSION == "2.7" ]]; then make package && pip install ./dist/pact-python-*.tar.gz && make e2e; fi
23+
- if [[ $TRAVIS_PYTHON_VERSION == "3.6" ]]; then make package && pip install ./dist/pact-python-*.tar.gz && make e2e; fi
2424

2525
before_deploy:
2626
- export RELEASE_PACKAGE=$(ls dist/pact-python-*.tar.gz)

Makefile

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,42 +29,10 @@ deps:
2929

3030

3131
define E2E
32-
set -ex
33-
cd e2e
34-
nosetests ./contracts
35-
python app.py &
36-
APP_PID=$$!
37-
function teardown {
38-
echo 'Tearing down Flask server'
39-
kill $$APP_PID
40-
}
41-
trap teardown EXIT
42-
while ! nc -z localhost 5000; do
43-
sleep 0.1
44-
done
45-
46-
set +e
47-
pact-verifier \
48-
--provider-base-url=http://localhost:5000 \
49-
--provider-states-url=http://localhost:5000/_pact/provider-states \
50-
--provider-states-setup-url=http://localhost:5000/_pact/provider-states/active \
51-
./pacts/failing-consumer-provider.json
52-
EXIT_CODE=$$?
53-
set -e
54-
55-
if [ $$EXIT_CODE -eq 1 ]; then
56-
echo "Failing verification exited with 1 as expected"
57-
else
58-
echo "Failing verification did not exit with 1 as expected"
59-
exit 1
60-
fi
61-
62-
pact-verifier \
63-
--provider-base-url=http://localhost:5000 \
64-
--provider-states-url=http://localhost:5000/_pact/provider-states \
65-
--provider-states-setup-url=http://localhost:5000/_pact/provider-states/active \
66-
./pacts/consumer-provider.json
67-
32+
cd examples/e2e
33+
pip install -r requirements.txt
34+
pytest tests/test_user_consumer.py
35+
./verify_pact.sh
6836
endef
6937

7038

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ test your code more efficiently, check out the [Pact documentation].
1919
pip install pact-python
2020
```
2121

22+
## Getting started
23+
24+
A guide follows but if you go to the [e2e examples](examples/e2e/README.md). This has a consumer, provider and pact-broker set of tests.
25+
2226
## Writing a Pact
27+
2328
Creating a complete contract is a two step process:
2429

2530
1. Create a test on the consumer side that declares the expectations it has of the provider
@@ -236,7 +241,7 @@ EachLike({
236241
> Note, you do not need to specify everything that will be returned from the Provider in a
237242
> JSON response, any extra data that is received will be ignored and the tests will still pass.
238243
239-
> Note, to get the generated values from an object that can contain matchers like Term, Like, EachLike, etc.
244+
> Note, to get the generated values from an object that can contain matchers like Term, Like, EachLike, etc.
240245
> for assertion in self.assertEqual(result, expected) you may need to use get_generated_values() helper function:
241246
242247
```python

examples/e2e/.vscode/settings.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"python.testing.pytestArgs": [
3+
"tests"
4+
],
5+
"python.testing.unittestEnabled": false,
6+
"python.testing.nosetestsEnabled": false,
7+
"python.testing.pytestEnabled": true
8+
}

examples/e2e/README.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Introduction
2+
3+
This is an e2e example using flask to help as a guide to getting started with Pact for Python. For the provider it imports the app into a wrapper (pact_provider.py) so as to decorate extra functions we can use to test our app.
4+
5+
## Setup
6+
7+
Create your own virtualenv for this. Run
8+
9+
```bash
10+
pip install -r requirements.txt
11+
pip install pact-python
12+
```
13+
14+
TODO: Make this so you can point to the parent install to help with development.
15+
16+
Create the local broker (for demo purposes only) To do this separately clone this repo:
17+
* https://github.com/pact-foundation/pact-broker-docker
18+
19+
Then from where this is install run in it's own terminal
20+
21+
```bash
22+
docker-compose up
23+
```
24+
25+
If you can open a browser to http://localhost and see the broker you have succeeded.
26+
27+
## Consumer
28+
29+
From the root directory run:
30+
31+
```bash
32+
pytest
33+
```
34+
35+
Or you can run individual tests like:
36+
37+
```bash
38+
pytest tests/test_user_consumer.py::test_get_non_existing_user
39+
```
40+
41+
If you want to publish this to the pact broker add the '--publish-pact' option like:
42+
43+
```bash
44+
pytest --publish-pact=XX
45+
```
46+
47+
XX is the version number of the pact and is for you to manage in your deployment process.
48+
49+
Sometimes you may get the mock server in a hung state. You can kill it via (untested):
50+
51+
```bash
52+
pkill -f pact-mock-service.rb
53+
```
54+
55+
## Provider States
56+
57+
Run the script (placeholder version number for pact broker)
58+
59+
```bash
60+
./verify_pact.sh 1
61+
```
62+
63+
This will import the provider.py file which is the actual app and then decorate it with extra urls. It then puts this into the background and runs the pact-verifier tool against it.
64+
65+
To test what this looks like when failing change one of these values.
66+
67+
```python
68+
def setup_user_a_nonadmin():
69+
70+
id = '00000000-0000-4000-a000-000000000000'
71+
some_date = '2016-12-15T20:16:01'
72+
73+
```
74+
75+
### Provider debugging
76+
77+
To manually trigger one of the 2 manual states you can run:
78+
79+
```bash
80+
curl -X POST -H "Content-Type: application/json" --data "{\"state\": \"UserA exists and is not an administrator\"}" http://127.0.0.1:5000/_pact/provider_states
81+
```
82+
83+
Changing the json content to match the state you want.

examples/e2e/__init__.py

Whitespace-only changes.

examples/e2e/pact_provider.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from flask import jsonify, request
2+
3+
from src.provider import app, fakedb
4+
5+
6+
@app.route('/_pact/provider_states', methods=['POST'])
7+
def provider_states():
8+
mapping = {'UserA does not exist': setup_no_user_a,
9+
'UserA exists and is not an administrator':
10+
setup_user_a_nonadmin}
11+
mapping[request.json['state']]()
12+
return jsonify({'result': request.json['state']})
13+
14+
15+
def setup_no_user_a():
16+
if 'UserA' in fakedb:
17+
del fakedb['UserA']
18+
19+
20+
def setup_user_a_nonadmin():
21+
id = '00000000-0000-4000-a000-000000000000'
22+
some_date = '2016-12-15T20:16:01'
23+
24+
fakedb['UserA'] = {
25+
'name': "UserA",
26+
'id': id,
27+
'created_on': some_date,
28+
'admin': False
29+
}
30+
31+
32+
if __name__ == '__main__':
33+
app.run(debug=True, port=5001)

examples/e2e/requirements.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
attrs==19.3.0
2+
certifi==2019.11.28
3+
chardet==3.0.4
4+
click==7.1.1
5+
Flask==1.1.1
6+
idna==2.9
7+
importlib-metadata==1.6.0
8+
itsdangerous==1.1.0
9+
Jinja2==2.11.1
10+
MarkupSafe==1.1.1
11+
more-itertools==8.2.0
12+
packaging==20.3
13+
pluggy==0.13.1
14+
psutil==5.7.0
15+
py==1.8.1
16+
pyparsing==2.4.6
17+
pytest==5.4.1
18+
requests==2.23.0
19+
six==1.14.0
20+
urllib3==1.25.8
21+
wcwidth==0.1.9
22+
Werkzeug==1.0.1
23+
zipp==3.1.0

examples/e2e/src/__init__.py

Whitespace-only changes.

examples/e2e/src/consumer.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import requests
2+
3+
4+
class UserConsumer(object):
5+
def __init__(self, base_uri):
6+
self.base_uri = base_uri
7+
8+
def get_user(self, user_name):
9+
"""Fetch a user object by user_name from the server."""
10+
uri = self.base_uri + '/users/' + user_name
11+
response = requests.get(uri)
12+
if response.status_code == 404:
13+
return None
14+
15+
name = response.json()['name']
16+
return User(name)
17+
18+
19+
class User(object):
20+
def __init__(self, name):
21+
self.name = name

0 commit comments

Comments
 (0)