any idea what the issue is with this: ```I, [2022-...
# pact-python
j
any idea what the issue is with this:
Copy code
I, [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
m
Copy code
<http://127.0.0.1:5000/kitchen/schedule/><pact.matchers.Like object at 0x104e7b220>/cancel
That should give you a hint.
I’m no Python expert, but that is a function call embedded in a string. So what you’re getting, is the output of a matcher (an object) being stringified.
Rather than
Copy code
with_request('get', f'<http://localhost:3001/kitchen/schedule/{Like(12343)}/cancel>')
You probably need to do something like this:
Copy code
with_request('get', Regex('<http://localhost:3001/kitchen/schedule/([0-9+])/cancel>')
(that’s not a real regex and I’m not sure that’s the right function to do a regex, but hopefully that gives you an idea)
j
ah well spotted thanks
🙌 1
Hey Matt I tried something similar but it didnt work. I looked at the readme in the python-pact and they just add paths to this :
Copy code
pact.with_request(
    method='POST',
    path='/api/v1/my-resources/123',
    body={'user_ids': [1, 2, 3]},
    headers={'Content-Type': 'application/json'},
)
In the pact log, where is the actal request and which is the expected it isn't very clear? I tried just adding hte path instead of the whole url.
this is what i see:
Copy code
I, [2022-06-23T19:21:41.781641 #92156]  INFO -- : Cleared interactions
I, [2022-06-23T19:21:41.798136 #92156]  INFO -- : Registered expected interaction GET /kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/cancel
D, [2022-06-23T19:21:41.798505 #92156] DEBUG -- : {
  "description": "a request for cancellation",
  "providerState": "A scheduled order exists and it is not cancelled already",
  "request": {
    "method": "GET",
    "path": "/kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/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-23T19:21:41.848351 #92156]  WARN -- : Verifying - actual interactions do not match expected interactions. 
Missing requests:
	GET /kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/cancel



W, [2022-06-23T19:21:41.848412 #92156]  WARN -- : Missing requests:
	GET /kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/cancel
m
Try to follow the patterns in the examples - the "UserConsumer" is effectively a service which provides methods you can call. This is your consumer, rather than one of the methods being called as part of declaring it (cancel, which is something you would call in the 'with pact' part) Since you haven't created an Order there's no state to check against, so none of the matches will apply Above about the status issue, you're trying to call cancel giving it the argument of the string, rather than the order I would again suggest try and get some help from someone in your company, or go and learn more about the python side, general coding etc. It will help you understand the Pact side much more. The Pact side is plenty confusing by itself without the barrier of using the language 😄
j
Hey Mike, If i run the test it cancels the order in the other service
So i basically created 2 microservices this orders Microservice and this Kitchen service. I just created the order in the kitchen service via swagger and when I run the test it successfully calls the order service cancel function and canceled the order int he kitchen service can you explain this again: Above about the status issue, you're trying to call cancel giving it the argument of the string, rather than the order
m
you have
Copy code
@pytest.fixture
def consumer() -> Order.cancel:
    return Order.cancel("http://{host}:{port}".format(host=PACT_MOCK_HOST, port=PACT_MOCK_PORT))
so you are trying to call Order.cancel with the argument of the url, you aren't creating Order anywhere I can see
j
Copy code
import atexit
from datetime import datetime
import logging
import os
from uuid import UUID
import requests
import pytest
import subprocess
import re

from pact import Consumer, Like, Provider, Term, Format, EachLike

from orders_service.orders import Order, OrderItem

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://xxx.pactflow.io/>"
PACT_BROKER_USERNAME = "xxx"
PACT_BROKER_PASSWORD = "xxx"

# 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("http://{host}:{port}".format(host=PACT_MOCK_HOST, port=PACT_MOCK_PORT))
    order = [{"id":"43b2ee86-9c06-45a0-a281-6c136e3bdd7b", "product":"coffee", "size":"big", "quantity":1}]
    # payload = Order(id="76ac3eb5-a656-49e3-b973-17e7c992cc5e", created="2022-06-23T03:11:19.415574", items=order, status="created").dict()
    # print("first payload: ",payload)
    # return Order(payload)
    return Order(id="43b2ee86-9c06-45a0-a281-6c136e3bdd7b", created="2022-06-23T05:59:02.948144", items=order, status="cancelled")



@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": Format().uuid(),
  "order": [
    {
      "product": "coffee",
      "quantity": 1,
      "size": "big"
    }
  ],
  "scheduled": Format().date(),
  "status": "cancelled"
}


        (pact
        .given('A scheduled order exists and it is not cancelled already')
        .upon_receiving('a request for cancellation')
        .with_request(method='GET', path='<http://127.0.0.1:5000/kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/cancel>', headers={'Content-Type': 'application/json'},)
        .will_respond_with(200, body=Like(expected)))

        with pact:
            # order = [{"id":"43b2ee86-9c06-45a0-a281-6c136e3bdd7b", "product":"coffee", "size":"big", "quantity":1}]
            # payload = Order(id="43b2ee86-9c06-45a0-a281-6c136e3bdd7b", created="2022-06-23T06:00:24.667545", items=order, status="pending").dict()
            # return Order.cancel(payload)
            # payload = Order(UUID, datetime.now, {"product":"coffee", "size":"large", "quantity":1}, "progress")
            # print("payload is: ",payload)
            response = consumer.cancel()
            # resp = response.json()
            # print("resp: ", response.json())
            # assert resp['status'] == "cancelled"
            # assert response['status'] == "cancelled"
        
        pact.verify()
so i create the order hewre:
Copy code
return Order(id="43b2ee86-9c06-45a0-a281-6c136e3bdd7b", created="2022-06-23T05:59:02.948144", items=order, status="cancelled")
sorry i realised you must been looking at my old code
m
If you're creating it with status cancelled, then it won't match your "if"s in cancel
j
sorry Mike what do you mean?
I just hacked it a bit so that shouldnt be an issue
i editted the line: if self.status == 'progress'
so it also considers cancelled orders
this api is just a sandbox to test out contract testing
m
so i create the order hewre:
Copy code
return Order(id="43b2ee86-9c06-45a0-a281-6c136e3bdd7b", created="2022-06-23T05:59:02.948144", items=order, status="cancelled")
you're creating it as cancelled
j
so even if i create it as pending it is the same
one thing that isnt yet working. Is when i create an order it doenst yet created the order int he kitchen api
i can only cancle the orders that are in the other service
m
In the pact log, where is the actal request and which is the expected it isn’t very clear?
It should say “Registered expected interaction” for the expected when, and the “Received” when it receives a call. In your log above, it never received a call - so that would indicate your code is not sending the request to the mock service (which I think you know now, but writing for posterity)
.with_request(method=‘GET’, path=‘http://127.0.0.1:5000/kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/cancel’, headers={‘Content-Type’: ‘application/json’},)
You’re right btw, you should only have the path here, not the full URI
I can’t see in the code above where it calls the actual mock service. I’ve removed the comented things for readability:
Copy code
(pact
        .given('A scheduled order exists and it is not cancelled already')
        .upon_receiving('a request for cancellation')
        .with_request(method='GET', path='/kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/cancel', headers={'Content-Type': 'application/json'},)
        .will_respond_with(200, body=Like(expected)))

        with pact:
            // this needs to be sending a request to
            // <http://localhost:1234/kitchen/schedule/bc72e917-4af1-4e39-b897-1eda6d006b18/cancel>
            response = consumer.cancel() 
        
        pact.verify()
This method, seems to just be returning a canned object:
Copy code
@pytest.fixture
def consumer() -> Order.cancel:
    # return Order("http://{host}:{port}".format(host=PACT_MOCK_HOST, port=PACT_MOCK_PORT))
    order = [{"id":"43b2ee86-9c06-45a0-a281-6c136e3bdd7b", "product":"coffee", "size":"big", "quantity":1}]
    # payload = Order(id="76ac3eb5-a656-49e3-b973-17e7c992cc5e", created="2022-06-23T03:11:19.415574", items=order, status="created").dict()
    # print("first payload: ",payload)
    # return Order(payload)
    return Order(id="43b2ee86-9c06-45a0-a281-6c136e3bdd7b", created="2022-06-23T05:59:02.948144", items=order, status="cancelled")
Assuming the
consumer.cancel()
calls this code, this would explain why the mock service isn’t seeing any requests, because it’s not making any requests
j
thanks Matt Ill have a oplay with it, makes more sense now
👍 1
Hey Matt! It seems to work now that i added this:
Copy code
with pact:
            requests.get(
                f'<http://localhost:1234/kitchen/schedule/43b2ee86-9c06-45a0-a281-6c136e3bdd7b/cancel>',
                data={'order':  [{"id":"43b2ee86-9c06-45a0-a281-6c136e3bdd7b", "product":"coffee", "size":"big", "quantity":1}]})
            response = consumer.cancel()
            response = response.json()
            
            assert response
            ['status'] == "cancelled"
        
        pact.verify()
I though that: response = consumer.cancel() would make the call to the mock server as i defined the Object which the cancel method is a apart of in the python fixture. I didnt realise that I need to make the request to the mock server basically. I just need to sort out my api bearer token and i should be able to publish that>
I wont be giving up my. day job to be writing these tests fulltime thats for sure. But Ill no doubt have many more questions so thanks so much for your patience. It really is appreciated as even my own questions give myself a headache too LOL
😆 1
m
So I assume this is just for testing purposes, but:
Copy code
# 1
            requests.get(
                f'<http://localhost:1234/kitchen/schedule/43b2ee86-9c06-45a0-a281-6c136e3bdd7b/cancel>',
                data={'order':  [{"id":"43b2ee86-9c06-45a0-a281-6c136e3bdd7b", "product":"coffee", "size":"big", "quantity":1}]})
# 2
            response = consumer.cancel()
# 3
            response = response.json()
1. should really be done as part of 2. 2. You should obviously also add assertions to 3 The whole idea here is that you’re unit testing an API client. Presumably,
consumer
is supposed to be an API client?
m
From earlier in the thread, consumer.cancel() would be able to make the GET but the consumer ('Order') being created is 'intentionally' created with status 'cancelled' so it doesn't go through that logic block
j
Im not sure how to make #1 part of the #2. That Order class has a method called function in it which makes the request to the kitchen service
m
If you can't do that then we're testing the wrong thing and we should instead be testing the
function
call, which is the thing that calls the API. It needs to be configurable to talk to the mock service during the unit test
j
yeahs thats why I orginally called that consumer.cancel()
Im going to have to investigate how to configure that for unit testing - this is an area i dont know too much about
so just so im clear Matt. Presently the consumer.cancel() is calling the real endpoint and not the mock. So I somehow need to configure this Order.cancel to point to the mock server? Does that mean for this contract test it isnt necessary to call the real endpoint?
☝️ 1
m
Bingo
j
and it isnt needed at all in the test to call the real endpoint?
i guess that the provider test will make that request
m
and it isnt needed at all in the test to call the real endpoint? (edited)
correct. You usually would dynamically configure the API client to talk to the Pact mock server instead of the real thing. This way it’s close to being deterministic, much more reliable, and you don’t need to stand up the real test environment to run it (isolation).
👍 1
i guess that the provider test will make that request
Yes. The provider test will re-issue what you put in your consumer test, against the locally running provider in a unit test like environment for the provider side (again, aiming for isolation for deterministic/reliable tests)
j
ok thanks for all your help. Im going to do some investigating about how to set up that pact mock server with this fastapi.