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?Mike Geeves
06/25/2022, 2:24 PMMike Geeves
06/25/2022, 2:25 PMJohn Williams
06/26/2022, 1:06 AMJohn Williams
06/26/2022, 7:11 AM@blueprint.response(200)
@blueprint.route('/_pact/provider_states', methods=['POST'])
def provider_states():
data = setup_scheduled_order_for_cancellation()
mapping = {
"A scheduled order exists and it is not cancelled already": data,
}
mapping[request.json["state"]]()
# return {"state":mapping}
return jsonify({"result": request.json['state']})
# return mapping
def setup_scheduled_order_for_cancellation():
print('inside creat teest data function')
payload = {}
payload['id'] = '273429e2-e513-4df7-95bc-eeaad7e0e5aa'
payload['scheduled'] = datetime.utcnow()
payload['status'] = 'pending'
payload['order'] = [
{
"product": "coffee",
"quantity": 1,
"size": "big"
}
]
schedules.append(payload)
validate_schedule(payload)
return payload
im not sure what the following lines are supposed to do:
mapping[request.json["state"]]()
return jsonify({"result": request.json['state']})
Matt (pactflow.io / pact-js / pact-go)
Matt (pactflow.io / pact-js / pact-go)
403
when attempting to communicate to your endpoint. Is there auth on it?John Williams
06/26/2022, 11:02 AMMatt (pactflow.io / pact-js / pact-go)
John Williams
06/26/2022, 11:06 AMJohn Williams
06/26/2022, 11:06 AMJohn Williams
06/26/2022, 11:07 AMJohn Williams
06/26/2022, 11:08 AMJohn Williams
06/26/2022, 11:08 AMFailed interactions:
PACT_DESCRIPTION='a request for cancellation' PACT_PROVIDER_STATE='A scheduled order exists and it is not cancelled already' /Users/johnwilliams/.local/share/virtualenvs/kitchen-75vucmml/bin/pytest provider_test.py # A request for cancellation given A scheduled order exists and it is not cancelled already
opening connection to <http://reallyusefulengine.pactflow.io:443|reallyusefulengine.pactflow.io:443>...
opened
starting SSL for <http://reallyusefulengine.pactflow.io:443|reallyusefulengine.pactflow.io:443>...
SSL established
<- "POST /pacts/provider/KitchenService/consumer/OrdersService/pact-version/151338713becd543895163268e6e1043cd4d772f/metadata/c1tdW2xdPXRydWUmc1tdW2N2XT0xNg/verification-results HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: application/hal+json, */*\r\nUser-Agent: Ruby\r\nContent-Type: application/json\r\nAuthorization: [redacted]\r\n"
<- "{\"success\":false,\"providerApplicationVersion\":\"1\",\"testResults\":{\"tests\":[{\"testDescription\":\"has status code 200\",\"testFullDescription\":\"Verifying a pact between OrdersService and KitchenService Given A scheduled order exists and it is not cancelled already a request for cancellation with GET /kitchen/schedule/273429e2-e513-4df7-95bc-eeaad7e0e5aa/cancel returns a response which has status code 200\",\"status\":\"failed\",\"interactionProviderState\":\"A scheduled order exists and it is not cancelled already\",\"interactionDescription\":\"a request for cancellation\",\"exception\":{\"class\":\"Pact::ProviderVerifier::SetUpProviderStateError\",\"message\":\"Error setting up provider state 'A scheduled order exists and it is not cancelled already' for consumer 'OrdersService' at <http://localhost:5000/_pact/provider_states>. response status=403 response body=\"}},{\"testDescription\":\"has a matching body\",\"testFullDescription\":\"Verifying a pact between OrdersService and KitchenService Given A scheduled order exists and it is not cancelled already a request for cancellation with GET /kitchen/schedule/273429e2-e513-4df7-95bc-eeaad7e0e5aa/cancel returns a response which has a matching body\",\"status\":\"failed\",\"interactionProviderState\":\"A scheduled order exists and it is not cancelled already\",\"interactionDescription\":\"a request for cancellation\",\"exception\":{\"class\":\"Pact::ProviderVerifier::SetUpProviderStateError\",\"message\":\"Error setting up provider state 'A scheduled order exists and it is not cancelled already' for consumer 'OrdersService' at <http://localhost:5000/_pact/provider_states>. response status=403 response body=\"}}],\"summary\":{\"testCount\":2,\"failureCount\":2},\"metadata\":{\"warning\":\"These test results use a beta format. Do not rely on it, as it will definitely change.\",\"pactVerificationResultsSpecification\":{\"version\":\"1.0.0-beta.1\"}}}}"
-> "HTTP/1.1 201 Created\r\n"
-> "Date: Sun, 26 Jun 2022 11:06:16 GMT\r\n"
-> "Content-Type: application/hal+json;charset=utf-8\r\n"
-> "Content-Length: 2830\r\n"
-> "Connection: keep-alive\r\n"
-> "Vary: Accept\r\n"
-> "Location: <https://reallyusefulengine.pactflow.io/pacts/provider/KitchenService/consumer/OrdersService/pact-version/151338713becd543895163268e6e1043cd4d772f/verification-results/246>\r\n"
-> "Server: Webmachine-Ruby/1.6.0 Rack/1.3\r\n"
-> "X-Pact-Broker-Version: 2.100.0\r\n"
-> "X-Content-Type-Options: nosniff\r\n"
-> "Strict-Transport-Security: max-age=63072000; includeSubDomains; preload\r\n"
-> "X-Pact-Broker-Git-Sha: eb163ede\r\n"
-> "X-Pactflow-Git-Sha: 20b84cf0a\r\n"
-> "X-Request-Id: 1d1c887ae363993d23c5ba48cc86741d\r\n"
-> "\r\n"
reading 2830 bytes...
-> "{\"providerName\":\"KitchenService\",\"providerApplicationVersion\":\"1\",\"success\":false,\"verificationDate\":\"2022-06-26T11:06:16.390+00:00\",\"testResults\":{\"tests\":[{\"testDescription\":\"has status code 200\",\"testFullDescription\":\"Verifying a pact between OrdersService and KitchenService Given A scheduled order exists and it is not cancelled already a request for cancellation with GET /kitchen/schedule/273429e2-e513-4df7-95bc-eeaad7e0e5aa/cancel returns a response which has status code 200\",\"status\":\"failed\",\"interactionProviderState\":\"A scheduled order exists and it is not cancelled already\",\"interactionDescription\":\"a request for cancellation\",\"exception\":{\"class\":\"Pact::ProviderVerifier::SetUpProviderStateError\",\"message\":\"Error setting up provider state 'A scheduled order exists and it is not cancelled already' for consumer 'OrdersService' at <http://localhost:5000/_pact/provider_states>. response status=403 response body=\"}},{\"testDescription\":\"has a matching body\",\"testFullDescription\":\"Verifying a pact between OrdersService and KitchenService Given A scheduled order exists and it is not cancelled already a request for cancellation with GET /kitchen/schedule/273429e2-e513-4df7-95bc-eeaad7e0e5aa/cancel returns a response which has a matching body\",\"status\":\"failed\",\"interactionProviderState\":\"A scheduled order exists and it is not cancelled already\",\"interactionDescription\":\"a request for cancellation\",\"exception\":{\"class\":\"Pact::ProviderVerifier::SetUpProviderStateError\",\"message\":\"Error setting up provider state 'A scheduled order exists and it is not cancelled already' for consumer 'OrdersService' at <http://localhost:5000/_pact/provider_states>. response status=403 response body=\"}}],\"summary\":{\"testCount\":2,\"failureCount\":2},\"metadata\":{\"warning\":\"These test results use a beta format. Do not rely on it, as it will definitely change.\",\"pactVerificationResultsSpecification\":{\"version\":\"1.0.0-beta.1\"}}},\"verifiedBy\":{},\"_links\":{\"self\":{\"title\":\"Verification result\",\"name\":\"Verification result 246 for Pact between OrdersService (1) and KitchenService\",\"href\":\"<https://reallyusefulengine.pactflow.io/pacts/provider/KitchenService/consumer/OrdersService/pact-version/151338713becd543895163268e6e1043cd4d772f/verification-results/246>\"},\"pb:pact-version\":{\"title\":\"Pact\",\"name\":\"Pact between OrdersService (1) and KitchenService\",\"href\":\"<https://reallyusefulengine.pactflow.io/pacts/provider/KitchenService/consumer/OrdersService/pact-version/151338713becd543895163268e6e1043cd4d772f/metadata/Y3ZuPTE>\"},\"pb:triggered-webhooks\":{\"title\":\"Webhooks triggered by the publication of this verification result\",\"href\":\"<https://reallyusefulengine.pactflow.io/pacts/provider/KitchenService/consumer/OrdersService/pact-version/151338713becd543895163268e6e1043cd4d772f/verification-results/246/triggered-webhooks>\"}}}"
read 2830 bytes
Conn keep-alive
INFO: Verification results published to <https://reallyusefulengine.pactflow.io/pacts/provider/KitchenService/consumer/OrdersService/pact-version/151338713becd543895163268e6e1043cd4d772f/verification-results/246>
Matt (pactflow.io / pact-js / pact-go)
Matt (pactflow.io / pact-js / pact-go)
VERBOSE=true
environment variable might land you with better logsMatt (pactflow.io / pact-js / pact-go)
John Williams
06/26/2022, 11:16 AM2) Verifying a pact between OrdersService and KitchenService Given A scheduled order exists and it is not cancelled already a request for cancellation with GET /kitchen/schedule/273429e2-e513-4df7-95bc-eeaad7e0e5aa/cancel returns a response which has a matching body
Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]
Pact::ProviderVerifier::SetUpProviderStateError:
Error setting up provider state 'A scheduled order exists and it is not cancelled already' for consumer 'OrdersService' at <http://localhost:5000/kitchen/_pact/provider_states>. response status=403 response body=
Matt (pactflow.io / pact-js / pact-go)
That doesn’t seem to line up with the code I see above. Specifically, I can’t see whereCopy code<http://localhost:5000/kitchen/_pact/provider_states>
/kitchen
is set on the states endpoint?John Williams
06/26/2022, 11:18 AMJohn Williams
06/26/2022, 11:19 AMJohn Williams
06/26/2022, 11:19 AMJohn Williams
06/26/2022, 11:19 AMJohn Williams
06/26/2022, 11:20 AMJohn Williams
06/26/2022, 11:21 AMMatt (pactflow.io / pact-js / pact-go)
one thing Matt if I use swagger to manually post to that _pact/provider_states endpoint i get an errorThis is your code right? So perhaps you have validation to ensure you have a valid JSON object sent to that endpoint
Matt (pactflow.io / pact-js / pact-go)
kitchen/_pact/provider_states
endpoint to
a) ensure it’s actually called
b) see what the shape of the payload from Pact is
c) ensure it’s not failing mid wayMatt (pactflow.io / pact-js / pact-go)
403
from some endpoint - you just need to figure out which one!)John Williams
06/26/2022, 11:26 AMJohn Williams
06/26/2022, 1:06 PMmapping[request.json["state"]]()
TypeError: 'dict' object is not callable
but im not sure how to fix that. Anyhow pretty late so ill go to bedJohn Williams
06/26/2022, 1:08 PMJohn Williams
06/26/2022, 1:09 PM@blueprint.response(200)
@blueprint.route('/kitchen/_pact/provider_states', methods=['Post'])
def provider_state():
print(request)
data = setup_scheduled_order_for_cancellation()
mapping = {
"A scheduled order exists and it is not cancelled already": data,
}
# mapping[request.json["state"]]()
return {"state":mapping}
at least it is one step forwardMatt (pactflow.io / pact-js / pact-go)
mapping = {
"A scheduled order exists and it is not cancelled already": setup_scheduled_order_for_cancellation(),
}
mapping[request.json["state"]]() # <- this now only fires the function if found. You should properly guard this to avoid errors for states that aren't mapped
John Williams
06/26/2022, 11:37 PMmapping[request.json["state"]]()
TypeError: 'dict' object is not callable
Matt (pactflow.io / pact-js / pact-go)
Matt (pactflow.io / pact-js / pact-go)
@blueprint.response(200)
@blueprint.route('/_pact/provider_states', methods=['POST'])
def provider_states():
data = setup_scheduled_order_for_cancellation() # -< You already called the function here
mapping = {
"A scheduled order exists and it is not cancelled already": data, # <- data is not a function now, it's the data. You should just pass the function here
}
mapping[request.json["state"]]() # <- can't call this, because it's just data not a function.
# return {"state":mapping}
return jsonify({"result": request.json['state']})
# return mapping
John Williams
06/26/2022, 11:49 PMJohn Williams
06/26/2022, 11:50 PMMatt (pactflow.io / pact-js / pact-go)
John Williams
06/27/2022, 12:16 AMJohn Williams
06/27/2022, 12:26 AM