Matching requests based on the partial contents of JSON body.

751 views
Skip to first unread message

Tuomas Hämäläinen

unread,
Jul 26, 2018, 4:28:31 AM7/26/18
to hoverfly
Hello,

I'm trying to figure out how to match partial contents of a JSON payload to choose which response to give with Hoverfly.

First I thought of using jsonpath, but it doesn't seem to support this kind of matching. I tried the following request/response pairs on my simulation.json:

      {
        "request": {
          "path": [
            {
              "matcher": "exact",
              "value": "/foobar"
            }
          ],
          "method": [
            {
              "matcher": "exact",
              "value": "POST"
            }
          ],
          "destination": [
            {
              "matcher": "exact",
              "value": "www.google.com"
            }
          ],
          "scheme": [
            {
              "matcher": "exact",
              "value": "http"
            }
          ],
          "body": [
            {
              "matcher": "jsonpath",
              "value": "$.user.id?(@==1)"
            }
          ]
        },
        "response": {
          "status": 200,
          "body": "{ \"user\" : { \"id\": \"1\", \"name\": \"John Doe\"}}",
          "encodedBody": false,
          "headers": {
            "Hoverfly": [
              "Was-Here"
            ],
            "Content-Type": [
              "application/json;charset=UTF-8"
            ]
          },
          "templated": false
        }
      },
      {
        "request": {
          "path": [
            {
              "matcher": "exact",
              "value": "/foobar"
            }
          ],
          "method": [
            {
              "matcher": "exact",
              "value": "POST"
            }
          ],
          "destination": [
            {
              "matcher": "exact",
              "value": "www.google.com"
            }
          ],
          "scheme": [
            {
              "matcher": "exact",
              "value": "http"
            }
          ],
          "body": [
            {
              "matcher": "jsonpath",
              "value": "$.user.id?(@==1)"
            }
          ]
        },
        "response": {
          "status": 200,
          "body": "{ \"user\" : { \"id\": \"2\", \"name\": \"Jane Doe\"}}",
          "encodedBody": false,
          "headers": {
            "Hoverfly": [
              "Was-Here"
            ],
            "Content-Type": [
              "application/json;charset=UTF-8"
            ]
          },
          "templated": false
        }


(This example is based on instructions given in ticket https://github.com/SpectoLabs/hoverfly/issues/636)

I fire a request towards it:

$ curl --request POST http://www.google.com/foobar --proxy localhost:8500 --data '{ "user" : { "id" : 1 }}'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1780  100  1756  100    24   1756     24  0:00:01 --:--:--  0:00:01 1714kHoverfly Error!

There was an error when matching

Got error: Could not find a match for request, create or record a valid matcher first!

The following request was made, but was not matched by Hoverfly:

{
    "Path": "/foobar",
    "Method": "POST",
    "Destination": "www.google.com",
    "Scheme": "http",
    "Query": {},
    "Body": "{ \"user\" : { \"id\" : 1 }}",
    "Headers": {
        "Accept": [
            "*/*"
        ],
        "Content-Length": [
            "24"
        ],
        "Content-Type": [
            "application/x-www-form-urlencoded"
        ],
        "Proxy-Connection": [
            "Keep-Alive"
        ],
        "User-Agent": [
            "curl/7.53.0"
        ]
    }
}

Whilst Hoverfly has the following state:

{}

The matcher which came closest was:

{
    "path": [
        {
            "matcher": "exact",
            "value": "/foobar"
        }
    ],
    "method": [
        {
            "matcher": "exact",
            "value": "POST"
        }
    ],
    "destination": [
        {
            "matcher": "exact",
            "value": "www.google.com"
        }
    ],
    "scheme": [
        {
            "matcher": "exact",
            "value": "http"
        }
    ],
    "body": [
        {
            "matcher": "jsonpath",
            "value": "$.user.id?(@==2)"
        }
    ]
}

But it did not match on the following fields:

[body]

Which if hit would have given the following response:

{
    "status": 200,
    "body": "{ \"user\" : { \"id\": \"2\", \"name\": \"Jane Doe\"}}",
    "encodedBody": false,
    "headers": {
        "Content-Type": [
            "application/json;charset=UTF-8"
        ],
        "Hoverfly": [
            "Was-Here"
        ]
    },
    "templated": false
}

Then I check the Hoverfly logs:

ERRO[2018-07-26T07:49:20+03:00] Failed to parse json path query {$.user.id?(@==1)}: unrecognized character in action: U+003D '='
ERRO[2018-07-26T07:49:20+03:00] Failed to parse json path query {$.user.id?(@==2)}: unrecognized character in action: U+003D '='
WARN[2018-07-26T07:49:20+03:00] Failed to find matching request from simulation  destination=www.google.com error=No match found method=POST path=/foobar query=map[]
ERRO[2018-07-26T07:49:20+03:00] There was an error when matching              error=Could not find a match for request, create or record a valid matcher first!

So, guess the new engine doesn't support these kinds of queries, even though release notes promise better support.

Using a JSON matcher is highly annoying because it will try to match the exact contents and with a complex request JSON, debugging matching errors is going to be a headache.

Regex matcher works in this simple case, but I'm a bit sceptic about it when a more complex scenario presents itself. After all, the net is full of articles about what an anti-pattern it is to use regex to parse a non-regular language like XML or JSON.

          "body": [
            {
              "matcher": "regex",
              "value": "\"id\"\\s:\\s2"
            }
          ]


Any fancy suggestions on solving the dilemma?

Regards,
Tuomas

P.S. Before you suggest: "just capture the content first, then switch to simulation", I'll say that I can't capture anything from an endpoint that hasn't been built yet. Need to virtualize the interface, so that our team doesn't have to wait until the other team is finished with their work before we can get started.

Tommy Situ

unread,
Jul 27, 2018, 9:55:59 AM7/27/18
to Tuomas Hämäläinen, hoverfly
Hi Tuomas,

We are aware of the shortcoming of JSONPath and XPath supports for Golang, and I’m totally agreed with you, it’s unwieldy to work with.  We are trying to solve JSON/XML body matching with a different approach, which is why we having been upgrading the schema. 

In the next release, you can chain matchers, so that you can use JSONPath to get a JSON node, and use other matcher to compare its value. For example: 
{
  body: [
    {
      matcher: "jsonpath"
      value: “$.user.id"
      doMatch: {
        matcher: “exact”,
        value: “1"
    }
  ]
}

which should be very flexible and meet most of the use cases. 

I will open a GitHub issue later so that you can keep an eye on it, and comment if you have any suggestion.


Best regards, 
Tommy

--
You received this message because you are subscribed to the Google Groups "hoverfly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hoverfly+u...@specto.io.
To post to this group, send email to hove...@specto.io.
To view this discussion on the web visit https://groups.google.com/a/specto.io/d/msgid/hoverfly/a33156ce-07dd-41b5-a2ca-1fe44b7ae8f8%40specto.io.

Reply all
Reply to author
Forward
0 new messages