Hello everyone, I am currently trying to create co...
# pact-python
o
Hello everyone, I am currently trying to create contract tests between 2 applications that communicate through message queue using pact broker. The producer is written by python and the consumer is kotlin. I have created consumer contracts on kotlin using
pact-jvm-consumer-java8_2.12
, and the python one using pact-foundation package. I can see the pacts published on my broker, and when I want to verify my producer contract in python, I get an error called "no matched handler". I saw some github discussions about message queue contracts being v3 and python having ruby implementation which supports v2, could this error be about that? I'm also sharing my logs, can provide more info if needed. Many thanks
Copy code
I, [2022-10-18T22:10:20.628161 #97499]  INFO -- : Running example 'Verifying a pact between sms-consumer and sms-provider Given a user was created a create event has matching content'
I, [2022-10-18T22:10:20.629846 #97499]  INFO -- : Sending POST request to path: "/" with headers: {"CONTENT_TYPE"=>"application/json", "HTTP_X_PACT_ORIGINAL_HEADER_NAMES"=>"Content-Type", "X_PACT_PROVIDER_STATES"=>[{"name"=>"a user was created"}]}, see debug logs for body
D, [2022-10-18T22:10:20.629899 #97499] DEBUG -- : body :{"description":"a create event","providerStates":[{"name":"a user was created","params":{}}],"metadata":{"Content-Type":"application/json; charset=UTF-8"}}
I, [2022-10-18T22:10:20.646374 #97499]  INFO -- : Received response with status: 500, headers: {"Date"=>"Tue, 18 Oct 2022 19:10:20 GMT", "Server"=>"uvicorn", "Content-Length"=>"32", "Content-Type"=>"application/json"}, see debug logs for body
D, [2022-10-18T22:10:20.646463 #97499] DEBUG -- : body: {"detail":"No matched handler."}
m
Python supports messages
can you please share your code setup? The log file indicates you haven’t properly mapped the handlers and it couldn’t find the responsible handler for an interaction
o
Sure, I create my contracts on kotlin using this code
Copy code
import au.com.dius.pact.consumer.MessagePactBuilder
import au.com.dius.pact.consumer.MessagePactProviderRule
import au.com.dius.pact.consumer.Pact
import au.com.dius.pact.consumer.PactVerification
import au.com.dius.pact.model.v3.messaging.MessagePact
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.intenseye.notificationapi.model.EmailRequest
import com.intenseye.notificationapi.model.SmsRequest
import io.pactfoundation.consumer.dsl.LambdaDsl.newJsonBody
import org.junit.Rule
import org.junit.Test

class NotificationContractTest {

    @get:Rule
    val pactSmsRule = MessagePactProviderRule("sms-provider", this)
    @get:Rule
    val pactEmailRule = MessagePactProviderRule("email-provider", this)

    @Pact(provider = "sms-provider", consumer = "sms-consumer")
    fun createSmsEvent(builder: MessagePactBuilder): MessagePact {

        val body = newJsonBody { body ->
            body.stringType("to", "<mailto:email@mail.com|email@mail.com>")
            body.stringValue("body", "Alert")
        }.build()

        return builder.given("a user was created")
                .expectsToReceive("a create event")
                .withContent(body)
                .toPact()
    }

    @Pact(provider = "email-provider", consumer = "email-consumer")
    fun createEmailEvent(builder: MessagePactBuilder): MessagePact {

        val body = newJsonBody { body ->
            body.array("to") {
                it.stringValue("<mailto:email@mail.com|email@mail.com>")
            }
            body.stringValue("from", "Alert")
            body.stringValue("senderName", "Alert")
            body.stringValue("subject", "Alert")
            body.stringValue("body", "Alert")
            body.stringValue("templateName", "Alert")
            body.`object`("templateParams") {
                it.stringType("param1", "value1")
                it.stringType("param2", "value2")
            }
            body.array("cc") {
                it.stringValue("<mailto:email@mail.com|email@mail.com>")
            }
            body.array("bcc") {
                it.stringValue("<mailto:email@mail.com|email@mail.com>")
            }
        }.build()

        return builder.given("a user was created")
            .expectsToReceive("a create event")
            .withContent(body)
            .toPact()
    }

    @Test
    @PactVerification("sms-provider", fragment = "createSmsEvent")
    fun canParseCreateSmsEvent() {
        jacksonObjectMapper().readValue(pactSmsRule.message, SmsRequest::class.java)
    }

    @Test
    @PactVerification("email-provider", fragment = "createEmailEvent")
    fun canParseCreateEmailEvent() {
        jacksonObjectMapper().readValue(pactEmailRule.message, EmailRequest::class.java)
    }
}
and publish them with mvn verify:pact publish
and this is the python code
Copy code
def sms_notification_handler():
    return {
        "to": "<mailto:email@mail.com|email@mail.com>",
        "body": "Alert"
    }


def test_verify_success():
    provider = MessageProvider(
        message_providers={
            'a create event': sms_notification_handler
        },
        provider='sms-provider',
        consumer='sms-consumer',
    )
    with provider:
        provider.verify_with_broker(broker_url='<http://10.102.127.5:80>',
                                    log_level='DEBUG',
                                    publish_version='0.0.1',
                                    publish_verification_results=True)
this is the contract generated by kotlin
Copy code
{
  "consumer": {
    "name": "sms-consumer"
  },
  "provider": {
    "name": "sms-provider"
  },
  "messages": [
    {
      "_id": "3a837aa529513ced39204e2ab42f06dd3dc63022",
      "description": "a create event",
      "metaData": {
        "Content-Type": "application/json; charset=UTF-8"
      },
      "contents": {
        "body": "Alert",
        "to": "<mailto:email@mail.com|email@mail.com>"
      },
      "providerStates": [
        {
          "name": "a user was created"
        }
      ],
      "matchingRules": {
        "body": {
          "$.to": {
            "matchers": [
              {
                "match": "type"
              }
            ],
            "combine": "AND"
          }
        }
      }
    }
  ],
  "metadata": {
    "pactSpecification": {
      "version": "3.0.0"
    },
    "pact-jvm": {
      "version": "3.5.21"
    }
  }
}
since python is the message provider for my kotlin consumer, I just need it to compare the in memory content(
Copy code
{
  "to": "<mailto:email@mail.com|email@mail.com>",
  "body": "Alert"
}
) to the one it receives from pact broker, am I thinking wrong? Btw I haven't observed a pact directory generation in my python project, but it might be normal behaviour since I don't need mocking in provider, I just want to verify that my provider model fits the consumer requirement in broker
m
Looks like you’re missing the provider state handler too for the
a user was created
state
o
in consumer contract?
m
in the provider. You have defined states in your consumer test, you need to implement the provider state handler in the provider test
the provider state handler receives a message that says “please get your system into state X” so that scenario Y will work
How you do that is up to to you (seed database, stub code etc.)
o
doesn't this part in the logger mean that I am sending the provider states
Copy code
D, [2022-10-18T22:10:20.629899 #97499] DEBUG -- : body :{"description":"a create event","providerStates":[{"name":"a user was created","params":{}}],"metadata":{"Content-Type":"application/json; charset=UTF-8"}}
m
I believe that is the request being sent from the core framework into Pact Python framework.
Looking at https://github.com/pact-foundation/pact-python/blob/610173491b4cb0030e4e91a600dfd58d03249f26/pact/message_provider.py#L16 it does appear that there is no option to set the provider state handlers though. Is that actually causing issue? Is the logs you shared previously don’t seem to have the rest of the detail
o
yes I had no option to set provider state, I thought the framework would somehow generate them through the first param 'message _provider' 's value
Copy code
message_providers={
            'a create event': sms_notification_handler
        }
these are all the unique log lines that I have in the pact.log, I also have pact-mock-service logs but may be irrelevant
Matt, apparently the key in message providers map ('`a create event`') was supposed to be my provider state,
a user was created
. I found it after debugging
def _match_states(payload):
method in
http_proxy.py
.
now my provider can verify consumer pact successfully
m
That sounds like a bug to me!