Skip to content

Commit

Permalink
chore: allow conditional running with PactFlow for a self-hosted broker
Browse files Browse the repository at this point in the history
allows for testing in environments which dont have docker

TODO - set command for pact publish with pact-ruby-standalone instead of docker
  • Loading branch information
YOU54F committed Aug 2, 2023
1 parent d579913 commit 59e2ef6
Show file tree
Hide file tree
Showing 18 changed files with 764 additions and 61 deletions.
9 changes: 7 additions & 2 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ BUILD_TEST_TASK_TEMPLATE: &BUILD_TEST_TASK_TEMPLATE
- python -m flake8 --show-source --exclude examples/area_calculator/area_calculator_pb2.py,examples/area_calculator/area_calculator_pb2_grpc.py,.git,__pycache__,build,dist,.tox
- python -m pydocstyle pact
- python -m tox -e test
# - make examples
- make examples

linux_arm64_task:
only_if: $CIRRUS_CHANGE_TITLE !=~ 'ci\(gha\).*'
env:
RUN_BROKER: 0
USE_PACTFLOW: 1
# PACT_FFI_PATH: .tox/test/lib/python$VERSION/site-packages/pact/bin
matrix:
# - VERSION: 3.6
Expand All @@ -34,6 +36,8 @@ macos_arm64_task:
macos_instance:
image: ghcr.io/cirruslabs/macos-ventura-base:latest
env:
RUN_BROKER: "0"
USE_PACTFLOW: "1"
PATH: ${HOME}/.pyenv/shims:${PATH}
# PACT_FFI_PATH: .tox/test/lib/python$VERSION/site-packages/pact/bin
matrix:
Expand All @@ -45,9 +49,10 @@ macos_arm64_task:
- VERSION: 3.11
install_script:
- brew update
- brew install pyenv
- brew install pyenv protobuf # protobuf only needed if using grpc plugins
- pyenv install ${VERSION}
- pyenv global ${VERSION}
- pyenv rehash
- chmod +x script/install_plugins.sh
- find examples -name '*.sh' -exec chmod +x {} \;
<< : *BUILD_TEST_TASK_TEMPLATE
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# pact-python specific ignores
e2e/pacts
userserviceclient-userservice.json
detectcontentlambda-contentprovider.json
# userserviceclient-userservice.json # unexcluded for cirrus ci locally
# detectcontentlambda-contentprovider.json # unexcluded for cirrus ci locally
pact/bin

# Byte-compiled / optimized / DLL files
Expand Down Expand Up @@ -109,4 +109,4 @@ ENV/
# MacOS stuff
.DS_Store
# Generated pact directories
/pacts/
# /pacts/
62 changes: 48 additions & 14 deletions UPGRADE_FFI.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ For the following
)
```

* You can provide multiple sources in the `sources` argument
* You can provide auth credentials, if a pact broker requires it
- You can provide multiple sources in the `sources` argument
- You can provide auth credentials, if a pact broker requires it

```python
verifier = VerifierV3(provider="area-calculator-provider",
Expand All @@ -60,10 +60,10 @@ For the following

#### Dynamically Fetched Sources

* You can dynamically fetch pacts from the broker.
* You should not provide anything in the `sources` argument and set the `broker_url`
- You can dynamically fetch pacts from the broker.
- You should not provide anything in the `sources` argument and set the `broker_url`

* The following will retrieve the latest pacts for the named `provider`
- The following will retrieve the latest pacts for the named `provider`

```python
verifier = VerifierV3(provider="area-calculator-provider",
Expand All @@ -76,10 +76,10 @@ For the following
)
```

* You can dynamically fetch pacts from the broker.
* Provide dynamic fetching arguments
* `consumer_version_selectors`
* `consumer_version_tags`
- You can dynamically fetch pacts from the broker.
- Provide dynamic fetching arguments
- `consumer_version_selectors`
- `consumer_version_tags`

```python
verifier = VerifierV3(provider="area-calculator-provider",
Expand All @@ -104,11 +104,11 @@ Now utilises `VerifierV3` rather than the old ruby implementation.

No changes to interface.

Ideally should
Ideally should

* change to utilise all options of `VerifierV3`
* not hardcode `pact_dir` (use `sources` instead)
* replace `verify` & `verify_with_broker` with `verify_pacts`
- change to utilise all options of `VerifierV3`
- not hardcode `pact_dir` (use `sources` instead)
- replace `verify` & `verify_with_broker` with `verify_pacts`

## Consumer

Expand All @@ -120,6 +120,40 @@ Rough examples in
`./tests/ffi/test_ffi_http_consumer.py`
`./tests/ffi/test_ffi_message_consumer.py`

### PactV3 Consumer

New imports and matchers/generators

```python
from pact import PactV3
from pact.matchers_v3 import Like, Regex, Format
```

* lifecyle changes
* start_provider moves to inside test, after interaction setup
* stop_provider no longer called
* verify moves to inside test, after client has issued request
* can be pre or post unit test assertions.

## Message Consumer

Not started
Not started

## Logging

By default, pact-python will log to a buffer, at INFO level

you can set

- PACT_LOG_LEVEL
- NONE | ERROR | INFO | DEBUG | TRACE
- PACT_LOG_OUTPUT
- STDOUT | FILE | BUFFER
- PACT_LOG_FILE
- Location to logfile if PACT_LOG_OUTPUT=FILE (default `./log/pact.log`)

examples

`PACT_LOG_LEVEL=TRACE PACT_LOG_OUTPUT=STDOUT pytest tests/test_todo_consumer.py -rP`
`PACT_LOG_LEVEL=INFO PACT_LOG_OUTPUT=FILE pytest tests/test_todo_consumer.py -rP`
`PACT_LOG_LEVEL=DEBUG PACT_LOG_OUTPUT=BUFFER pytest tests/test_todo_consumer.py -rP`
57 changes: 41 additions & 16 deletions examples/common/sharedfixtures.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from os.path import join, dirname
from os import getenv
import platform
import pathlib
import subprocess

import docker
import pytest
Expand Down Expand Up @@ -58,30 +61,52 @@ def publish_existing_pact(broker):
:revision_number=>nil, :consumer_version_number=>"1", :pact_version_sha=>nil, \
:consumer_name_in_pact=>"UserServiceClient", :provider_name_in_pact=>"UserService"}
"""
if int(getenv('SKIP_PUBLISH', '1')) == 0:
return

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",
}

use_pactflow = int(getenv('USE_PACTFLOW', '0'))
if use_pactflow == 1:
envs = {
"PACT_BROKER_BASE_URL": "https://test.pactflow.io",
"PACT_BROKER_USERNAME": "dXfltyFMgNOFZAxr8io9wJ37iUpY42M",
"PACT_BROKER_PASSWORD": "O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1",
}
else:
envs = {
"PACT_BROKER_BASE_URL": "http://broker_app:9292",
"PACT_BROKER_USERNAME": "pactbroker",
"PACT_BROKER_PASSWORD": "pactbroker",
}

target_platform = platform.platform().lower()

if 'macos' in target_platform or 'windows' in target_platform:
if ('macos' in target_platform or 'windows' in target_platform) and use_pactflow != 1:
envs["PACT_BROKER_BASE_URL"] = "http://host.docker.internal:80"

client = docker.from_env()

print("Publishing existing Pact")
client.containers.run(
remove=True,
network="broker_default",
volumes=pacts,
image="pactfoundation/pact-cli:latest-multi",
environment=envs,
command="publish /pacts --consumer-app-version 1",
)
use_standalone = int(getenv('USE_STANDALONE', '1'))
if use_standalone == 1:
# pb = subprocess.Popen(['pact-broker', 'pacts --consumer-app-version 1'],
# cwd=join(dirname(__file__), '..', '..', 'pact', 'bin','pact','bin'))
result = subprocess.run([join(dirname(__file__), '..', '..', 'pact', 'bin', 'pact', 'bin', 'pact-broker'),
'publish', 'pacts', '--consumer-app-version', '1'], shell=True, capture_output=True, text=True)

print(result.stdout)
else:
client = docker.from_env()

client.containers.run(
remove=True,
# network="broker_default",
volumes=pacts,
image="pactfoundation/pact-cli:latest-multi",
environment=envs,
command="publish /pacts --consumer-app-version 1",
)

print("Finished publishing")


Expand Down
6 changes: 5 additions & 1 deletion examples/consumer/run_pytest.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/bin/bash
set -o pipefail

pytest tests --run-broker True --publish-pact 1
if [ "$RUN_BROKER" = '0' ]; then
pytest tests --publish-pact 1 -rP
else
pytest tests --run-broker True --publish-pact 1
fi
16 changes: 11 additions & 5 deletions examples/consumer/tests/consumer/test_user_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@
# If publishing the Pact(s), they will be submitted to the Pact Broker here.
# 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"
use_pactflow = int(os.getenv('USE_PACTFLOW', '0'))
if use_pactflow == 1:
PACT_BROKER_URL = os.getenv("PACT_BROKER_URL", "https://test.pactflow.io")
PACT_BROKER_USERNAME = os.getenv("PACT_BROKER_USERNAME", "dXfltyFMgNOFZAxr8io9wJ37iUpY42M")
PACT_BROKER_PASSWORD = os.getenv("PACT_BROKER_PASSWORD", "O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1")
else:
PACT_BROKER_URL = os.getenv("PACT_BROKER_URL", "http://localhost")
PACT_BROKER_USERNAME = os.getenv("PACT_BROKER_USERNAME", "pactbroker")
PACT_BROKER_PASSWORD = os.getenv("PACT_BROKER_PASSWORD", "pactbroker")

# Define where to run the mock server, for the consumer to connect to. These
# are the defaults so may be omitted
Expand Down Expand Up @@ -102,7 +108,7 @@ def test_get_user_non_admin(pact: PactV3, consumer):
# appropriate content e.g. for ip_address.
(
pact
.new_http_interaction('same_as_upon_receiving')
.new_http_interaction('a request for UserA')
.given("UserA exists and is not an administrator")
.upon_receiving("a request for UserA")
.with_request("get", "/users/UserA")
Expand All @@ -127,7 +133,7 @@ def test_get_non_existing_user(pact: PactV3, consumer):
# Pact mock provider will behave. In this case, we expect a 404
(
pact
.new_http_interaction('same_as_upon_receiving')
.new_http_interaction('a request for UserA')
.given("UserA does not exist")
.upon_receiving("a request for UserA")
.with_request("get", "/users/UserA")
Expand Down
Loading

0 comments on commit 59e2ef6

Please sign in to comment.