pact-foundation / pact-net

.NET version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
https://pact.io
MIT License
842 stars 231 forks source link

Add support for non-JSON request bodies in consumer tests #348

Closed stylesm closed 2 years ago

stylesm commented 2 years ago

We have a couple of API endpoints which accept non-JSON bodies, e.g.

PUT /statuscode HTTP/1.1
Host: x
Content-Type: text/plain
Content-Length: 1

7

Currently with PactNet we cannot write a consumer test for bodies which are not JSON objects as the use of a JSON serializer is hard-coded.

As a workaround with v3 we could create a custom JsonWriter and JsonSerializer and pass the JsonSerializationSettings into the MockService setup, but this feels very hacky. In v4, we can do this with the RequestBuilder, which is nicer that it is scoped better, but still hacky.

The Pact v4 specification appears to support other request body formats, e.g. strings. It mainly discusses encoded strings, and says 'can be any JSON', though doesn't seem to rule out unencoded non-JSON bodies.

Would it be possible to add support for this in the public APIs in PactNet?

In RequestBuilder, we have:

IRequestBuilderV2 IRequestBuilderV2.WithJsonBody(dynamic body)
    => this.WithJsonBody(body);

My suggestion would be exposing something similar but for strings:

IRequestBuilderV2 IRequestBuilderV2.WithStringBody(string body, string contentType)
    => this.server.WithRequestBody(this.interaction, contentType, body);

Though open to different implementation suggestions. Would also be good to have an overload for encoded, but would need matching work on the provider side to handle the encoding, so would suggest that's a separate extension issue.

Happy to pick up the work and tests myself we think it's a good idea to implement.

adamrodger commented 2 years ago

Hi @stylesm, thanks for getting in touch.

At the moment Pact only supports JSON, but as you say the v4 specification looks to extend that to things like XML and gRPC (mostly via plugins I think.

PactNet v4.x supports Pact specification V3 only (it's annoying they don't match, but just one of those things) so at the moment we can't support non-JSON.

If/when the Rust core library supports non-JSON this can be reviewed and reopened, but for now I'll close this issue as that's going to be quite a long way off.

mefellows commented 2 years ago

At the moment Pact only supports JSON

No, that's incorrect.

The Rust core does support non-JSON for both HTTP and Message based interactions, you can even pass it binary data if you wish, it just doesn't have matchers on non-JSON/non-XML bodies. It will encode binary data as base64.

(see e.g. the Golang implementation that supportns binary/multipart bodies: https://github.com/pact-foundation/pact-go/blob/cac3cf0f2829c2b9f059204458958e88200dad63/internal/native/mock_server.go#L599-L637)

The current Ruby core is the same, with the exception that binary payloads aren't supported from memory.

You're right, in that things like gRPC and protobufs will be supported via plugins, to avoid each language binding having to write the protocol specific interfaces each time.