pact-foundation / pact-go

Golang version of Pact. Pact is a contract testing framework for HTTP APIs and non-HTTP asynchronous messaging systems.
http://pact.io
MIT License
859 stars 109 forks source link

EachKeyLike matcher generates wrong rules and wrong example #285

Open nsfrias opened 1 year ago

nsfrias commented 1 year ago

Software versions

Expected behaviour

Given a request body like:

{
    "author": "Mark Twain",
    "book": "The Adventures of Tom Sawyer",
    "extraInfo": {
        "genre": "folk"
    }
}

I expect a contract with the following rules for the request body to match (all else being OK):

req.JSONBody(matchers.StructMatcher{
    "book": matchers.Like("The Adventures of Tom Sawyer"),
    "author": matchers.Like("Mark Twain"),
    "extraInfo": matchers.EachKeyLike("genre",
    matchers.Like("folk")),
})

Actual behaviour

Given the previous request body and contract rules I get the following error:

2023/04/05 10:37:28 [INFO] pact validation failed, errors: 
            Expected 'folk' to be equal to '{"genre":"folk"}'
            expected:    "folk"
            actual:      {"genre":"folk"}
            Expected 'folk' to be the same type as '{"genre":"folk"}'
            expected:    "folk"
            actual:      {"genre":"folk"}

Looking at the log I see the pact expected request is also wrong:

2023-04-05T09:37:28.595060Z DEBUG tokio-runtime-worker pact_matching:      body: '{"author":"Mark Twain","book":"The Adventures of Tom Sawyer","extraInfo":"folk"}'

Steps to reproduce

You can run this test to reproduce the issue:

import (
    "net/http"
    "testing"

    "github.com/go-resty/resty/v2"
    "github.com/pact-foundation/pact-go/v2/consumer"
    pactLog "github.com/pact-foundation/pact-go/v2/log"
    "github.com/pact-foundation/pact-go/v2/matchers"
    "github.com/stretchr/testify/assert"
)

func TestMockConsumer(t *testing.T) {
    _ = pactLog.SetLogLevel("DEBUG")

    mockProvider, err := consumer.NewV4Pact(consumer.MockHTTPProviderConfig{
        Consumer: "mock-consumer",
        Provider: "mock-provider",
        Host:     "127.0.0.1",
        Port:     8080,
        TLS:      false,
    })

    if err != nil {
        t.Fatal(err)
    }

    request := map[string]any{
        "book":   "The Adventures of Tom Sawyer",
        "author": "Mark Twain",
        "extraInfo": map[string]string{
            "genre": "folk",
        },
    }

    test := func(config consumer.MockServerConfig) (err error) {
        _, err = resty.New().R().
            SetBody(request).
            SetHeader("content-type", "application/json").
            Post("http://127.0.0.1:8080/books")
        return err
    }

    mockProvider.AddInteraction().
        WithRequest("POST", "/books", func(req *consumer.V4RequestBuilder) {
            req.JSONBody(matchers.StructMatcher{
                "book":      matchers.Like("The Adventures of Tom Sawyer"),
                "author":    matchers.Like("Mark Twain"),
                "extraInfo": matchers.EachKeyLike("genre", matchers.Like("folk")),
            })
        }).
        WillRespondWith(http.StatusOK)

    assert.NoError(t, mockProvider.ExecuteTest(t, test))
}

Relevent log files

eachkeylike_issue.log

nsfrias commented 1 year ago

I also attempted to change the type on the pact-go library (matcher_v3.go) to eachValue as other comments on related issues suggested was the correct approach.

// Object where the key itself is ignored, but the value template must match.
//
// key - Example key to use (which will be ignored)
// template - Example value template to base the comparison on
func EachKeyLike(key string, template interface{}) Matcher {
    return eachKeyLike{
        Specification: models.V3,
        Type:          "eachValue",
        Contents:      template,
    }
}

This is the rule generated

DocPath { path_tokens: [Root, Field("extraInfo")], expr: "$.extraInfo" }: RuleList { rules: [EachValue(MatchingRuleDefinition { value: "{\"pact:matcher:type\":\"type\",\"specification\":\"2.0.0\",\"value\":\"folk\"}", value_type: Unknown, rules: [], generator: None }), Type], rule_logic: And, cascaded: false }

However the test also failed, albeit with a different error:

2023/04/05 11:28:44 [INFO] pact validation failed, errors: 
            Expected 'folk' to be the same type as '{"genre":"folk"}'
            expected:    "folk"
            actual:      {"genre":"folk"}

The example was also wrong:

2023-04-05T10:28:44.196839Z DEBUG tokio-runtime-worker pact_matching:      body: '{"author":"Mark Twain","book":"The Adventures of Tom Sawyer","extraInfo":"folk"}'
mefellows commented 1 year ago

I think it's a matter of changing the value of this attribute on the matcher to eachKey.

If you wanted to attempt a PR and check if it resolves, that would be super helpful!

nsfrias commented 1 year ago

Thanks @mefellows I'll try your suggested fix and create a PR for it.

mefellows commented 1 year ago

Thank you!

github-actions[bot] commented 1 year ago

👋 Hi! The 'smartbear-supported' label has just been added to this issue, which will create an internal tracking ticket in PactFlow's Jira (PACT-1056). We will use this to prioritise and assign a team member to this task. All activity will be public on this ticket. For now, sit tight and we'll update this ticket once we have more information on the next steps.

See our documentation for more information.

mefellows commented 1 year ago

Waiting on upstream clarification: https://github.com/pact-foundation/pact-reference/issues/299