1 import re
2 import difflib
3
4 from datadiff.tools import assert_equals
5 import json
6
7 import pytest
8 from collections import OrderedDict
9 from warnings import warn
10 from tlib.base.TestHelper import sort_list, sort_dict
15 """
16 Functions for modifying and validating JSON data
17 """
18
19 @staticmethod
21 """
22 Compares two JSON values\n
23 If parameter validate_order is True, position of keys is taken into account during validation\n
24 If json1 or json2 are strings, it must be a valid JSON string
25
26 @param json1: String or dictionary to compare
27 @type json1: str, dict
28 @param json2: String or dictionary to compare
29 @type json2: str, dict
30 @param ignore_order: If true, order of keys is not validated
31 @type ignore_order: bool
32 @param exclude_keys: Keys to exclude from comparison, in the format 'key2.key3[2].key1'
33 @type exclude_keys: list
34 @raise AssertionError: JSON values doesn't match
35 """
36 if ignore_order:
37 JsonHelper._validate_json_ignoring_order(json1, json2, exclude_keys)
38 else:
39 JsonHelper._validate_json_with_order(json1, json2, exclude_keys)
40
41 @staticmethod
43 """
44 Compares two JSON values, ignoring position of keys\n
45 If json1 or json2 are strings, it must be a valid JSON string
46 @param json1: String or dictionary to compare
47 @type json1: str, dict
48 @param json2: String or dictionary to compare
49 @type json2: str, dict
50 @raise AssertionError: JSON values doesn't match
51 """
52 items = [json1, json2]
53 for i, item in enumerate(items):
54
55 if isinstance(item, basestring):
56 try:
57 item = json.loads(item)
58 except ValueError:
59 pytest.fail("Value doesn't represent a valid JSON string\n%s" % item)
60
61
62 if type(item) is dict:
63
64 item = sort_dict(item)
65 elif type(item) is list:
66
67 item = sort_list(item)
68 else:
69 pytest.fail("Parameter doesn't represent a valid JSON object: %s" % item)
70
71
72 JsonHelper.remove_keys(item, exclude_keys)
73
74 items[i] = item
75
76 assert_equals(items[0], items[1])
77
78 @staticmethod
80 """
81 Compares two JSON values, taking into account key order
82 If json1 or json2 are strings, it must be a valid JSON string
83 @param json1: String to compare
84 @type json1: str
85 @param json2: String to compare
86 @type json2: str
87 @param exclude_keys: Keys to exclude from comparison, in the format 'key2.key3[2].key1'
88 @type exclude_keys: list
89 @raise AssertionError: JSON values doesn't match
90 """
91
92 def object_pairs_hook(values):
93 """
94 Method that stores objects in an OrderedDict so comparison takes into account key order
95 """
96 return OrderedDict(values)
97
98 items = [json1, json2]
99 for i, item in enumerate(items):
100 if not isinstance(item, basestring):
101 pytest.fail("Only strings are allowed when validating key order")
102
103
104
105 try:
106 item = json.loads(item, object_pairs_hook=object_pairs_hook)
107 except ValueError:
108 pytest.fail("Value doesn't represent a valid JSON string\n%s" % item)
109
110
111 JsonHelper.remove_keys(item, exclude_keys)
112
113 items[i] = item
114
115
116 if items[0] != items[1]:
117 json1 = json.dumps(items[0], indent=3).splitlines(True)
118 json2 = json.dumps(items[1], indent=3).splitlines(True)
119 diff = difflib.unified_diff(json1, json2)
120 diff_txt = "".join(diff)
121
122 raise AssertionError(r"JSON values didn't match\nValue1:\n%s\n\nValue2:\n%s\n\nDiff:\n%s""" %
123 ("".join(json1), "".join(json2), diff_txt))
124
125 @staticmethod
127 """
128 Takes a dictionary or a list and removes keys specified by 'keys' parameter
129 @param data: dictionary or OrderedDict that will be modified.
130 Object is replaced in place
131 @type data: dictionary, OrderedDict
132 @param keys: array indicating the path to remove.
133 e.g. ['businessHours.headings[2]', 'businessHours.values.name']
134 @type keys: list
135 """
136 for key in keys:
137 JsonHelper._remove_key(data, key.split('.'), top_level)
138
139 @staticmethod
141 """
142 Takes a dictionary or a list and removes keys specified by 'keys' parameter
143 @param data: dictionary or OrderedDict that will be modified.
144 Object is replaced in place
145 @type data: dictionary, OrderedDict
146 @param key: array indicating the path to remove. e.g. ['businessHours', 'headings[2]']
147 @type key: list
148 """
149 orig_key = None
150
151
152 if type(key) is not list:
153 pytest.fail("Invalid argument key. Was expecting a list")
154
155 if not (type(data) in (dict, list) or isinstance(data, OrderedDict)):
156 pytest.fail("Invalid argument data. Was expecting a dict or OrderedDict")
157
158 if top_level:
159
160 orig_key = list(key)
161
162
163 match = re.search("^\*\[(.*)\]$", key[0])
164 if match:
165 try:
166 key[0] = int(match.group(1))
167 except ValueError:
168 pytest.fail("Index '%s' is not a valid integer" % match.group(2))
169
170
171
172 new_key = list()
173
174 for i, value in enumerate(key):
175 match = re.search("(^.+)\[(.+)\]$", str(value))
176 if match:
177 try:
178 new_key.append(match.group(1))
179 new_key.append(int(match.group(2)))
180 except ValueError:
181 pytest.fail("Index '%s' is not a valid integer" % match.group(2))
182
183 else:
184 new_key.append(value)
185
186 key = list(new_key)
187
188 if type(data) is list:
189
190 if type(key[0]) is int:
191 index = key.pop(0)
192
193 if len(key) == 0:
194
195 data.pop(index)
196 else:
197
198 JsonHelper._remove_key(data[index], key, top_level=False)
199
200 else:
201 pytest.fail("Key '%s' is not valid for the given JSON object" % key)
202 elif type(data) is dict or isinstance(data, OrderedDict):
203
204 for k in data:
205 if k == key[0]:
206 key.pop(0)
207
208 if len(key) == 0:
209
210 del(data[k])
211 else:
212
213 JsonHelper._remove_key(data[k], key, top_level=False)
214
215
216 break
217 else:
218 pytest.fail("Element %s is not allowed in a JSON response" % type(data))
219
220 if len(key) > 0:
221
222
223 warn("Key '%s' does not represent a valid element" % '.'.join(orig_key))
224
225 if top_level:
226
227
228 key = list(orig_key)
229