From 6e7db6cb42a9f92377ec31d919b78b704a50fca8 Mon Sep 17 00:00:00 2001 From: Mike Geeves Date: Sat, 30 Oct 2021 12:01:57 +0100 Subject: [PATCH 1/4] test(examples-fastapi): tidy FastAPI example, making consistent with Flask re #270 --- Makefile | 3 +- examples/README.md | 53 +++++++++- .../pythonclient-pythonservice.json | 81 ---------------- examples/fastapi_provider/run_pytest.sh | 15 +-- examples/fastapi_provider/src/provider.py | 13 ++- examples/fastapi_provider/tests/__init__.py | 0 examples/fastapi_provider/tests/conftest.py | 97 ++++++++++++++++++- .../tests/fastapi_provider.py | 50 ---------- .../fastapi_provider/tests/pact_provider.py | 46 +++++++++ .../tests/provider/__init__.py | 0 .../tests/provider/test_provider.py | 83 ++++++++++++---- examples/fastapi_provider/verify_pact.sh | 30 +++--- examples/flask_provider/src/provider.py | 2 +- 13 files changed, 288 insertions(+), 185 deletions(-) delete mode 100644 examples/fastapi_provider/pythonclient-pythonservice.json create mode 100644 examples/fastapi_provider/tests/__init__.py delete mode 100644 examples/fastapi_provider/tests/fastapi_provider.py create mode 100644 examples/fastapi_provider/tests/pact_provider.py create mode 100644 examples/fastapi_provider/tests/provider/__init__.py diff --git a/Makefile b/Makefile index a1e4a2528..2d4245f54 100644 --- a/Makefile +++ b/Makefile @@ -94,8 +94,7 @@ messaging: .PHONY: examples -examples: consumer flask messaging -# TODO: Fix fastapi, to run all examples this should be: consumer flask fastapi messaging +examples: consumer flask fastapi messaging .PHONY: package diff --git a/examples/README.md b/examples/README.md index 6b1672e1e..e7abdbb8e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -82,6 +82,9 @@ The following file(s) will be created when the tests are run: The Flask [Provider] example consists of a basic Flask app, with a single endpoint route. This implements the service expected by the [consumer](#consumer). +Functionally, this provides the same service and tests as the [fastapi_provider](#fastapi_provider). Both are included to +demonstrate how Pact can be used in different environments with different technology stacks and approaches. + The [Provider] side is responsible for performing the tests to verify if it is compliant with the [Pact file] contracts associated with it. @@ -132,7 +135,55 @@ The following file(s) will be created when the tests are run ## fastapi_provider -TODO +The FastAPI [Provider] example consists of a basic FastAPI app, with a single endpoint route. +This implements the service expected by the [consumer](#consumer). + +Functionally, this provides the same service and tests as the [flask_provider](#flask_provider). Both are included to +demonstrate how Pact can be used in different environments with different technology stacks and approaches. + +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/fastapi_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 FastAPI, and then run the tests +``` + +To perform verification using CLI to verify the [Pact file] against the FastAPI [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 FastAPI, 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 +``` + +### Output + +The following file(s) will be created when the tests are run + +| Filename | Contents | +|-------------------------------| ----------| +| fastapi_provider/log/pact.log | All Pact interactions with the FastAPI Provider. Every interaction example retrieved from the Pact Broker will be performed during the Verification test; the request/response logged here. | + ## message diff --git a/examples/fastapi_provider/pythonclient-pythonservice.json b/examples/fastapi_provider/pythonclient-pythonservice.json deleted file mode 100644 index 25bf24f9e..000000000 --- a/examples/fastapi_provider/pythonclient-pythonservice.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "consumer": { - "name": "PythonClient" - }, - "provider": { - "name": "PythonService" - }, - "interactions": [ - { - "description": "a request for UserA", - "providerState": "UserA exists and is not an administrator", - "request": { - "method": "get", - "path": "/users/UserA" - }, - "response": { - "status": 200, - "headers": { - }, - "body": { - "json_class": "Pact::SomethingLike", - "contents": { - "name": "UserA", - "id": { - "json_class": "Pact::Term", - "data": { - "generate": "fc763eba-0905-41c5-a27f-3934ab26786c", - "matcher": { - "json_class": "Regexp", - "o": 0, - "s": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" - } - } - }, - "created_on": { - "json_class": "Pact::Term", - "data": { - "generate": "2016-12-15T20:16:01", - "matcher": { - "json_class": "Regexp", - "o": 0, - "s": "\\d+-\\d+-\\d+T\\d+:\\d+:\\d+" - } - } - }, - "ip_address": { - "json_class": "Pact::Term", - "data": { - "generate": "127.0.0.1", - "matcher": { - "json_class": "Regexp", - "o": 0, - "s": "(\\d{1,3}\\.)+\\d{1,3}" - } - } - }, - "admin": false - } - } - } - }, - { - "description": "a request for UserA", - "providerState": "UserA does not exist", - "request": { - "method": "get", - "path": "/users/UserA" - }, - "response": { - "status": 404, - "headers": { - } - } - } - ], - "metadata": { - "pactSpecification": { - "version": "1.0.0" - } - } -} \ No newline at end of file diff --git a/examples/fastapi_provider/run_pytest.sh b/examples/fastapi_provider/run_pytest.sh index c93b90551..ff579b469 100755 --- a/examples/fastapi_provider/run_pytest.sh +++ b/examples/fastapi_provider/run_pytest.sh @@ -1,15 +1,6 @@ #!/bin/bash set -o pipefail -uvicorn src.provider:app --host 0.0.0.0 --port 8080 & &>/dev/null -FASTAPI_PID=$! - -function teardown { - echo "Tearing down FastAPI server: ${FASTAPI_PID}" - kill -9 $FLASK_PID -} -trap teardown EXIT - -sleep 1 - -pytest \ No newline at end of file +# Unlike in the Flask example, here the FastAPI service is started up as a pytest fixture. This is then including the +# main and pact routes via fastapi_provider.py to run the tests against +pytest --run-broker True --publish-pact 1 \ No newline at end of file diff --git a/examples/fastapi_provider/src/provider.py b/examples/fastapi_provider/src/provider.py index 9703c0b14..206a87da8 100644 --- a/examples/fastapi_provider/src/provider.py +++ b/examples/fastapi_provider/src/provider.py @@ -1,14 +1,25 @@ +import logging + from fastapi import FastAPI, HTTPException, APIRouter +from fastapi.logger import logger fakedb = {} # Use a simple dict to represent a database + +logger.setLevel(logging.DEBUG) router = APIRouter() app = FastAPI() @app.get("/users/{name}") def get_user_by_name(name: str): + """Handle requests to retrieve a single user from the simulated database. + + :param name: Name of the user to "search for" + :return: The user data if found, HTTP 404 if not + """ user_data = fakedb.get(name) if not user_data: + logger.error(f"GET user for: '{name}', HTTP 404 not found") raise HTTPException(status_code=404, detail="User not found") - + logger.error(f"GET user for: '{name}', returning: {user_data}") return user_data diff --git a/examples/fastapi_provider/tests/__init__.py b/examples/fastapi_provider/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/fastapi_provider/tests/conftest.py b/examples/fastapi_provider/tests/conftest.py index 1d23983d0..20a998161 100644 --- a/examples/fastapi_provider/tests/conftest.py +++ b/examples/fastapi_provider/tests/conftest.py @@ -1,11 +1,98 @@ +import pathlib +from multiprocessing import Process + +import docker +import pytest +from testcontainers.compose import DockerCompose + +from .pact_provider import run_server + + +@pytest.fixture(scope="module") +def server(): + proc = Process(target=run_server, args=(), daemon=True) + proc.start() + yield proc + proc.kill() # Cleanup after test + def pytest_addoption(parser): parser.addoption( - "--publish-pact", type=str, action="store", - help="Upload generated pact file to pact broker with version" + "--publish-pact", type=str, action="store", help="Upload generated pact file to pact broker with version" ) - parser.addoption( - "--provider-url", type=str, action="store", - help="The url to our provider." + parser.addoption("--run-broker", type=bool, action="store", help="Whether to run broker in this test or not.") + + +@pytest.fixture(scope="session", autouse=True) +def publish_existing_pact(broker): + """Publish the contents of the pacts folder to the Pact Broker. + + In normal usage, a Consumer would publish Pacts to the Pact Broker after + running tests - this fixture would NOT be needed. + . + Because the broker is being used standalone here, it will not contain the + required Pacts, so we must first spin up the pact-cli and publish them. + + In the Pact Broker logs, this corresponds to the following entry: + PactBroker::Pacts::Service -- Creating new pact publication with params \ + {:consumer_name=>"UserServiceClient", :provider_name=>"UserService", \ + :revision_number=>nil, :consumer_version_number=>"1", :pact_version_sha=>nil, \ + :consumer_name_in_pact=>"UserServiceClient", :provider_name_in_pact=>"UserService"} + """ + source = str(pathlib.Path.cwd().joinpath("..", "pacts").resolve()) + pacts = [f"{source}:/pacts"] + envs = { + "PACT_BROKER_BASE_URL": "http://broker_app:9292", + "PACT_BROKER_USERNAME": "pactbroker", + "PACT_BROKER_PASSWORD": "pactbroker", + } + + client = docker.from_env() + + print("Publishing existing Pact") + client.containers.run( + remove=True, + network="broker_default", + volumes=pacts, + image="pactfoundation/pact-cli:latest", + environment=envs, + command="publish /pacts --consumer-app-version 1", ) + print("Finished publishing") + + +# This fixture is to simulate a managed Pact Broker or Pactflow account. +# For almost all purposes outside this example, you will want to use a real +# broker. See https://github.com/pact-foundation/pact_broker for further details. +@pytest.fixture(scope="session", autouse=True) +def broker(request): + version = request.config.getoption("--publish-pact") + publish = True if version else False + + # If the results are not going to be published to the broker, there is + # nothing further to do anyway + if not publish: + yield + return + + run_broker = request.config.getoption("--run-broker") + + if run_broker: + # Start up the broker using docker-compose + print("Starting broker") + with DockerCompose("../broker", compose_file_name=["docker-compose.yml"], pull=True) as compose: + stdout, stderr = compose.get_logs() + if stderr: + print("Errors\\n:{}".format(stderr)) + print("{}".format(stdout)) + print("Started broker") + + yield + print("Stopping broker") + print("Broker stopped") + else: + # Assuming there is a broker available already, docker-compose has been + # used manually as the --run-broker option has not been provided + yield + return diff --git a/examples/fastapi_provider/tests/fastapi_provider.py b/examples/fastapi_provider/tests/fastapi_provider.py deleted file mode 100644 index baae0b25a..000000000 --- a/examples/fastapi_provider/tests/fastapi_provider.py +++ /dev/null @@ -1,50 +0,0 @@ -import uvicorn - -from fastapi import APIRouter -from pydantic import BaseModel - -from ..src.provider import app, fakedb, router as main_router - -pact_router = APIRouter() - - -class ProviderState(BaseModel): - state: str # noqa: E999 - - -@pact_router.post("/_pact/provider_states") -async def provider_states(provider_state: ProviderState): - mapping = { - 'UserA does not exist': setup_no_user_a, - 'UserA exists and is not an administrator': - setup_user_a_nonadmin - } - mapping[provider_state.state]() - - return {'result': mapping[provider_state.state]} - - -def run_server(): - app.include_router(main_router) - app.include_router(pact_router) - - uvicorn.run(app) - - -def setup_no_user_a(): - if 'UserA' in fakedb: - del fakedb['UserA'] - - -def setup_user_a_nonadmin(): - id = '00000000-0000-4000-a000-000000000000' - some_date = '2016-12-15T20:16:01' - ip_address = '198.0.0.1' - - fakedb['UserA'] = { - 'name': "UserA", - 'id': id, - 'created_on': some_date, - 'ip_address': ip_address, - 'admin': False - } diff --git a/examples/fastapi_provider/tests/pact_provider.py b/examples/fastapi_provider/tests/pact_provider.py new file mode 100644 index 000000000..d778cd433 --- /dev/null +++ b/examples/fastapi_provider/tests/pact_provider.py @@ -0,0 +1,46 @@ +import uvicorn + +from fastapi import APIRouter +from pydantic import BaseModel + +from src.provider import app, fakedb, router as main_router + +pact_router = APIRouter() + + +class ProviderState(BaseModel): + state: str # noqa: E999 + + +@pact_router.post("/_pact/provider_states") +async def provider_states(provider_state: ProviderState): + mapping = { + "UserA does not exist": setup_no_user_a, + "UserA exists and is not an administrator": setup_user_a_nonadmin, + } + mapping[provider_state.state]() + + return {"result": mapping[provider_state.state]} + + +# Make sure the app includes both routers. This needs to be done after the +# declaration of the provider_states +app.include_router(main_router) +app.include_router(pact_router) + + +def run_server(): + uvicorn.run(app) + + +def setup_no_user_a(): + if "UserA" in fakedb: + del fakedb["UserA"] + + +def setup_user_a_nonadmin(): + id = "00000000-0000-4000-a000-000000000000" + some_date = "2016-12-15T20:16:01" + ip_address = "198.0.0.1" + + fakedb["UserA"] = {"name": "UserA", "id": id, "created_on": some_date, "ip_address": ip_address, "admin": False} diff --git a/examples/fastapi_provider/tests/provider/__init__.py b/examples/fastapi_provider/tests/provider/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/fastapi_provider/tests/provider/test_provider.py b/examples/fastapi_provider/tests/provider/test_provider.py index b83d2330c..2ebec55f3 100644 --- a/examples/fastapi_provider/tests/provider/test_provider.py +++ b/examples/fastapi_provider/tests/provider/test_provider.py @@ -1,43 +1,88 @@ """pact test for user service client""" import logging import os -from multiprocessing import Process -import pytest -from ..fastapi_provider import run_server +import pytest from pact import Verifier log = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) -PACT_UPLOAD_URL = "http://127.0.0.1/pacts/provider/UserService/consumer" "/User_ServiceClient/version" -PACT_FILE = "userserviceclient-userservice.json" + +# For the purposes of this example, the broker is started up as a fixture defined +# in conftest.py. For normal usage this would be self-hosted or using Pactflow. PACT_BROKER_URL = "http://localhost" PACT_BROKER_USERNAME = "pactbroker" PACT_BROKER_PASSWORD = "pactbroker" -PACT_MOCK_HOST = "localhost" -PACT_MOCK_PORT = 8000 -PACT_URL = "http://{}:{}".format(PACT_MOCK_HOST, PACT_MOCK_PORT) -PACT_DIR = os.path.dirname(os.path.realpath(__file__)) +# For the purposes of this example, the FastAPI provider will be started up as +# a fixture in conftest.py ("server"). Alternatives could be, for example +# running a Docker container with a database of test data configured. +# This is the "real" provider to verify against. +PROVIDER_HOST = "127.0.0.1" +PROVIDER_PORT = 8000 +PROVIDER_URL = f"http://{PROVIDER_HOST}:{PROVIDER_PORT}" + + +def test_success(): + pass -@pytest.fixture(scope="module") -def server(): - proc = Process(target=run_server, args=(), daemon=True) - proc.start() - yield proc - proc.kill() # Cleanup after test +@pytest.fixture +def broker_opts(): + return { + "broker_username": PACT_BROKER_USERNAME, + "broker_password": PACT_BROKER_PASSWORD, + "broker_url": PACT_BROKER_URL, + "publish_version": "3", + "publish_verification_results": True, + } + + +def test_user_service_provider_against_broker(server, broker_opts): + verifier = Verifier(provider="UserService", provider_base_url=PROVIDER_URL) + + # Request all Pact(s) from the Pact Broker to verify this Provider against. + # In the Pact Broker logs, this corresponds to the following entry: + # PactBroker::Api::Resources::ProviderPactsForVerification -- Fetching pacts for verification by UserService -- {:provider_name=>"UserService", :params=>{}} + success, logs = verifier.verify_with_broker( + **broker_opts, + verbose=True, + provider_states_setup_url=f"{PROVIDER_URL}/_pact/provider_states", + enable_pending=False, + ) + # If publish_verification_results is set to True, the results will be + # published to the Pact Broker. + # In the Pact Broker logs, this corresponds to the following entry: + # PactBroker::Verifications::Service -- Creating verification 200 for \ + # pact_version_sha=c8568cbb30d2e3933b2df4d6e1248b3d37f3be34 -- \ + # {"success"=>true, "providerApplicationVersion"=>"3", "wip"=>false, \ + # "pending"=>"true"} + + # Note: + # If "successful", then the return code here will be 0 + # This can still be 0 and so PASS if a Pact verification FAILS, as long as + # it has not resulted in a REGRESSION of an already verified interaction. + # See https://docs.pact.io/pact_broker/advanced_topics/pending_pacts/ for + # more details. + assert success == 0 -def test_get_user_non_admin(server): - verifier = Verifier(provider="UserService", provider_base_url=PACT_URL) +def test_user_service_provider_against_pact(server): + verifier = Verifier(provider="UserService", provider_base_url=PROVIDER_URL) + # Rather than requesting the Pact interactions from the Pact Broker, this + # will perform the verification based on the Pact file locally. + # + # Because there is no way of knowing the previous state of an interaction, + # if it has been successful in the past (since this is what the Pact Broker + # is for), if the verification of an interaction fails then the success + # result will be != 0, and so the test will FAIL. output, _ = verifier.verify_pacts( - "./userserviceclient-userservice.json", + "../pacts/userserviceclient-userservice.json", verbose=False, - provider_states_setup_url="{}/_pact/provider_states".format(PACT_URL), + provider_states_setup_url="{}/_pact/provider_states".format(PROVIDER_URL), ) assert output == 0 diff --git a/examples/fastapi_provider/verify_pact.sh b/examples/fastapi_provider/verify_pact.sh index 48a6d64c5..df0ec34a6 100755 --- a/examples/fastapi_provider/verify_pact.sh +++ b/examples/fastapi_provider/verify_pact.sh @@ -1,33 +1,37 @@ #!/bin/bash set -o pipefail -python pact_provider.py & &>/dev/null -FLASK_PID=$! -VERSION=$1 +# Run the FastAPI server, using the pact_provider.py as the app to be able to +# inject the provider_states endpoint +uvicorn tests.pact_provider:app & &>/dev/null +FASTAPI_PID=$! +# Make sure the FastAPI server is stopped when finished to avoid blocking the port function teardown { - echo "Tearing down Flask server ${FLASK_PID}" - CHILD=`pgrep -P $FLASK_PID` - echo "Kill provider app with pid $CHILD" - - kill -9 $CHILD + echo "Tearing down FastAPI server ${FASTAPI_PID}" + kill -9 $FASTAPI_PID } trap teardown EXIT +# Wait a little in case FastAPI isn't quite ready +sleep 1 + +VERSION=$1 if [ -x $VERSION ]; then echo "Validating provider locally" - pact-verifier --provider-base-url=http://localhost:5001 \ - --provider-states-setup-url=http://localhost:5001/_pact/provider_states \ - ./userserviceclient-userservice.json + pact-verifier --provider-base-url=http://localhost:8000 \ + --provider-states-setup-url=http://localhost:8000/_pact/provider_states \ + ../pacts/userserviceclient-userservice.json else + echo "Validating against Pact Broker" - pact-verifier --provider-base-url=http://localhost:5001 \ + pact-verifier --provider-base-url=http://localhost:8000 \ --provider-app-version $VERSION \ --pact-url="http://127.0.0.1/pacts/provider/UserService/consumer/UserServiceClient/latest" \ --pact-broker-username pactbroker \ --pact-broker-password pactbroker \ --publish-verification-results \ - --provider-states-setup-url=http://localhost:5001/_pact/provider_states + --provider-states-setup-url=http://localhost:8000/_pact/provider_states fi \ No newline at end of file diff --git a/examples/flask_provider/src/provider.py b/examples/flask_provider/src/provider.py index 3b21a7f36..abdb4549f 100644 --- a/examples/flask_provider/src/provider.py +++ b/examples/flask_provider/src/provider.py @@ -10,7 +10,7 @@ def get_user_by_name(name: str): """Handle requests to retrieve a single user from the simulated database. :param name: Name of the user to "search for" - :return: The user data if found, None if not + :return: The user data if found, None (HTTP 404) if not """ user_data = fakedb.get(name) if not user_data: From 4d5c35e82ad332566295aa4be98610491f40e4ea Mon Sep 17 00:00:00 2001 From: mikegeeves <71873349+mikegeeves@users.noreply.github.com> Date: Sat, 30 Oct 2021 12:28:43 +0100 Subject: [PATCH 2/4] test(examples-fastapi): tidy FastAPI example, making consistent with Flask re #270 --- examples/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/README.md b/examples/README.md index e7abdbb8e..e74683017 100644 --- a/examples/README.md +++ b/examples/README.md @@ -157,14 +157,14 @@ The following commands can be run from within your [Virtual Environment], in the To perform the python tests: ```bash -pip install -r requirements.txt # Install the dependencies for the Flask example +pip install -r requirements.txt # Install the dependencies for the FastAPI 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 FastAPI, and then run the tests ``` To perform verification using CLI to verify the [Pact file] against the FastAPI [Provider] instead of the python tests: ```bash -pip install -r requirements.txt # Install the dependencies for the Flask example +pip install -r requirements.txt # Install the dependencies for the FastAPI example ./verify_pact.sh # Wrapper script to first run FastAPI, and then use `pact-verifier` to verify locally ``` @@ -172,8 +172,8 @@ To perform verification using CLI, but verifying the [Pact file] previously prov 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 +pip install -r requirements.txt # Install the dependencies for the FastAPI example +./verify_pact.sh 1 # Wrapper script to first run FastAPI, and then use `pact-verifier` to verify and publish ``` ### Output @@ -204,4 +204,4 @@ without a [Pact Broker]. [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 \ No newline at end of file +[How to Run a Flask Application]: https://www.twilio.com/blog/how-run-flask-application From 6cfb8edc1c96816a42abf211b53af2103dd89fc6 Mon Sep 17 00:00:00 2001 From: Mike Geeves Date: Sat, 30 Oct 2021 12:34:12 +0100 Subject: [PATCH 3/4] test(examples-fastapi): tidy FastAPI example, making consistent with Flask re #270 --- examples/fastapi_provider/tests/provider/test_provider.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/fastapi_provider/tests/provider/test_provider.py b/examples/fastapi_provider/tests/provider/test_provider.py index 2ebec55f3..427a4ca52 100644 --- a/examples/fastapi_provider/tests/provider/test_provider.py +++ b/examples/fastapi_provider/tests/provider/test_provider.py @@ -1,6 +1,5 @@ """pact test for user service client""" import logging -import os import pytest From 720cd07267aa58402753196ac4c0e306e182f8d0 Mon Sep 17 00:00:00 2001 From: Mike Geeves Date: Sat, 30 Oct 2021 19:24:03 +0100 Subject: [PATCH 4/4] test(examples-fastapi): tidy FastAPI example, making consistent with Flask re #270 --- examples/fastapi_provider/requirements.txt | 1 + examples/fastapi_provider/tests/conftest.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/fastapi_provider/requirements.txt b/examples/fastapi_provider/requirements.txt index b99ec5a21..dfb69dbb2 100644 --- a/examples/fastapi_provider/requirements.txt +++ b/examples/fastapi_provider/requirements.txt @@ -2,3 +2,4 @@ fastapi==0.67.0 pytest==5.4.1 requests>=2.26.0 uvicorn>=0.14.0 +testcontainers==3.3.0 \ No newline at end of file diff --git a/examples/fastapi_provider/tests/conftest.py b/examples/fastapi_provider/tests/conftest.py index 20a998161..d4d5e8427 100644 --- a/examples/fastapi_provider/tests/conftest.py +++ b/examples/fastapi_provider/tests/conftest.py @@ -1,4 +1,5 @@ import pathlib +import sys from multiprocessing import Process import docker @@ -13,7 +14,13 @@ def server(): proc = Process(target=run_server, args=(), daemon=True) proc.start() yield proc - proc.kill() # Cleanup after test + + # Cleanup after test + if sys.version_info >= (3, 7): + # multiprocessing.kill is new in 3.7 + proc.kill() + else: + proc.terminate() def pytest_addoption(parser):