Mike Geeves
06/16/2022, 8:28 PM/_pact/provider_states"
endpoint, I quite liked that pattern, iirc I did have a conversation about why you have to have this other way of starting it along the lines of "well, alternatively we could have the /_pact/provider_states_ endpoint exposed alongside the other routes, where people could then wipe the database from a single unauthenticated call" -> "ohhhhh! I get it now, this way is better" 🙂 but I agree having to explain it one person at a time is tricky to scaleMike Geeves
06/16/2022, 8:33 PMMike Geeves
06/18/2022, 10:50 AMMike Geeves
06/18/2022, 10:58 AMMike Geeves
06/18/2022, 10:58 AMJohn Williams
06/22/2022, 10:08 AMHint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/homebrew/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/importlib/__init__.py:127: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
consumer.py:9: in <module>
from web.api.api import cancel_order
E ModuleNotFoundError: No module named 'web'
Im not too familiar with pythonJohn Williams
06/22/2022, 12:40 PMimport atexit
import logging
import os
import requests
import pytest
import subprocess
from pact import Consumer, Like, Provider, Term, Format
from orders_service.orders import Order
log = logging.getLogger(__name__)
logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
# 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 = "<https://johnwilliams.pactflow.io/>"
PACT_BROKER_USERNAME = "johnwilliams"
PACT_BROKER_PASSWORD = "secure123"
# Define where to run the mock server, for the consumer to connect to. These
# are the defaults so may be omitted
PACT_MOCK_HOST = "localhost"
PACT_MOCK_PORT = 1234
# Where to output the JSON Pact files created by any tests
PACT_DIR = os.path.dirname(os.path.realpath(__file__))
@pytest.fixture
def consumer() -> Order.cancel:
return Order.cancel("http://{host}:{port}".format(host=PACT_MOCK_HOST, port=PACT_MOCK_PORT))
@pytest.fixture(scope="session")
def pact(request):
"""Setup a Pact Consumer, which provides the Provider mock service. This
will generate and optionally publish Pacts to the Pact Broker"""
# When publishing a Pact to the Pact Broker, a version number of the Consumer
# is required, to be able to construct the compatability matrix between the
# Consumer versions and Provider versions
# version = request.config.getoption("--publish-pact")
# publish = True if version else False
pact = Consumer("UserServiceClient", version=1).has_pact_with(
Provider("UserService"),
host_name=PACT_MOCK_HOST,
port=PACT_MOCK_PORT,
pact_dir=PACT_DIR,
publish_to_broker=True,
broker_base_url=PACT_BROKER_URL,
broker_username=PACT_BROKER_USERNAME,
broker_password=PACT_BROKER_PASSWORD,
)
pact.start_service()
# Make sure the Pact mocked provider is stopped when we finish, otherwise
# port 1234 may become blocked
atexit.register(pact.stop_service)
yield pact
# This will stop the Pact mock server, and if publish is True, submit Pacts
# to the Pact Broker
pact.stop_service()
# Given we have cleanly stopped the service, we do not want to re-submit the
# Pacts to the Pact Broker again atexit, since the Broker may no longer be
# available if it has been started using the --run-broker option, as it will
# have been torn down at that point
pact.publish_to_broker = False
def test_cancel_scheduled_order(pact, consumer):
expected = \
{
"id": "1e54e244-d0ab-46ed-a88a-b9e6037655ef",
"order": [
{
"product": "coffee",
"quantity": 1,
"size": "small"
}
],
"scheduled": "Wed, 22 Jun 2022 09:21:26 GMT",
"status": "cancelled"
}
(pact
.given('A scheduled order exists and it is not cancelled already')
.upon_receiving('a request for cancellation')
.with_request('get', f'<http://localhost:3001/kitchen/schedule/{Like(12343)}/cancel>')
.will_respond_with(200, body=Like(expected)))
with pact:
response = consumer.cancel()
assert response['status'] == "cancelled"
pact.verify()
John Williams
06/22/2022, 12:41 PMwith pact:
response = consumer.cancel()
assert response['status'] == "cancelled"
John Williams
06/22/2022, 12:41 PMclass Order:
def __init__(self, id, created, items, status, schedule_id=None,
delivery_id=None, order_=None):
self._order = order_
self._id = id
self._created = created
self.items = [OrderItem(**item) for item in items]
self._status = status
self.schedule_id = schedule_id
self.delivery_id = delivery_id
@property
def id(self):
return self._id or self._order.id
@property
def created(self):
return self._created or self._order.created
@property
def status(self):
return self._status or self._order.status
def cancel(self):
if self.status == 'progress':
response = requests.get(
f'<http://localhost:3001/kitchen/schedule/{self.schedule_id}/cancel>',
data={'order': self.items}
)
if response.status_code == 200:
return
raise APIIntegrationError(
f'Could not cancel order with id {self.id}'
)
if self.status == 'delivery':
raise InvalidActionError(f'Cannot cancel order with id {self.id}')
John Williams
06/22/2022, 12:42 PMself = '<http://localhost:1234>'
def cancel(self):
> if self.status == 'progress':
E AttributeError: 'str' object has no attribute 'status'
../orders_service/orders.py:48: AttributeError
John Williams
06/23/2022, 6:15 AMI, [2022-06-23T16:14:14.635384 #84978] INFO -- : Cleared interactions
I, [2022-06-23T16:14:14.663371 #84978] INFO -- : Registered expected interaction GET <http://127.0.0.1:5000/kitchen/schedule/><pact.matchers.Like object at 0x104e7b220>/cancel
D, [2022-06-23T16:14:14.663741 #84978] DEBUG -- : {
"description": "a request for cancellation",
"providerState": "A scheduled order exists and it is not cancelled already",
"request": {
"method": "get",
"path": "<http://127.0.0.1:5000/kitchen/schedule/><pact.matchers.Like object at 0x104e7b220>/cancel"
},
"response": {
"status": 200,
"headers": {
},
"body": {
"json_class": "Pact::SomethingLike",
"contents": {
"id": "1e54e244-d0ab-46ed-a88a-b9e6037655ef",
"order": [
{
"product": "coffee",
"quantity": 1,
"size": "small"
}
],
"scheduled": "Wed, 22 Jun 2022 09:21:26 GMT",
"status": "cancelled"
}
}
},
"metadata": null
}
W, [2022-06-23T16:14:14.721886 #84978] WARN -- : Verifying - actual interactions do not match expected interactions.
Missing requests:
GET <http://127.0.0.1:5000/kitchen/schedule/><pact.matchers.Like object at 0x104e7b220>/cancel
W, [2022-06-23T16:14:14.721961 #84978] WARN -- : Missing requests:
GET <http://127.0.0.1:5000/kitchen/schedule/><pact.matchers.Like object at 0x104e7b220>/cancel
John Williams
06/25/2022, 1:59 PM"""pact test for OrderService provider"""
import logging
import pytest
import os
from pact import Verifier
from dotenv import load_dotenv
load_dotenv()
log = logging.getLogger(__name__)
logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
# 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_BASE_URL=os.getenv('PACT_USER')
PACT_BROKER_TOKEN=os.getenv('PACT_BROKER_TOKEN')
# For the purposes of this example, the Flask provider will be started up as part
# of run_pytest.sh when running the tests. 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 = "localhost"
PROVIDER_PORT = 5000
PROVIDER_URL = f"http://{PROVIDER_HOST}:{PROVIDER_PORT}"
@pytest.fixture
def broker_opts():
return {
# "broker_username": PACT_BROKER_USERNAME,
# "broker_password": PACT_BROKER_PASSWORD,
"broker_url": PACT_BROKER_BASE_URL,
"publish_version": "1",
"publish_verification_results": True,
"broker_base_url":PACT_BROKER_BASE_URL,
"broker_token":PACT_BROKER_TOKEN
}
def test_user_service_provider_against_broker(broker_opts):
verifier = Verifier(provider="KitchenService", 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_KitchenService_provider_against_pact():
verifier = Verifier(provider="KitchenService", 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(
"../pacts/OrderService-KitchenService.json",
verbose=False,
provider_states_setup_url="{}/_pact/provider_states".format(PROVIDER_URL),
)
assert output == 0
but I get the error:
I did create a new endpoint that posts the data to the KitchenService on the endpoint:
So im not quite sure what the error means?John Williams
07/01/2022, 2:53 AMtmegha
07/06/2022, 7:18 AMcontainer for service "broker_app" is unhealthy
What can be the issue with the container? Also, on running ./run_pytest.sh
, with a healthy broker, I get the following error:-
docker.errors.DockerException: Error while fetching server API version: ('Connection aborted.', ConnectionRefusedError(61, 'Connection refused'))
What can be causing the issue? Any insights will be greatly appreciated. TIAElliott Murray
07/06/2022, 7:23 AMAdam Anderson
07/08/2022, 1:19 AMGitHub
07/18/2022, 3:07 PMdef write_to_pact_file(self):
"""
Create a pact file based on provided attributes in DSL.
Return 0 if success, 1 otherwise.
:rtype: int
"""
command = [
MESSAGE_PATH,
"update",
json.dumps(self._messages[0]),
"--pact-dir", self.pact_dir,
f"--pact-specification-version={self.version}",
"--consumer", f"{self.consumer.name}",
"--provider", f"{self.provider.name}",
]
self._message_process = Popen(command)
self._message_process.wait()
It claims to return 0 or 1 but does not!
Need to look at other implementations, how the STDERR/STDOUT is handled, this should maybe do something like Popen(command, stdout=PIPE, stderr=PIPE) and communicate instead of wait?
May simply be best to just "return self._message_process.wait()" -> but then how to handle later.
For reference, picked up while doing silly things with Term("5|15|60", 60)
which is obviously invalid, causes ruby to fail, but it carries on regardless. The end result being if a previous pact file exists, it won't then fail. If one does not exist it fails because "No value provided for required pact_files" which isn't a helpful message.
pact-foundation/pact-pythonjson
07/20/2022, 2:14 PMTimbo
07/21/2022, 3:03 AM./pact/bin/pactflow publish-provider-contract \
"openapi/v0/openapi.yaml" \
--broker-token=<token> \
--broker-base-url=<https://adae.pactflow.io> \
--specification=oas \
--provider "messaging-api" \
--provider-app-version 12345\
--branch cdc \
After I ran the above, it published the openapi spec and also validated the consumer contract but
I also noticed the provider contract (self verification) was not done. That's the piece I am a bit confused about.
Our openapi spec is manually generated and we use pytest to validate it.
How can I get the provider contract (openapi) to be validated in pactflow as well? I saw the below args for the CLI but I am not quite sure how to use them.
--verification-exit-code=EXIT_CODE \
--verification-results REPORT_PATH \
--verification-results-content-type REPORT_CONTENT_TYPE \
--verifier VERIFIER
Is it even necessary to do the provider contract self validation in pactflow if we already have tests that validate the openapi spec?
I am also contemplating using dredd maybe to validate our openapi spec. I am looking for a bit of guidance, best practice etc. Any help is greatly appreciated.Matt (pactflow.io / pact-js / pact-go)
Is it even necessary to do the provider contract self validation in pactflow if we already have tests that validate the openapi spec?Pactflow doesn’t do the verification, you just need to tell Pactflow that it was / was not verified successfully.
Matt (pactflow.io / pact-js / pact-go)
Matt (pactflow.io / pact-js / pact-go)
Matt (pactflow.io / pact-js / pact-go)
0
would indicate successtmegha
07/21/2022, 10:14 AMdocker.errors.ContainerError: Command 'publish /pacts --consumer-app-version 1' in image 'pactfoundation/pact-cli:latest' returned non-zero exit status 1: b"bundler: failed to load command: /pact/bin/pact (/pact/bin/pact)\n/usr/lib/ruby/2.7.0/net/http.rb:960:in `initialize': Failed to open TCP connection to broker_app:9292 (getaddrinfo: Name does not resolve)
I did not change anything in the sharedfixtures.py file. The pact-broker app is up and running on 9292 but I am still getting this error. Any help would be greatly appreciated!Adam Anderson
08/03/2022, 11:46 PMlocalhost:<port>
?Andre Rodrigues
08/17/2022, 2:24 PMM K
08/30/2022, 11:10 AMdavT
09/08/2022, 12:00 PMMatt (pactflow.io / pact-js / pact-go)
Matt (pactflow.io / pact-js / pact-go)