spring-cloud-contract icon indicating copy to clipboard operation
spring-cloud-contract copied to clipboard

OptionalProperty produces wrong output with ClientDslProperty & ServerDslProperty

Open artemy opened this issue 2 years ago • 0 comments

Bug description

optional around ClientDslProperty/ServerDslProperty properties such as anyOf, anyNonBlankString, anyEmail, etc. produce wrong output, like OPTIONAL>>ClientDslProperty{\nclientValue=^FOO$|^BAR$, \n\tserverValue=FOO}<<

Version affected: at least 3.1.3

Sample

Sample repository: https://github.com/artemy/spring-cloud-contract-demo

Example contract
package contracts

import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract

contract {
    request {
        method = GET
        url = url("/api")
        headers {
            contentType = APPLICATION_JSON
            accept = APPLICATION_JSON
        }
        body = body(
            "foo" to value(consumer(optional(anyOf("FOO", "BAR"))), producer("FOO")),
            "bar" to value(consumer(optional(anyNonBlankString)), producer("FOO")),
            "baz" to value(consumer(optional(nonBlank)), producer("BAZ"))
        )
    }
    response {
        status = OK
        headers {
            contentType = APPLICATION_JSON
        }
        body = body(
            "foo" to value(consumer("FOO"), producer(optional(anyOf("FOO", "BAR")))),
            "bar" to value(consumer("FOO"), producer(optional(anyNonBlankString))),
            "baz" to value(consumer("BAZ"), producer(optional(nonBlank)))
        )
    }
}

Expected result:

generated test
package com.github.artemy.springcloudcontractdemo;

import com.github.artemy.springcloudcontractdemo.ContractVerifierBaseIT;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import io.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
import io.restassured.response.ResponseOptions;

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

@SuppressWarnings("rawtypes")
public class ContractVerifierTest extends ContractVerifierBaseIT {

	@Test
	public void validate_example_contract_kotlin() throws Exception {
		// given:
			MockMvcRequestSpecification request = given()
					.header("Content-Type", "application/json")
					.header("Accept", "application/json")
					.body("{\"foo\":\"FOO\",\"bar\":\"FOO\",\"baz\":\"BAZ\"}");

		// when:
			ResponseOptions response = given().spec(request)
					.get("/api");

		// then:
			assertThat(response.statusCode()).isEqualTo(200);
			assertThat(response.header("Content-Type")).matches("application/json.*");

		// and:
			DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
			assertThatJson(parsedJson).field("['foo']").matches("(^FOO$|^BAR$)?");
			assertThatJson(parsedJson).field("['bar']").matches("(^\\s*\\S[\\S\\s]*)?");
			assertThatJson(parsedJson).field("['baz']").matches("(^\\s*\\S[\\S\\s]*)?");
	}

}
generated stub
{
  "id" : "4079dae6-0ce0-4f85-b9bf-35662d19315e",
  "request" : {
    "url" : "/api",
    "method" : "GET",
    "headers" : {
      "Content-Type" : {
        "matches" : "application/json.*"
      },
      "Accept" : {
        "matches" : "application/json.*"
      }
    },
    "bodyPatterns" : [ {
      "matchesJsonPath" : "$[?(@.['foo'] =~ /(^FOO$|^BAR$)?/)]"
    }, {
      "matchesJsonPath" : "$[?(@.['bar'] =~ /(^\\s*\\S[\\S\\s]*)?/)]"
    }, {
      "matchesJsonPath" : "$[?(@.['baz'] =~ /(^\\s*\\S[\\S\\s]*)?/)]"
    } ]
  },
  "response" : {
    "status" : 200,
    "body" : "{\"foo\":\"FOO\",\"bar\":\"FOO\",\"baz\":\"BAZ\"}",
    "headers" : {
      "Content-Type" : "application/json"
    },
    "transformers" : [ "response-template", "spring-cloud-contract" ]
  },
  "uuid" : "4079dae6-0ce0-4f85-b9bf-35662d19315e"
}

Actual result:

generated test
package com.github.artemy.springcloudcontractdemo;

import com.github.artemy.springcloudcontractdemo.ContractVerifierBaseIT;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import io.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
import io.restassured.response.ResponseOptions;

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

@SuppressWarnings("rawtypes")
public class ContractVerifierTest extends ContractVerifierBaseIT {

	@Test
	public void validate_example_contract_kotlin() throws Exception {
		// given:
			MockMvcRequestSpecification request = given()
					.header("Content-Type", "application/json")
					.header("Accept", "application/json")
					.body("{\"foo\":\"FOO\",\"bar\":\"FOO\",\"baz\":\"BAZ\"}");

		// when:
			ResponseOptions response = given().spec(request)
					.get("/api");

		// then:
			assertThat(response.statusCode()).isEqualTo(200);
			assertThat(response.header("Content-Type")).matches("application/json.*");

		// and:
			DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
			assertThatJson(parsedJson).field("['foo']").isEqualTo("OPTIONAL>>ServerDslProperty{\nclientValue=FOO, \n\tserverValue=^FOO$|^BAR$}<<");
			assertThatJson(parsedJson).field("['bar']").isEqualTo("OPTIONAL>>ServerDslProperty{\nclientValue=ESFMJYTTPZOBTXNKOBPA, \n\tserverValue=^\\s*\\S[\\S\\s]*}<<");
			assertThatJson(parsedJson).field("['baz']").matches("(^\\s*\\S[\\S\\s]*)?");
	}

}
generated stub
{
  "id" : "52a4282f-1ec2-46cc-bb8e-665a3b672496",
  "request" : {
    "url" : "/api",
    "method" : "GET",
    "headers" : {
      "Content-Type" : {
        "matches" : "application/json.*"
      },
      "Accept" : {
        "matches" : "application/json.*"
      }
    },
    "bodyPatterns" : [ {
      "matchesJsonPath" : "$[?(@.['foo'] == 'OPTIONAL>>ClientDslProperty{\nclientValue=^FOO$|^BAR$, \n\tserverValue=FOO}<<')]"
    }, {
      "matchesJsonPath" : "$[?(@.['bar'] == 'OPTIONAL>>ClientDslProperty{\nclientValue=^\\s*\\S[\\S\\s]*, \n\tserverValue=NGLOGRZLBZZKSDKSVDRL}<<')]"
    }, {
      "matchesJsonPath" : "$[?(@.['baz'] =~ /(^\\s*\\S[\\S\\s]*)?/)]"
    } ]
  },
  "response" : {
    "status" : 200,
    "body" : "{\"foo\":\"FOO\",\"bar\":\"FOO\",\"baz\":\"BAZ\"}",
    "headers" : {
      "Content-Type" : "application/json"
    },
    "transformers" : [ "response-template", "spring-cloud-contract" ]
  },
  "uuid" : "52a4282f-1ec2-46cc-bb8e-665a3b672496"
}

artemy avatar Aug 07 '22 15:08 artemy