JSON matcher *any-object* is misinterpreted
Hi there, thanks for your work!
I am using JSON matchers extensively in my application and I realised that in 2 HTTP responses with exactly the same JSON object (within another object), in a test I can match it with *any-object*, but in another test, I have to fully describe it.
In both tests i am using "Then assert body.is(...)"
The failing test tells me:
"matcher any-string (checks if the field is a String) failed for input (...)"
I have tried to match with object, so why the test fails to match with string ?
This wouldn't be so weird if it never worked, but as i said, i works in another test, that tests exactly the same object. Any help would be very much appreciated
Hi @seugnimod,
Thanks for the report!
This looks very weird, would it be possible for you to share some of the body assertions and JSON payloads?
Hi, thanks for the quick answer!
JSONs in the tests
{
"data":
{
"attributes": {
"_kind": "urn:api:personnes",
"numeroPersonne": $clientId,
"adresseCorrespondance" : *any-object*,
"dematerialisation": *any-boolean*,
"securitesSociales": {
"LU": {
"_kind": *any-string*,
"matriculeSocial": *any-string*
}
}
},
"relationships" : {
...
},
}
}
{
"data":
{
"attributes": {
"_kind": "urn:api:personnes",
"numeroPersonne": $clientId,
"adresseCorrespondance" : *any-object*,
"dematerialisation": *any-boolean*,
"securitesSociales": *any-object*
},
"relationships" : {
...
},
}
}
JSON in the request
{
...,
"securitesSociales": {
"LU": {
"_kind": "urn:api:securite-sociale",
"matriculeSocial": "00000000000"
}
},
....
}
I have trimmed a bit because there is a lot of personal data, not sure if this helps at all.
So, In a test that parses correctly, I use the JSON 1 to compare with the response.
The JSON 2, also parses correctly, in another test, with different data, but the part "securitesSociales" is exactly the same.
If in the JSON 1 I try to use "securitesSociales": *any-object* instead, it doesn't work, showing the error above. (matcher any-string (checks if the field is a String) failed for input (...) )
To me it is only plausible that the issue comes from this object in the JSON, because if I describe de object, I no longer have parsing issues.
I also tried moving "securitesSociales" up and down in the JSON, but it doesn't affect the result.
I am using cornichon 0.20.3, with scala 2.11.12
Does this help at all ? Thanks in advance !
Thanks for sharing your data :+1:
Is it possible to share the exact error as well?
yes !
Then assert response body is [8 ms]
{
Object JSON I compare my response with. This body contains "securitesSociales": *any-object*
}
*** FAILED ***
matcher 'any-string' (checks if the field is a String) failed for input '{
"LU" : {
"_kind" : "urn:api:securite-sociale",
"matriculeSocial" : "000000000000"
}
}'
A little bit anonymized :)
Thanks, I think I have an idea :+1:
I have a hunch regarding a possibly broken invariant in the MatcherResolver.
I have created a PR to harden it with https://github.com/agourlay/cornichon/pull/677
You can give it a try via 0.20.4-SNAPSHOT which corresponds to the current master branch plus the aforementioned PR.
You might need to add the SBT resolver for https://oss.sonatype.org.
Give it a try and tell me how it goes!
@seugnimod pinging you gently about testing the snapshot :innocent:
Hello @agourlay , I'm sorry for not having responded. Project priorities didn't let me test it yet, but hey, today or tomorrow i'm supposed to test a bit and Ill keep you informed, thanks ! Also, I had this error while quickly testing the latest version: [error] (integration / IntegrationTest / executeTests) java.lang.UnsupportedClassVersionError: com/github/benmanes/caffeine/cache/Caffeine
We come from v.0.19.3, and I saw something caffeine related in the breaking changes 0.19.7 Compatibility note Java 11 or above is required following the upgrade of caffeine in version 3.0
This is probably the reason, no? Thanks !
No worries, I did not mean to pressure you :+1:
We come from v.0.19.3,
That's quite the upgrade jump you have got there!
Compatibility note
You are correct about the breaking change due to Caffeine. I hope you will be able to migrate.
@seugnimod another gentle ping to know how your JDK migration upgrade is doing :)
FYI I am thinking about merging the fix PR #677 and performing a new release soon anyway as I believe it is an overall good patch.
Hey there @agourlay , sorry for the long wait. Never had the time to check on your solution. Today I tried the 0.20.4 and I can still reproduce the issue. Is there any other thing I can provide you? For info, it is not related with being the last object in the "father object". I moved it to between 2 other items, and still have the issue.
So, i have in my matcher
"securitesSociales": *any-object*,
The response is:
"LU": {
"_kind": "urn:api:referentiel:personnes:view:securite-sociale",
"matriculeSocial": "999999999999"
}
},
And the error i'm having
[info] with error(s):
[info] matcher 'any-string' (checks if the field is a String) failed for input '{
[info] "LU" : {
[info] "_kind" : "urn:api:referentiel:personnes:view:securite-sociale",
[info] "matriculeSocial" : "999999999999"
[info] }
[info] }'
Happy to hear back from you, that bug is really a mystery, very interesting!
The best for me would be able to reproduce locally without any networking involved.
I propose the following trick, you can write in the session your result body and assert it with the usual DSL.
package com.github.agourlay.cornichon.framework.examples
import com.github.agourlay.cornichon.CornichonFeature
import com.github.agourlay.cornichon.http.HttpService.SessionKeys.lastResponseBodyKey
class Repro extends CornichonFeature {
override def feature = Feature("feature bug repro") {
Scenario("Scenario bug repro") {
Given I save((
lastResponseBodyKey,
"""
|{
| "attributes": {
| "_kind": "urn:api:personnes",
| "numeroPersonne": 123,
| "adresseCorrespondance" : "some_addresse_correspondance",
| "dematerialisation": false,
| "securitesSociales": {
| "LU": {
| "_kind": "some_kind",
| "matriculeSocial": "some_matricule_social"
| }
| }
| }
|}
|""".stripMargin))
And I show_last_body
And assert body.is(
"""
{
"attributes": {
"_kind": "urn:api:personnes",
"numeroPersonne": 123,
"adresseCorrespondance" : "some_addresse_correspondance",
"dematerialisation": false,
"securitesSociales": {
"LU": {
"_kind": "some_kind",
"matriculeSocial": "some_matricule_social"
}
}
}
}
""")
And assert body.is(
"""
{
"attributes": {
"_kind": "urn:api:personnes",
"numeroPersonne": 123,
"adresseCorrespondance" : "some_addresse_correspondance",
"dematerialisation": *any-boolean*,
"securitesSociales": *any-object*
}
}
""")
}
}
}
That scenario is green for me.
It would awesome if you could tweak the payloads and assertions in there to reproduce the bug and share it with me. This way you also easily anonymize the fields.
If you cannot reproduce the issue this way, it means the error is more complex.
I am terribly ashamed and a bit angry....
The fault is in my assertion, there is a duplicated key that a colleague of mine added. I still share it with you, reduced it as much as I could to reproduce the issue.
I still think that it is interesting. I was mislead to the "securitesSociales", when actually the issue was with "langueCorrespondance" all along
This reproduces the issue
package personne.independant
import com.github.agourlay.cornichon.CornichonFeature
import com.github.agourlay.cornichon.core.FeatureDef
import com.github.agourlay.cornichon.http.HttpService.SessionKeys.lastResponseBodyKey
class Repro extends CornichonFeature {
override def feature: FeatureDef = Feature("feature bug repro") {
Scenario("Scenario bug repro") {
print_step(s"testing CT view")
Given I save((lastResponseBodyKey, ctStub()))
Then assert body.is(ctView())
}
}
def ctView(): String =
s"""
|{
| "langueCorrespondance":{
| "code":*any-string*,
| "label":*any-string*
| },
| "langueCorrespondance":{
| "code":*any-string*,
| "label":*any-string*
| },
| "_kind":"urn:api:personnes:view",
| "securitesSociales":*any-object*
|}
|""".stripMargin
def ctStub(): String =
s"""
|{
| "_kind":"urn:api:personnes:view",
| "langueCorrespondance":{
| "code":"fr",
| "label":"Français"
| },
| "securitesSociales":{
| "LU":{
| "_kind":"urn:api:personnes:view:securite-sociale",
| "matriculeSocial":"9999999999999"
| }
| }
|}
|""".stripMargin
}
Some thing I found funny however, is if I try this, I am all green, when I assume I shouldn't,
package personne.independant
import com.github.agourlay.cornichon.CornichonFeature
import com.github.agourlay.cornichon.core.FeatureDef
import com.github.agourlay.cornichon.http.HttpService.SessionKeys.lastResponseBodyKey
class Repro extends CornichonFeature {
override def feature: FeatureDef = Feature("feature bug repro") {
Scenario("Scenario bug repro") {
print_step(s"testing CT view")
Given I save((lastResponseBodyKey, ctStub()))
Then assert body.is(ctView())
}
}
def ctView(): String =
s"""
|{
| "langueCorrespondance":{
| "code":*any-string*,
| "label":*any-string*
| },
| "langueCorrespondance":{
| "code":*any-string*,
| "label":*any-string*
| },
| "_kind":"urn:api:personnes:view",
| "securitesSociales":{
| "LU":{
| "_kind":"urn:api:personnes:view:securite-sociale",
| "matriculeSocial":"9999999999999"
| }
| }
|}
|""".stripMargin
def ctStub(): String =
s"""
|{
| "_kind":"urn:api:personnes:view",
| "langueCorrespondance":{
| "code":"fr",
| "label":"Français"
| },
| "securitesSociales":{
| "LU":{
| "_kind":"urn:api:personnes:view:securite-sociale",
| "matriculeSocial":"9999999999999"
| }
| }
|}
|""".stripMargin
}
I thank you very much for your help, and I'm really sory for the time you spent :(
Hey @seugnimod, happy to see some progress here!
I am terribly ashamed and a bit angry....
No need to worry, the outcome of this issue will mostly likely be at least an improvement to the error reporting. There seems to be rough edges regarding duplicate keys in JSON objects.
I want this library to have a really smooth UX which is the opposite of leaving users confused by error messages for days :)
The first snippet your shared is green for me
feature bug repro:
Starting scenario 'Scenario bug repro'
- Scenario bug repro [178 ms]
Scenario : Scenario bug repro
main steps
testing CT view
Given I add value ' [302 μs]
{
"_kind":"urn:api:personnes:view",
"langueCorrespondance":{
"code":"fr",
"label":"Français"
},
"securitesSociales":{
"LU":{
"_kind":"urn:api:personnes:view:securite-sociale",
"matriculeSocial":"9999999999999"
}
}
}
' to session under key 'last-response-body'
Then assert response body is [99 ms]
{
"langueCorrespondance":{
"code":*any-string*,
"label":*any-string*
},
"langueCorrespondance":{
"code":*any-string*,
"label":*any-string*
},
"_kind":"urn:api:personnes:view",
"securitesSociales":*any-object*
}
replay only this scenario with the command:
testOnly *Repro -- "Scenario bug repro" "--seed=1663094617644"
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
Is it supposed to fail? Which version of cornichon are you using there?
The second snippet is also green as you mentioned.
feature bug repro:
Starting scenario 'Scenario bug repro'
- Scenario bug repro [162 ms]
Scenario : Scenario bug repro
main steps
testing CT view
Given I add value ' [300 μs]
{
"_kind":"urn:api:personnes:view",
"langueCorrespondance":{
"code":"fr",
"label":"Français"
},
"securitesSociales":{
"LU":{
"_kind":"urn:api:personnes:view:securite-sociale",
"matriculeSocial":"9999999999999"
}
}
}
' to session under key 'last-response-body'
Then assert response body is [84 ms]
{
"langueCorrespondance":{
"code":*any-string*,
"label":*any-string*
},
"langueCorrespondance":{
"code":*any-string*,
"label":*any-string*
},
"_kind":"urn:api:personnes:view",
"securitesSociales":{
"LU":{
"_kind":"urn:api:personnes:view:securite-sociale",
"matriculeSocial":"9999999999999"
}
}
}
replay only this scenario with the command:
testOnly *Repro -- "Scenario bug repro" "--seed=1663095418097"
Some thing I found funny however, is if I try this, I am all green, when I assume I shouldn't,
I agree that it is surprising to see the test green because the body does not in fact contain a duplicated langueCorrespondance key.
I did a quick investigation and it appears Circe uses the Jawn parser with allowDuplicateKeys
https://github.com/circe/circe/blob/f15be0dfc2706ffc227f99ec00f0e681a5ca467e/modules/jawn/shared/src/main/scala/io/circe/jawn/JawnParser.scala#L54
It could be interesting to add a configuration knob to cornichon to enable users to disallow duplicate keys. The matchers logic also needs some special treatment of duplicate keys.
Hi there !
Is it supposed to fail? Which version of cornichon are you using there?
Yes, it was supposed to fail, with the error I pointed out earlier
*** FAILED ***
matcher 'any-string' (checks if the field is a String) failed for input '{
"LU" : {
"_kind" : "urn:api:personnes:view:securite-sociale",
"matriculeSocial" : "9999999999999"
}
}'
It's red on my side. I am using com.github.agourlay:cornichon-core_2.12:0.20.4:jar com.github.agourlay:cornichon-kafka_2.12:0.20.4:jar com.github.agourlay:cornichon-scalatest_2.12:0.19.3:jar with scala 2.12.10
Can I provide you some more info to help you ?
com.github.agourlay:cornichon-scalatest_2.12:0.19.3:jar
This needs to be 0.20.4 as well, all artifacts are released together.
Hello! I'd like to give you a little update. Excuse-me, about the versions, that's my fault, I had no requirement for cornichon-scalatest and I thought there would be an internal dependance between them if needed. Yes, it is now green for me the first snippet also ;) Thanks for your support !
Happy to hear this!
The mystery is not entirely solved as I was never able to reproduce the issue but the most important is that it works for you now.
To avoid having someone else struggle like we did, I will try improving:
- the error reporting for matcher failures (https://github.com/agourlay/cornichon/commit/52e15a0ea3ef128a50722d5a06136efa0cfb9ede)
- the experience of having JSON duplicate keys via a configuration knob
- the detection of duplicate matchers
Out of curiosity, what is preventing you to upgrade to Scala 2.13? You could try bumping to the freshly released 2.12.17 as well.
Actually, we have a fork of play 2.5 and that wouldn't work for us. I have to push the Core team to get up to date with that, and also with the version of your lib, cause we use currently the 0.19.3, but I want all the recent goodies :)