diff --git a/docs/source/format.rst b/docs/source/format.rst index c12d3b2..de15bbb 100644 --- a/docs/source/format.rst +++ b/docs/source/format.rst @@ -274,7 +274,8 @@ All of these variables may be used in all of the following fields: * ``data`` * ``request_headers`` * ``response_strings`` -* ``response_json_paths`` (on the value side of the key value pair) +* ``response_json_paths`` (in both the key and value, see + :ref:`json path substitution <json-subs>` for more info) * ``response_headers`` (on the value side of the key value pair) * ``response_forbidden_headers`` * ``count`` and ``delay`` fields of ``poll`` diff --git a/docs/source/jsonpath.rst b/docs/source/jsonpath.rst index f65db38..317603b 100644 --- a/docs/source/jsonpath.rst +++ b/docs/source/jsonpath.rst @@ -81,6 +81,31 @@ Examples like this can be found in one of gabbi's `own tests`_. There are more JSONPath examples in :doc:`example` and in the `jsonpath_rw`_ and `jsonpath_rw_ext`_ documentation. +.. _json-subs: + +Substitution +------------ + +:ref:`Substitutions <state-substitution>` can be made in both the +left (query) and right (expected) hand sides of the json path +expression. When subtitutions are used in the query, care must be +taken to ensure proper quoting of the resulting value. For example +if there is a uuid (with hyphens) at ``$RESPONSE['$.id']`` then this +expression may fail:: + + $.nested.structure.$RESPONSE['$.id'].name: foobar + +as it will evaluate to something like:: + + $.nested.structure.ADC8AAFC-D564-40D1-9724-7680D3C010C2.name: foobar + +which may be treated as an arithemtic expression by the json path +parser. The test author should write:: + + $.nested.structure["$RESPONSE['$.id']"].name: foobar + +to quote the result of the substitution. + .. _jsonpath_rw: http://jsonpath-rw.readthedocs.io/en/latest/ .. _jsonpath_rw_ext: https://python-jsonpath-rw-ext.readthedocs.io/en/latest/ .. _own tests: https://github.com/cdent/gabbi/blob/master/gabbi/tests/gabbits_intercept/data.yaml diff --git a/gabbi/handlers/jsonhandler.py b/gabbi/handlers/jsonhandler.py index da20af0..2f85360 100644 --- a/gabbi/handlers/jsonhandler.py +++ b/gabbi/handlers/jsonhandler.py @@ -75,11 +75,8 @@ class JSONHandler(base.ContentHandler): def action(self, test, path, value=None): """Test json_paths against json data.""" - # NOTE: This process has some advantages over other process that - # might come along because the JSON data has already been - # processed (to provided for the magic template replacing). - # Other handlers that want access to data structures will need - # to do their own processing. + # Do template expansion in the left hand side. + path = test.replace_template(path) try: match = self.extract_json_path_value( test.response_data, path) diff --git a/gabbi/tests/gabbits_intercept/json-left-side.yaml b/gabbi/tests/gabbits_intercept/json-left-side.yaml new file mode 100644 index 0000000..6ce342c --- /dev/null +++ b/gabbi/tests/gabbits_intercept/json-left-side.yaml @@ -0,0 +1,37 @@ +defaults: + request_headers: + content-type: application/json + verbose: True + +tests: +- name: left side json one + desc: for reuse on the next test + POST: / + data: + alpha: alpha1 + beta: beta1 + +- name: expand left side + POST: / + data: + alpha1: alpha + beta1: beta + response_json_paths: + $["$RESPONSE['$.alpha']"]: alpha + +- name: expand environ left side + POST: / + data: + alpha1: alpha + beta1: beta + 1: cow + response_json_paths: + $.['$ENVIRON['ONE']']: cow + +- name: set key and value + GET: /jsonator?key=$ENVIRON['ONE']&value=10 + +- name: check key and value + GET: /jsonator?key=$ENVIRON['ONE']&value=10 + response_json_paths: + $.["$ENVIRON['ONE']"]: $RESPONSE['$['1']'] diff --git a/gabbi/tests/simple_wsgi.py b/gabbi/tests/simple_wsgi.py index fabb7e3..b7528ff 100644 --- a/gabbi/tests/simple_wsgi.py +++ b/gabbi/tests/simple_wsgi.py @@ -107,6 +107,11 @@ class SimpleWsgi(object): # fall through if we've ended the loop elif path_info == '/cookie': headers.append(('Set-Cookie', 'session=1234; domain=.example.com')) + elif path_info == '/jsonator': + json_data = json.dumps({query_data['key'][0]: + query_data['value'][0]}) + start_response('200 OK', [('Content-Type', 'application/json')]) + return [json_data.encode('utf-8')] start_response('200 OK', headers)