Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapping provider/customer with description #829

Open
3 of 4 tasks
ramondepieri opened this issue Oct 14, 2024 · 4 comments
Open
3 of 4 tasks

Mapping provider/customer with description #829

ramondepieri opened this issue Oct 14, 2024 · 4 comments

Comments

@ramondepieri
Copy link

Have you read the Contributing Guidelines on issues?

Prerequisites

  • I'm using the latest version of pact-python.
  • I have read the console error message carefully (if applicable).

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

  • I'd be willing to fix this bug myself.
@mefellows
Copy link
Member

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
Copy link
Member

YOU54F commented Oct 22, 2024

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

pact.given("a request to write test.txt")

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

assert CURRENT_STATE is not None, "State is not set"
function_name = responses.get(CURRENT_STATE, {}).get("function_name")
assert function_name is not None, "Function name could not be found"
producer_function = getattr(producer, function_name)
if producer_function.__name__ == "send_write_event":
producer_function("provider_file_name.txt", "Hello, world!")
elif producer_function.__name__ == "send_read_event":
producer_function("provider_file_name.txt")

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

def produce_message() -> flask.Response | tuple[str, int]:

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

@JP-Ellis
Copy link
Contributor

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
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants