Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(examples): tidy build and flask/fastapi examples, improve consistency #271

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .flake8
Expand Up @@ -2,3 +2,4 @@
ignore = E226,E302,E41,W503
max-line-length = 160
max-complexity = 10
exclude = .git,venv,.tox
2 changes: 1 addition & 1 deletion .github/workflows/build_and_test.yml
Expand Up @@ -29,4 +29,4 @@ jobs:
tox -e test
- name: Test examples
run: |
make e2e
make examples
72 changes: 53 additions & 19 deletions Makefile
Expand Up @@ -5,9 +5,12 @@ help:
@echo ""
@echo " clean to clear build and distribution directories"
@echo " deps to install the required files for development"
@echo " e2e to run the end to end tests"
@echo " verifier to run the verifier end to end tests"
@echo " examples to run the example end to end tests"
@echo " examples to run the example end to end tests (consumer, fastapi, flask, messaging)"
@echo " consumer to run the example consumer tests"
@echo " fastapi to run the example FastApi provider tests"
@echo " flask to run the example Flask provider tests"
@echo " messaging to run the example messaging e2e tests"
@echo " package to create a distribution package in /dist/"
@echo " release to perform a release build, including deps, test, and package targets"
@echo " test to run all tests"
Expand All @@ -30,38 +33,69 @@ deps:
pip install -r requirements_dev.txt -e .


define E2E
echo "e2e make"
cd examples/e2e
pip install -r requirements.txt
pip install -e ../../
./run_pytest.sh
define CONSUMER
echo "consumer make"
cd examples/consumer
pip install -q -r requirements.txt
pip install -e ../../
./run_pytest.sh
endef
export E2E
export CONSUMER


define messaging
define FLASK_PROVIDER
echo "flask make"
cd examples/flask_provider
pip install -q -r requirements.txt
pip install -e ../../
./run_pytest.sh
endef
export FLASK_PROVIDER


define FASTAPI_PROVIDER
echo "fastapi make"
cd examples/fastapi_provider
pip install -q -r requirements.txt
pip install -e ../../
./run_pytest.sh
endef
export FASTAPI_PROVIDER


define MESSAGING
echo "messaging make"
cd examples/message
pip install -r requirements.txt
pip install -e ../../
./run_pytest.sh
pip install -q -r requirements.txt
pip install -e ../../
./run_pytest.sh
endef
export messaging
export MESSAGING


.PHONY: consumer
consumer:
bash -c "$$CONSUMER"


.PHONY: flask
flask:
bash -c "$$FLASK_PROVIDER"


.PHONY: e2e
e2e:
bash -c "$$E2E"
.PHONY: fastapi
fastapi:
bash -c "$$FASTAPI_PROVIDER"


.PHONY: messaging
messaging:
bash -c "$$messaging"
bash -c "$$MESSAGING"


.PHONY: examples
examples: e2e messaging
examples: consumer flask messaging
# TODO: Fix fastapi, to run all examples this should be: consumer flask fastapi messaging


.PHONY: package
Expand Down
156 changes: 156 additions & 0 deletions examples/README.md
@@ -0,0 +1,156 @@
# Examples

## Table of Contents

* [Overview](#overview)
* [broker](#broker)
* [consumer](#consumer)
* [flask_provider](#flask_provider)
* [fastapi_provider](#fastapi_provider)
* [message](#message)
* [pacts](#pacts)

## Overview

Here you can find examples of how to use Pact using the python language. You can find more of an overview on Pact in the
[Pact Introduction].

Examples are given of both the [Consumer] and [Provider], this does not mean however that you must use python for both.
Different languages can be mixed and matched as required.

In these examples, `1` is just used to meet the need of having *some* [Consumer] or [Provider] version. In reality, you
will generally want to use something more complicated and automated. Guidelines and best practices are available in the
[Versioning in the Pact Broker]

## broker

The [Pact Broker] stores [Pact file]s and [Pact verification] results. It is used here for the [consumer](#consumer),
[flask_provider](#flask-provider) and [message](#message) tests.

### Running

These examples run the [Pact Broker] as part of the tests when specified. It can be run outside the tests as well by
performing the following command from a separate terminal in the `examples/broker` folder:
```bash
docker-compose up
```

You should then be able to open a browser and navigate to http://localhost where you will initially be able to see the
default Example App/Example API Pact.

Running the [Pact Broker] outside the tests will mean you are able to then see the [Pact file]s submitted to the
[Pact Broker] as the various tests are performed.

## consumer

Pact is consumer-driven, which means first the contracts are created. These Pact contracts are generated during
execution of the consumer tests.

### Running

When the tests are run, the "minimum" is to generate the Pact contract JSON, additional options are available. The
following commands can be run from the `examples/consumer` folder:

- To startup the broker, run the tests, and publish the results to the broker:
```bash
pytest --run-broker True --publish-pact 1
```
- Alternatively the same can be performed with the following command, which is called from a `make consumer`:
```bash
./run_pytest.sh
```
- To run the tests, and publish the results to the broker which is already running:
```bash
pytest --publish-pact 1
```
- To just run the tests:
```bash
pytest
```

### Output

The following file(s) will be created when the tests are run:

| Filename | Contents |
|---------------------------------------------| ----------|
| consumer/pact-mock-service.log | All interactions with the mock provider such as expected interactions, requests, and interaction verifications. |
| consumer/userserviceclient-userservice.json | This contains the Pact interactions between the `UserServiceClient` and `UserService`, as defined in the tests. The naming being derived from the named Pacticipants: `Consumer("UserServiceClient")` and `Provider("UserService")` |

## flask_provider

The Flask [Provider] example consists of a basic Flask app, with a single endpoint route.
This implements the service expected by the [consumer](#consumer).

The [Provider] side is responsible for performing the tests to verify if it is compliant with the [Pact file] contracts
associated with it.

As such, the tests use the pact-python Verifier to perform this verification. Two approaches are demonstrated:
- Testing against the [Pact broker]. Generally this is the preferred approach, see information on [Sharing Pacts].
- Testing against the [Pact file] directly. If no [Pact broker] is available you can verify against a static [Pact file].

### Running

To avoid package version conflicts with different applications, it is recommended to run these tests from a
[Virtual Environment]

The following commands can be run from within your [Virtual Environment], in the `examples/flask_provider`.

To perform the python tests:
```bash
pip install -r requirements.txt # Install the dependencies for the Flask example
pip install -e ../../ # Using setup.py in the pact-python root, install any pact dependencies and pact-python
./run_pytest.sh # Wrapper script to first run Flask, and then run the tests
```

To perform verification using CLI to verify the [Pact file] against the Flask [Provider] instead of the python tests:
```bash
pip install -r requirements.txt # Install the dependencies for the Flask example
./verify_pact.sh # Wrapper script to first run Flask, and then use `pact-verifier` to verify locally
```

To perform verification using CLI, but verifying the [Pact file] previously provided by a [Consumer], and publish the
results. This example requires that the [Pact broker] is already running, and the [Consumer] tests have been published
already, described in the [consumer](#consumer) section above.
```bash
pip install -r requirements.txt # Install the dependencies for the Flask example
./verify_pact.sh 1 # Wrapper script to first run Flask, and then use `pact-verifier` to verify and publish
```

These examples demonstrate by first launching Flask via a `python -m flask run`, you may prefer to start Flask using an
`app.run()` call in the python code instead, see [How to Run a Flask Application]. Additionally for tests, you may want
to manage starting and stopping Flask as part of a fixture setup. Any approach can be chosen here, in line with your
existing Flask testing practices.

### Output

The following file(s) will be created when the tests are run

| Filename | Contents |
|-----------------------------| ----------|
| flask_provider/log/pact.log | All Pact interactions with the Flask Provider. Every interaction example retrieved from the Pact Broker will be performed during the Verification test; the request/response logged here. |

## fastapi_provider

TODO

## message

TODO

## pacts

Both the Flask and the FastAPI [Provider] examples implement the same service the [Consumer] example interacts with.
This folder contains the generated [Pact file] for reference, which is also used when running the [Provider] tests
without a [Pact Broker].

[Pact Broker]: https://docs.pact.io/pact_broker
[Pact Introduction]: https://docs.pact.io/
[Consumer]: https://docs.pact.io/getting_started/terminology#service-consumer
[Provider]: https://docs.pact.io/getting_started/terminology#service-provider
[Versioning in the Pact Broker]: https://docs.pact.io/getting_started/versioning_in_the_pact_broker/
[Pact file]: https://docs.pact.io/getting_started/terminology#pact-file
[Pact verification]: https://docs.pact.io/getting_started/terminology#pact-verification]
[Virtual Environment]: https://docs.python.org/3/tutorial/venv.html
[Sharing Pacts]: https://docs.pact.io/getting_started/sharing_pacts/]
[How to Run a Flask Application]: https://www.twilio.com/blog/how-run-flask-application
14 changes: 11 additions & 3 deletions examples/broker/docker-compose.yml
@@ -1,7 +1,7 @@
version: '3'

services:

# A PostgreSQL database for the Broker to store Pacts and verification results
postgres:
image: postgres
healthcheck:
Expand All @@ -13,10 +13,16 @@ services:
POSTGRES_PASSWORD: password
POSTGRES_DB: postgres

# The Pact Broker
broker_app:
image: dius/pact-broker
# Alternatively the DiUS Pact Broker can be used:
# image: dius/pact-broker
#
# As well as changing the image, the destination port will need to be changed
# from 9292 below, and in the nginx.conf proxy_pass section
image: pactfoundation/pact-broker
ports:
- "80:80"
- "80:9292"
links:
- postgres
environment:
Expand All @@ -27,6 +33,8 @@ services:
PACT_BROKER_BASIC_AUTH_USERNAME: pactbroker
PACT_BROKER_BASIC_AUTH_PASSWORD: pactbroker

# An NGINX reverse proxy in front of the Broker on port 8443, to be able to
# terminate with SSL
nginx:
image: nginx:alpine
links:
Expand Down
38 changes: 20 additions & 18 deletions examples/broker/ssl/nginx.conf
@@ -1,21 +1,23 @@
server {
listen 443 ssl default_server;
server_name localhost;
ssl_certificate /etc/nginx/ssl/nginx-selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/nginx-selfsigned.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_stapling on;
ssl_stapling_verify on;
listen 443 ssl default_server;
server_name localhost;
ssl_certificate /etc/nginx/ssl/nginx-selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/nginx-selfsigned.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_stapling on;
ssl_stapling_verify on;

location / {
proxy_pass http://broker:80;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme "https";
proxy_set_header X-Forwarded-Port "443";
proxy_set_header X-Forwarded-Ssl "on";
proxy_set_header X-Real-IP $remote_addr;
}
location / {
# To use with the Dius Pact Broker:
# proxy_pass http://broker:80;
proxy_pass http://broker:9292;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme "https";
proxy_set_header X-Forwarded-Port "443";
proxy_set_header X-Forwarded-Ssl "on";
proxy_set_header X-Real-IP $remote_addr;
}
}
3 changes: 3 additions & 0 deletions examples/consumer/requirements.txt
@@ -0,0 +1,3 @@
pytest==5.4.1
requests>=2.26.0
testcontainers==3.3.0
4 changes: 4 additions & 0 deletions examples/consumer/run_pytest.sh
@@ -0,0 +1,4 @@
#!/bin/bash
set -o pipefail

pytest tests --run-broker True --publish-pact 1