spring-cloud / spring-cloud-contract

Support for Consumer Driven Contracts in Spring
https://cloud.spring.io/spring-cloud-contract
Apache License 2.0
720 stars 440 forks source link

StackOverflow when using file() #864

Closed beltram closed 5 years ago

beltram commented 5 years ago

Bug report

Hi, I faced some strange behaviour in Spring Cloud Contract >=2.1.0.RC3. When writing a request's payload field in a contract from a json file I ran into a StackOverflow error if I'm using a version of SCC above 2.1.0.RC3 (release included). Don't have issue with 2.1.0.RC2. Looks like this

body (
    person: $(c(anyJson), p(file('person.json')))
)
Execution failed for task ':generateContractTests'.
> java.lang.StackOverflowError (no error message)

I've set up a reproducer for you here. Master branch contains issue while "ok" branch is fine (using 2.1.0.RC2) Thank you in advance 😄

marcingrzejszczak commented 5 years ago

I've spent a day analyzing this issue. I tried to write a unit test (but it worked fine) and I remote debugged Gradle plugin. Finally I saw that the answer is right in front of my eyes.

body (
    person: $(c(anyJson), p(file('person.json')))
)

you're using the file() method as a value of an element of the body. We never wanted to support that. You need to set it like this:

body (
    $(c(anyJson), p(file('person.json')))
)

then your generated test will look like this:

package org.springframework.cloud.contract.verifier.tests;

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import io.restassured.module.webtestclient.response.WebTestClientResponse;
import io.restassured.module.webtestclient.specification.WebTestClientRequestSpecification;
import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson;
import static io.restassured.module.webtestclient.RestAssuredWebTestClient.*;
import static org.springframework.cloud.contract.verifier.assertion.SpringCloudContractAssertions.assertThat;
import static org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.*;

public class ContractVerifierTest {

    @Test
    public void validate_hello() throws Exception {
        // given:
            WebTestClientRequestSpecification request = given()
                    .header("Content-Type", "application/json;charset=UTF-8")
                    .body(new String(fileToBytes(this, "hello_request_person.json")));

        // when:
            WebTestClientResponse response = given().spec(request)
                    .post("/api/persons");

        // then:
            assertThat(response.statusCode()).isEqualTo(200);
    }

}
beltram commented 5 years ago

@marcingrzejszczak sorry for the bad you had. My real life usecase is using file to populate request headers and it fails also. I wrote this in reproducer for simplicity sake. I'm using this trick because I'm in a "multi-producer" pattern and I want each producer to be able to slightly modify request headers. This used to work before so I was surprised it does not work anymore. Anyway I'll try to work around it. Thanks again for the time you spent, much appreciated !

marcingrzejszczak commented 5 years ago

@marcingrzejszczak sorry for the bad you had.

hehe, that's my job. It was more about how I had the answer right in front of my eyes ;)

Thanks again for the time you spent, much appreciated !

No problem!

marcingrzejszczak commented 5 years ago

Anyway I'll try to work around it. Thanks again for the time you spent, much appreciated !

BTW try changing to file('person.json') -> file('person.json').asString()

robeatoz commented 5 years ago

BTW try changing to file('person.json') -> file('person.json').asString()

works, thx!

But you have to be careful if your contract looks like this:

body(
    [
        rawPayload: file('test.txt').asString()
    ]
)

and your test.txt contains quotes " or Windows line separators. Assuming your test.txt looks like this:

Content with "quotes" and
Windows
line separators

In that case, the generated test will generate following, invalid JSON string:

MockMvcRequestSpecification request = given()
    .header("Content-Type", "application/json")
    .body("{\"rawPayload\":\"Content with \"quotes\" and\r\\n        Windows\r\\n        line separators\"}");

To fix this you just have to do two things:

body(
    [
        rawPayload: file('test.txt').asString().replace('"', '\\"')
    ]
)

Unfortunately StringEscapeUtils.escapeJson does not help if your test.txt contains line separators, because you don't want to have something like "\n" as a string.

I think it's not a bug because @marcingrzejszczak already mentioned:

you're using the file() method as a value of an element of the body. We never wanted to support that.