pact-foundation / pact-jvm

JVM 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://docs.pact.io
Apache License 2.0
1.08k stars 479 forks source link

MalformedJsonException is thrown is thrown if a response does not have Content-Type header #1013

Open flexfrank opened 4 years ago

flexfrank commented 4 years ago

I test a API that does not have Content-Type header by pact. ProviderClient treats its response body as application/json and failed to parse. I think the should be treated as application/octet-stream by default.

0 - com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 7 path $
com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 7 path $
    at com.google.gson.stream.JsonReader.syntaxError(JsonReader.java:1564)
    at com.google.gson.stream.JsonReader.checkLenient(JsonReader.java:1405)
    at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:543)
    at com.google.gson.stream.JsonReader.peek(JsonReader.java:426)
    at com.google.gson.JsonParser.parseReader(JsonParser.java:61)
    at com.google.gson.JsonParser.parseString(JsonParser.java:47)
    at com.google.gson.JsonParser.parse(JsonParser.java:98)
    at au.com.dius.pact.provider.ResponseComparison$Companion.generateFullDiff(ResponseComparison.kt:91)
    at au.com.dius.pact.provider.ResponseComparison$Companion.access$generateFullDiff(ResponseComparison.kt:85)
    at au.com.dius.pact.provider.ResponseComparison.bodyResult(ResponseComparison.kt:79)
    at au.com.dius.pact.provider.ResponseComparison$Companion.compareResponse(ResponseComparison.kt:124)
    at au.com.dius.pact.provider.ProviderVerifier.verifyRequestResponsePact(ProviderVerifier.kt:476)
    at au.com.dius.pact.provider.junit5.PactVerificationContext.validateTestExecution(PactJUnit5VerificationProvider.kt:96)
    at au.com.dius.pact.provider.junit5.PactVerificationContext.verifyInteraction(PactJUnit5VerificationProvider.kt:77)

https://github.com/DiUS/pact-jvm/blob/master/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/ProviderClient.kt#L363-L367

PabloThiele commented 4 years ago

Adding some support on the discussion: HTTP RFC Item 7.2.1 The problem is that seems to change a previous default behavior can be treated as major change.

uglyog commented 4 years ago

This is a regression, it should treat it as text/plain. Although application/octet-stream is the default in the RFC, we will need to treat it as text to be able to compare it.

uglyog commented 4 years ago

Version 4.0.7 has been released with this change

flexfrank commented 4 years ago

@uglyog In au.com.dius.pact.core.model.Request class, application/json is still default Content-Type. My tests throw AssertionError with 4.0.7 like:

0 - Expected a response type of 'application/json' but the actual type was 'text/plain'
java.lang.AssertionError: 
0 - Expected a response type of 'application/json' but the actual type was 'text/plain'
    at au.com.dius.pact.provider.junit5.PactVerificationContext.verifyInteraction(PactJUnit5VerificationProvider.kt:85)

https://github.com/DiUS/pact-jvm/blob/05ca602/core/model/src/main/kotlin/au/com/dius/pact/core/model/Request.kt#L116

wieslawmlynarski commented 4 years ago

This change 4.0.6 -> 4.0.7 resulted that the tests started to fail. The reason is that in the contract there is no content type. before was default was "application/json" now is text/plain which results in a 415 status code. Do you have an idea how you can go around it?

uglyog commented 4 years ago

4.0.8 released

stardust20 commented 3 years ago

I'm having a very similar issue when the response type produced by the mock controller is not a JSON but a text/csv.

I'm using pact-jvm 4.1.6 version.

java.lang.AssertionError: 
Failures:

1) Request method

    1.1)       com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 5 path $

    at au.com.dius.pact.provider.spring.target.MockMvcTarget.testInteraction(MockMvcTarget.kt:86)
    at au.com.dius.pact.provider.junit.InteractionRunner$interactionBlock$statement$1.evaluate(InteractionRunner.kt:226)
    at au.com.dius.pact.provider.junit.RunStateChanges.evaluate(RunStateChanges.kt:30)
    at au.com.dius.pact.provider.junit.InteractionRunner.run(InteractionRunner.kt:162)
    at au.com.dius.pact.provider.junit.PactRunner.runChild(PactRunner.kt:150)
    at au.com.dius.pact.provider.junit.PactRunner.runChild(PactRunner.kt:56)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)

Any idea how to solve this?

uglyog commented 3 years ago

@stardust20 you need to provide the content type of the body if it is not JSON.

stardust20 commented 3 years ago

I'm sending the accept header, I tried text/plain and text/csv but it doesn't make a difference. The contract in this case looks like the following: Upon receiving:

{
  "method": "GET",
  "path": "/api/v1/url/csv"
  "headers": {
    "Accept": "text/csv"
}

And the response that looks as follows:

{
  "status": 200,
  "headers": {
    "Content-Type": "text/csv"
}
  "body": "NAME,EMAIL\nSome name, email@email.com\nOther name, otheremail@email.com\n"
}

The method in the controller I'm mocking has the following mapping:

@GetMapping("/api/v1/url/csv", produces = ["text/csv"])
stardust20 commented 3 years ago

I think the issue here is that the mock controller is not setting up the response content-type, and that causes the error with pact:

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Disposition:"attachment; filename=csv-file.csv"]
     Content type = null
             Body = NAME,EMAIL\nSome name, email@email.com\nOther name, otheremail@email.com\n

And then the error:

1.1)       com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 5 path $

    at au.com.dius.pact.provider.spring.target.MockMvcTarget.testInteraction(MockMvcTarget.kt:86)
    at au.com.dius.pact.provider.junit.InteractionRunner$interactionBlock$statement$1.evaluate(InteractionRunner.kt:226)
    at au.com.dius.pact.provider.junit.RunStateChanges.evaluate(RunStateChanges.kt:30)
    at au.com.dius.pact.provider.junit.InteractionRunner.run(InteractionRunner.kt:162)
    at au.com.dius.pact.provider.junit.PactRunner.runChild(PactRunner.kt:150)
    at au.com.dius.pact.provider.junit.PactRunner.runChild(PactRunner.kt:56)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
uglyog commented 3 years ago

@stardust20 I can't replicate that error. My test works if the controller sets the content type, and fails if it does not. It looks like Springboot is setting the content type to application/json if the controller does not.

See https://github.com/pact-foundation/pact-jvm/blob/v4.1.x/provider/spring/src/test/java/au/com/dius/pact/provider/spring/BookController.java#L46 and https://github.com/pact-foundation/pact-jvm/blob/v4.1.x/provider/spring/src/test/resources/pacts/readers-contract.json#L82

stardust20 commented 3 years ago

@stardust20 I can't replicate that error. My test works if the controller sets the content type, and fails if it does not. It looks like Springboot is setting the content type to application/json if the controller does not.

See https://github.com/pact-foundation/pact-jvm/blob/v4.1.x/provider/spring/src/test/java/au/com/dius/pact/provider/spring/BookController.java#L46 and https://github.com/pact-foundation/pact-jvm/blob/v4.1.x/provider/spring/src/test/resources/pacts/readers-contract.json#L82

My controller had a very similar configuration to that one, with the produces = {"text/csv"} in the definition but it didn't work. I ended up changing it and adding that part inside the function like response.setHeader("Content-Type"...

I don't understand why it didn't work with the other setup. In any case, thanks for your help!