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

`messageBody` mangles json-ish inputs

Open aviv-amdocs opened this issue 3 years ago • 9 comments

I'm trying to use Spring Cloud Contract to test Messaging (Spring Cloud Stream over Kafka). The message I need to send is JSON encoded, and one of its fields is a JSON encoded string, like this:

    {
        "type": "x",
        "payload": "{\"a\": 4}"
    }

Using Spring Cloud Contract Maven Plugin, versions 2.2.6.RELEASE or 3.0.1.

This is the contract file - node body is provided as a String:

import org.springframework.cloud.contract.spec.Contract

Contract.make{
	input {
		messageBody('   {"type": "x","payload": "{\\"a\\": 4}"}')
	}
}

and this is the generated test class (trimmed for focus):

public class Sunny_dayTest extends AsyncBaseClass {
	@Inject ContractVerifierMessaging contractVerifierMessaging;
	@Inject ContractVerifierObjectMapper contractVerifierObjectMapper;

	@Test
	public void validate_notifyChangeUpdate() throws Exception {
		// given:
			ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
					"{\"type\":\"x\",\"payload\":{\"a\":4}}"
						, headers()
			);

		// when:;
		}
	}

The message in the Java file is altered from my input; Here it is printed and formatted:

{
  "type": "x",
  "payload": {
    "a": 4
  }
}

(Note that value of payload is now an object, not a string!)

It appears that the value of messageBody is being recursively parsed and reassembled.

Some other values are also modified:

messageBody(' [][]]') becomes "[]" (leading whitespace removed, as well as all content that can't be parsed as json). And, hilariously, messageBody(file('message.json')) translates into ...create("new String(fileToBytes(this, \"notifyChangeUpdate_request_message.json\")), ...)"

Is there a way to provide an arbitrary string to messageBody and have it used as the message?

aviv-amdocs avatar Mar 18 '21 11:03 aviv-amdocs

I see the problem... maybe try sending it as bytes?

marcingrzejszczak avatar Mar 18 '21 12:03 marcingrzejszczak

messageBody('{"type": "x"}'.getBytes())

generates:

ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
		"[123,34,116,121,112,101,34,58,32,34,120,34,125]"
			, headers()

...which is also not exactly right for me.

aviv-amdocs avatar Mar 18 '21 12:03 aviv-amdocs

no no, fileAsBytes() not .getBytes()

marcingrzejszczak avatar Mar 18 '21 12:03 marcingrzejszczak

Ah, yeah, filesAsBytes() seems to actually work:

messageBody(fileAsBytes('message.json') )

-->

contractVerifierMessaging.create(
		fileToBytes(this, "notifyChangeUpdate_withbug_request_message.json")
	, headers()

thanks :)

aviv-amdocs avatar Mar 18 '21 12:03 aviv-amdocs

Cool, can we close this? :)

marcingrzejszczak avatar Mar 18 '21 12:03 marcingrzejszczak

Are you OK with this behavior? especially the one with file()?

aviv-amdocs avatar Mar 18 '21 12:03 aviv-amdocs

The problem is that you have a JSON in a JSON. This is why we're parsing the JSON as JSON and your JSON in payload is also parsed. It's hard to fix really :grimacing: So I think that if you have the case where you don't want this to be parsed as we do then you should use the approach with fileAsBytes(...).

marcingrzejszczak avatar Mar 18 '21 12:03 marcingrzejszczak

Why would you parse JSON at all?

This can also show up in other strange places:

 {
        "methodName": "numbers",
        "returnValue": "[]int"
    }

will remove int from the returnValue types ([]int is a type in Go-lang).

If the input is a String, why bother manipulating it?

aviv-amdocs avatar Mar 18 '21 12:03 aviv-amdocs

Maybe it's a bug then - I can't do the investigation right now :grimacing:

marcingrzejszczak avatar Mar 18 '21 13:03 marcingrzejszczak

Is this also a problem in version 4.0.x of Spring Cloud Contract? We've changed the messaging contracts to only include the triggeredBy and outputMessage parts.

marcingrzejszczak avatar Apr 26 '23 11:04 marcingrzejszczak

With this commit https://github.com/spring-cloud/spring-cloud-contract/commit/a2444550d546f9f5663074d280475e72ed8cdf1d we're suggesting that whenever you have payload that contains an embedded JSON as part of your json, you should go with the fileAsBytes approach and then you won't have any additional JSON assertions.

marcingrzejszczak avatar Apr 26 '23 12:04 marcingrzejszczak