Skip to content

Provider verifier not working correctly when consumer uses matchers.Decimal  #329

Description

@AnhTaFP

Software versions

  • OS: e.g. Mac OS Monterey 12.6.8
  • Consumer Pact library: pact-go v2.0.1
  • Provider Pact library: pact-go v2.0.1
  • Golang Version: Go v1.20
  • Golang environment: Provide output of go env
GO111MODULE=""
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/some.user/Library/Caches/go-build"
GOENV="/Users/some.user/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/some.user/go/pkg/mod"
GOOS="darwin"
GOPATH="/Users/some.user/go"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/Users/some.user/go/go1.20.6"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/Users/some.user/go/go1.20.6/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.20.6"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/03/m_znf89s593bpkdgwytb9tzjdh18pw/T/go-build578168331=/tmp/go-build -gno-record-gcc-switches -fno-common"

Expected behaviour

5.0 should be a valid decimal value when running provider verifier

Actual behaviour

5.0 is not considered a valid decimal value when running provider verifier

Steps to reproduce

  • Create package consumer and its test.
// this is consumer.go
package consumer

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

type ProductConsumer struct {
	host string
}

func NewProductConsumer(host string) *ProductConsumer {
	return &ProductConsumer{
		host: host,
	}
}

func (c *ProductConsumer) GetProduct(id int) (*Product, error) {
	url := fmt.Sprintf("%s/products/%d", c.host, id)
	resp, err := http.DefaultClient.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	b, _ := io.ReadAll(resp.Body)

	var p Product
	_ = json.Unmarshal(b, &p)

	return &p, nil
}

type Product struct {
	ID    int     `json:"id"`
	Price float64 `json:"price"`
}
// this is consumer_test.go
package consumer 

import (
	"fmt"
	"testing"

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

func TestProductConsumer(t *testing.T) {
	mockProvider, err := consumer.NewV4Pact(consumer.MockHTTPProviderConfig{
		Consumer: "product-consumer",
		Provider: "product-provider",
		Port:     8089,
	})

	err = mockProvider.
		AddInteraction().
		Given("product #1 exists").
		UponReceiving("a request to get product #1").
		WithRequest("GET", "/products/1").
		WillRespondWith(200, func(b *consumer.V4ResponseBuilder) {
			b.JSONBody(matchers.Map{
				"id":    matchers.Integer(1),
				"price": matchers.Decimal(5.0),
			})
		}).
		ExecuteTest(t, func(config consumer.MockServerConfig) error {
			c := NewProductConsumer(fmt.Sprintf("http://%s:%d", config.Host, config.Port))
			p, err := c.GetProduct(1)

			assert.NoError(t, err)
			assert.Equal(t, 1, p.ID)

			return nil
		})

	assert.NoError(t, err)
}
  • Run the test, get the output of the pact
{
  "consumer": {
    "name": "product-consumer"
  },
  "interactions": [
    {
      "description": "a request to get product #1",
      "pending": false,
      "providerStates": [
        {
          "name": "product #1 exists"
        }
      ],
      "request": {
        "method": "GET",
        "path": "/products/1"
      },
      "response": {
        "body": {
          "content": {
            "id": 1,
            "price": 5
          },
          "contentType": "application/json",
          "encoded": false
        },
        "headers": {
          "Content-Type": [
            "application/json"
          ]
        },
        "matchingRules": {
          "body": {
            "$.id": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "integer"
                }
              ]
            },
            "$.price": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "decimal"
                }
              ]
            }
          }
        },
        "status": 200
      },
      "transport": "http",
      "type": "Synchronous/HTTP"
    }
  ],
  "metadata": {
    "pactRust": {
      "ffi": "0.4.5",
      "mockserver": "1.1.1",
      "models": "1.1.2"
    },
    "pactSpecification": {
      "version": "4.0"
    }
  },
  "provider": {
    "name": "product-provider"
  }
}
  • Create a provider test
package productprovider

import (
	"encoding/json"
	"net/http"
	"testing"

	"github.com/pact-foundation/pact-go/v2/models"
	"github.com/pact-foundation/pact-go/v2/provider"
	"github.com/stretchr/testify/assert"
)

func startServer() {
	type product struct {
		ID    int     `json:"id"`
		Price float64 `json:"price"`
	}

	http.HandleFunc("/products/1", func(writer http.ResponseWriter, request *http.Request) {
		d := product{
			ID:    1,
			Price: 5.0,
		}
		b, _ := json.Marshal(d)

		writer.Header().Set("Content-Type", "application/json")
		writer.Write(b)
	})

	http.ListenAndServe("localhost:8080", nil)
}

func TestProductProvider(t *testing.T) {
	go startServer()

	v := provider.NewVerifier()

	err := v.VerifyProvider(t, provider.VerifyRequest{
		ProviderBaseURL: "http://localhost:8080",
		Provider:        "product-provider",
		ProviderVersion: "product-provider-v1.0",
		PactDirs:        []string{"/path/to/consumer/pacts"},
		StateHandlers: models.StateHandlers{
			"product #1 exists": func(setup bool, state models.ProviderState) (models.ProviderStateResponse, error) {
				return models.ProviderStateResponse{}, nil
			},
		},
	})

	assert.NoError(t, err)
}
  • Run the provider test, it fails with this log
=== RUN   TestProductProvider
2023-08-05T14:11:11.107350Z  INFO ThreadId(11) pact_verifier: Running setup provider state change handler 'product #1 exists' for 'a request to get product #1'
2023/08/05 21:11:11 [INFO] executing state handler middleware
2023-08-05T14:11:11.287108Z  INFO ThreadId(11) pact_verifier: Running provider verification for 'a request to get product #1'
2023-08-05T14:11:11.287166Z  INFO ThreadId(11) pact_verifier::provider_client: Sending request to provider at http://localhost:52872/
2023-08-05T14:11:11.287168Z  INFO ThreadId(11) pact_verifier::provider_client: Sending request HTTP Request ( method: GET, path: /products/1, query: None, headers: None, body: Missing )
2023-08-05T14:11:11.288802Z  INFO ThreadId(11) pact_verifier::provider_client: Received response: HTTP Response ( status: 200, headers: Some({"content-length": ["18"], "content-type": ["application/json"], "date": ["Sat, 05 Aug 2023 14:11:11 GMT"]}), body: Present(18 bytes, application/json) )
2023-08-05T14:11:11.288821Z  INFO ThreadId(11) pact_matching: comparing to expected response: HTTP Response ( status: 200, headers: Some({"Content-Type": ["application/json"]}), body: Present(18 bytes, application/json) )
2023-08-05T14:11:11.290069Z  INFO ThreadId(11) pact_verifier: Running teardown provider state change handler 'product #1 exists' for 'a request to get product #1'
2023/08/05 21:11:11 [INFO] executing state handler middleware

Verifying a pact between product-consumer and product-provider

  a request to get product #1 (0s loading, 522ms verification)
     Given product #1 exists
    returns a response which
      has status code 200 (OK)
      includes headers
        "Content-Type" with value "application/json" (OK)
      has a matching body (FAILED)


Failures:

1) Verifying a pact between product-consumer and product-provider Given product #1 exists - a request to get product #1
    1.1) has a matching body
           $.price -> Expected '5' to be a decimal value


There were 1 pact failures

    productprovider_test.go:52: 
        	Error Trace:	/Users/some.user/path/to/productprovider/productprovider_test.go:52
        	Error:      	Received unexpected error:
        	            	the verifier failed to successfully verify the pacts, this indicates an issue with the provider API
        	Test:       	TestProductProvider
--- FAIL: TestProductProvider (0.58s)
=== RUN   TestProductProvider/Provider_pact_verification
    verifier.go:184: the verifier failed to successfully verify the pacts, this indicates an issue with the provider API
    --- FAIL: TestProductProvider/Provider_pact_verification (0.00s)


FAIL

Process finished with the exit code 1

Relevent log files

N/A

Please note that if I change the dummy value in provider server from 5.0 to 5.1 then the provider verifier test works.

Metadata

Metadata

Assignees

No one assigned

    Labels

    awaiting feedbackAwaiting Feedback from OPtriageThis issue is yet to be triaged by a maintainer

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    New Issue

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions