reecetech / pactman

Pact management (mocking, generation and verification)
Other
91 stars 37 forks source link

Support message pacts #68

Open richard-reece opened 5 years ago

richard-reece commented 5 years ago

Message pacts contain just the request part of a usual pact, though with renamed keys, as per the spec (note that "metaData" should be spelled "metadata".

An issue with message pacts is that there's no common transport like HTTP, so we need to provide a mechanism that can be more flexible to handle a variety of message system APIs. To that end, two components are proposed

First, a method to verify message pacts, extending the existing pytest pact_verifier fixture: pact_verifier.verify_message(handler=handle, provider_state=provider_state) The handler is responsible for accepting the pact structure (the JSON as a Python dict) and invoking the appropriate provider message handler function with the contents and metadata values.

This would be used like so (example shows both a stomp and AWS sqs implementation):

    sub = Mock()
    handler = NoteMessageHandler(sub)
    def handle(pact):
        handler.on_message(pact['metadata'], pact['contents'])
    pact_verifier.verify_message(handler=handle, provider_state=provider_state)
    sub.ack.assert_called_once()

def test_pact(pact_verifier, monkeypatch, patch):
    delete = monkeypatch(something_sqs, 'delete', Mock())
    def handle(pact):
        sqs_handle_messages([dict(MessageAttributes=pact['metadata'], Body=pact['contents'])])
    pact_verifier.verify_message(handler=handle, provider_state=provider_state)
    delete.assert_called_once()

The second addition is for mocking the consumer's generation of messages to capture and verify against a pact:

send_function_mock.register(
        description="Published credit data",
        providerState="or maybe 'scenario'? not sure about this",
        contents={"foo": Like("bar")},
        metadata={"contentType": "application/json"},
        transform=transform
)

This would be used like so (stomp example shown):

def MockConnection(mocker):
    mock = mocker.patch("stomp.Connection", autospec=True)
    def transform(**kwargs):
        return dict(contents=kwargs['body'], metadata=kwargs['headers'])
    mock.send = pactman.MessageMock()
    yield mock

The MockConnection fixture is then used in tests, like this:

def MockConnection(mocker):
    mock = mocker.patch("stomp.Connection", autospec=True)
    def transform(**kwargs):
        return dict(contents=kwargs['body'], metadata=kwargs['headers'])
    mock.send = pactman.MessageMock()
    yield mock

def test_sending(MockConnection):
    MockConnection.send.register(
        description="Published credit data",
        providerState="or maybe 'scenario'? not sure about this",
        contents={"foo": Like("bar")},
        metadata={"contentType": "application/json"},
        transform=transform
    )
    invoke_code_that_sends_a_message()
richard-reece commented 5 years ago

To be clear, all the above code would appear in the pactman user's test suite. pactman only exposes those things namespaced as pactman. to use in that test suite.

siwater commented 5 years ago

Looks interesting. Any timescale for an implementation?

aorumbayev commented 4 years ago

Any updates on this ?

r1chardj0n3s commented 4 years ago

I have no plans to implement this feature at this time.

gordol commented 4 years ago

so, this library doesn't support v3 of the pact spec, then.

r1chardj0n3s commented 4 years ago

It doesn't support the message queue component of version 3, correct, but it does implement the rest of v3. The issue with message queue pacts is that it's never been more than a theoretical use-case for me, so it's been very difficult to approach in a concrete manner. I spit-balled some ideas at the start of this thread, but I honestly have no idea whether they'd be useful or appropriate.

I welcome contributions from people with active interest in message pacts.

campellcl commented 4 years ago

The issue with message queue pacts is that it's never been more than a theoretical use-case for me, so it's been very difficult to approach in a concrete manner. I spit-balled some ideas at the start of this thread, but I honestly have no idea whether they'd be useful or appropriate.

@r1chardj0n3s I believe they would be useful. Particularly in the Severless context as shown here in the pact-foundation repo. This is definitely no longer purely theoretical, as Serverless and IaaS is on the rise.

I need to get a better grasp of the core lib before I could personally contribute anything of worth. But this feature would definitely be welcome, as we have other devs using pact-foundation's pact-js implementation which does support Message Pacts. I was looking at using Pactman over pact-foundation/pact-python. This would mean I would need to handle the interoperability between the two pact types by modifying the pact file itself in order to use Pactman's standard Pacts in conjunction with pact-foundation/pact-js's Message Pacts.

To be fair, pact-foundation/pact-python doesn't yet support Message Pacts either. But if you beat them to it, it would be just another reason (in a steadily growing list) to prefer Pactman to pact-foundation/pact-python!

campellcl commented 4 years ago

so, this library doesn't support v3 of the pact spec, then.

@r1chardj0n3s Maybe update the README to clarify that what the pact-foundation devs refer to as "Message Pacts" is not yet supported in the Pactman version 3 implementation of the pact-specification?

Although @gordol to be fair, the pact-foundation devs do not yet support v3 in pact-foundation/pact-python either.

But @r1chardj0n3s the pact-foundation/pact-python devs only claim to support version 2 of the pact specification, and do not claim to support version 3 yet. I agree with @gordol that it is a bit of a misnomer to claim support for version 3 without "Message Pacts" given that the version 3 pact specification clearly implies support for message queues (e.g. "Message Pacts").

r1chardj0n3s commented 4 years ago

@campellcl I look forward to seeing a contribution from you! I have no current need for pact at present, and having someone with a direct need working on the code would be great :)