pact-foundation / pact-python

Python version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
http://pact.io
MIT License
578 stars 137 forks source link

Mapping provider/customer with description #829

Open ramondepieri opened 1 month ago

ramondepieri commented 1 month ago

Have you read the Contributing Guidelines on issues?

Prerequisites

Description

I am trying to create a message contract test, but something is going wrong:

RuntimeError:
       An error was raised while verifying the message. The response body is: {"detail":"No matched handler."}

According to tests, the problem is happening in reason of mapping is being made by provider state instead of description

Reproducible demo

No response

Steps to reproduce

Run the consumer:

# consumer.py
from pact import MessageConsumer, Provider

def run_consumer():
    contract = MessageConsumer(
        "my_contract Consumer"
    ).has_pact_with(
        Provider("my_contract Provider"),
        pact_dir="pacts",
    )

    event_data = {
        "invoice_id": "12345",
        "amount": 100.00,
        "currency": "USD"
    }

    contract.given("Create contract").expects_to_receive(
        "An event of contract").with_content(event_data)

    with contract:
        pass

if __name__ == "__main__":
    run_consumer()

Now run the provider:

# provider.py
from pact import MessageProvider

def process_invoice_event(event):
    invoice_id = event.get("invoice_id")
    amount = event.get("amount")
    currency = event.get("currency")

    # Validação simples
    if not invoice_id or amount <= 0:
        return {"status": "error", "message": "Invalid invoice data"}

    print(f"Processing invoice: ID = {invoice_id}, Amount = {amount} {currency}")
    return event

def run_provider():
    simulated_event = {
        "invoice_id": "12345",
        "amount": 100.00,
        "currency": "USD"
    }

    provider = MessageProvider(
        message_providers={
            "An event of contract": lambda: process_invoice_event(simulated_event),
        },
        provider="my_contract Provider",
        pact_dir="pacts",
        consumer="my_contract Consumer",
    )

    with provider:
        provider.verify()

if __name__ == "__main__":
    run_provider()

This error will happens: RuntimeError: An error was raised while verifying the message. The response body is: {"detail":"No matched handler."}

Now change the message_providers, to use "Create contract" string instead of "An event of contract"

Note that now is working

Expected behavior

The mapping should use the description instead of provider state

Actual behavior

RuntimeError: An error was raised while verifying the message. The response body is: {"detail":"No matched handler."}

Your environment

No response

Self-service

mefellows commented 1 month ago

I think if we do fix it, to preserve backwards compatibility we may need to check (map) both the states and the description, and only error if one is not found.

YOU54F commented 1 month ago

This has always been a long-standing bug in the pact-python message pact implementation. You will need to map providerStates to message handlers in Pact-python at the moment.

It is also present in the v3 pact examples.

The consumer side

https://github.com/pact-foundation/pact-python/blob/34f686ff3fc57d48284092925bd5b5ac0b8507c6/examples/tests/test_02_message_consumer.py#L114

These should be swapped.

        pact.given("a request to read test.txt")
        .expects_to_receive("test.txt exists")

to be

        pact.given("test.txt exists")
        .expects_to_receive("a request to read test.txt")

In the message provider, we are incorrectly using the provider state, instead of the description

https://github.com/pact-foundation/pact-python/blob/34f686ff3fc57d48284092925bd5b5ac0b8507c6/examples/tests/v3/test_03_message_provider.py#L35-L44

in the message provider proxy, that is shown in the examples, it does not provide a mechanism to pass the message description

https://github.com/pact-foundation/pact-python/blob/34f686ff3fc57d48284092925bd5b5ac0b8507c6/examples/tests/v3/provider_server.py#L88

Those examples should be updated in V3 as well, to use the description, and not the provider state, for the handler mappings

JP-Ellis commented 4 weeks ago

I think this definitely needs to be changed in V3, and I'm surprised it accidentally made its way into the V3 examples... I need to check whether this was an oversight while adapting the existing example only, or whether this error exists in the library itself.

As for the situation in V2, I'm hesitant to 'fix' this. As much as it is confusing, this feature has been around for long enough that a fix now would result in breaking changes for a lot of people. I do think more warnings about this behaviour should added to the documentation.

mefellows commented 4 weeks ago

I think one way to fix it with backwards compatibility, would be to lookup both the description (first) and fallback to the state names if no handler is found that matches the description. If it falls back, we could consider printing a warning so it won't be as surprising when they upgrade to V4.