pact-go icon indicating copy to clipboard operation
pact-go copied to clipboard

Provider verifier not working correctly when consumer uses matchers.Decimal

Open AnhTaFP opened this issue 10 months ago • 2 comments

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.

AnhTaFP avatar Aug 05 '23 14:08 AnhTaFP