fixture_assertions.mojo (8592B)
1 from std.testing import assert_equal, assert_true 2 3 from json import Value, dumps, loads 4 5 from fixture_loader import ( 6 load_fixture_scenario_expected, 7 load_fixture_scenario_request, 8 ) 9 10 11 def load_scenario_request(relative_path: String) raises -> Value: 12 return load_fixture_scenario_request(relative_path) 13 14 15 def load_scenario_request_json(relative_path: String) raises -> String: 16 return _compact_json(load_scenario_request(relative_path)) 17 18 19 def status_request_with_invalid_version_json() raises -> String: 20 var request = load_scenario_request("scenarios/status_ok.json") 21 request.set("version", Value(2)) 22 return _compact_json(request) 23 24 25 def assert_matches_scenario_response( 26 actual: Value, relative_path: String 27 ) raises: 28 var expected = load_fixture_scenario_expected(relative_path) 29 30 if _has_key(expected, "ok"): 31 _assert_json_equal(actual["ok"], expected["ok"]) 32 33 if _has_key(expected, "equals"): 34 var equals = expected["equals"] 35 for path in equals.object_keys(): 36 _assert_json_equal( 37 _require_path(actual, path, "equals"), equals[path] 38 ) 39 40 if _has_key(expected, "contains_all"): 41 var contains_all = expected["contains_all"] 42 for path in contains_all.object_keys(): 43 _assert_contains_all( 44 _require_path(actual, path, "contains_all"), 45 contains_all[path], 46 ) 47 48 if _has_key(expected, "present_paths"): 49 for path in expected["present_paths"].array_items(): 50 assert_true( 51 _path_exists(actual, path.string_value()), 52 "expected present path '" + path.string_value() + "'", 53 ) 54 55 if _has_key(expected, "absent_paths"): 56 for path in expected["absent_paths"].array_items(): 57 assert_true( 58 not _path_exists(actual, path.string_value()), 59 "expected absent path '" + path.string_value() + "'", 60 ) 61 62 if _has_key(expected, "error_code"): 63 _assert_json_equal(actual["error"]["code"], expected["error_code"]) 64 65 if _has_key(expected, "message_contains"): 66 assert_true( 67 actual["error"]["message"].string_value().find( 68 expected["message_contains"].string_value() 69 ) 70 >= 0 71 ) 72 73 74 def _lookup_path(value: Value, dotted_path: String) raises -> Value: 75 var current = value.copy() 76 for token in dotted_path.split("."): 77 var token_string = String(token) 78 if current.is_array(): 79 var items = current.array_items() 80 current = items[Int(token_string)].copy() 81 else: 82 current = loads(current.get(token_string)) 83 return current^ 84 85 86 def _require_path( 87 value: Value, dotted_path: String, section: String 88 ) raises -> Value: 89 try: 90 return _lookup_path(value, dotted_path) 91 except: 92 raise Error( 93 "missing " 94 + section 95 + " path '" 96 + dotted_path 97 + "' in actual response" 98 ) 99 100 101 def _path_exists(value: Value, dotted_path: String) -> Bool: 102 try: 103 _ = _lookup_path(value, dotted_path) 104 return True 105 except: 106 return False 107 108 109 def _compact_json(value: Value) raises -> String: 110 if value.is_null() or value.is_bool() or value.is_int() or value.is_float(): 111 return dumps(value) 112 113 if value.is_string(): 114 return dumps(Value(value.string_value())) 115 116 if value.is_array(): 117 var result = String("[") 118 var items = value.array_items() 119 for index in range(len(items)): 120 if index > 0: 121 result += "," 122 result += _compact_json(items[index]) 123 result += "]" 124 return result^ 125 126 if value.is_object(): 127 var result = String("{") 128 var first = True 129 for key in value.object_keys(): 130 if not first: 131 result += "," 132 first = False 133 result += dumps(Value(String(key))) 134 result += ":" 135 result += _compact_json(value[key]) 136 result += "}" 137 return result^ 138 139 return dumps(value) 140 141 142 def _assert_contains_all(actual: Value, expected_subset: Value) raises: 143 if expected_subset.is_array(): 144 assert_true(actual.is_array()) 145 var actual_items = actual.array_items() 146 for expected_item in expected_subset.array_items(): 147 var found = False 148 for actual_item in actual_items: 149 if _json_values_equal(actual_item, expected_item): 150 found = True 151 break 152 assert_true( 153 found, 154 "expected array item missing: " + dumps(expected_item), 155 ) 156 return 157 158 if expected_subset.is_object(): 159 assert_true(actual.is_object()) 160 for key in expected_subset.object_keys(): 161 assert_true( 162 _has_key(actual, key), 163 "expected object key '" + key + "'", 164 ) 165 _assert_contains_all(actual[key], expected_subset[key]) 166 return 167 168 _assert_json_equal(actual, expected_subset) 169 170 171 def _json_values_equal(lhs: Value, rhs: Value) raises -> Bool: 172 if lhs.is_null() or rhs.is_null(): 173 return lhs.is_null() and rhs.is_null() 174 175 if lhs.is_bool() or rhs.is_bool(): 176 return ( 177 lhs.is_bool() 178 and rhs.is_bool() 179 and lhs.bool_value() == rhs.bool_value() 180 ) 181 182 if lhs.is_int() or rhs.is_int(): 183 return ( 184 lhs.is_int() 185 and rhs.is_int() 186 and lhs.int_value() == rhs.int_value() 187 ) 188 189 if lhs.is_float() or rhs.is_float(): 190 return ( 191 lhs.is_float() 192 and rhs.is_float() 193 and lhs.float_value() == rhs.float_value() 194 ) 195 196 if lhs.is_string() or rhs.is_string(): 197 return ( 198 lhs.is_string() 199 and rhs.is_string() 200 and lhs.string_value() == rhs.string_value() 201 ) 202 203 if lhs.is_array() or rhs.is_array(): 204 if not lhs.is_array() or not rhs.is_array(): 205 return False 206 var lhs_items = lhs.array_items() 207 var rhs_items = rhs.array_items() 208 if len(lhs_items) != len(rhs_items): 209 return False 210 for i in range(len(lhs_items)): 211 if not _json_values_equal(lhs_items[i], rhs_items[i]): 212 return False 213 return True 214 215 if lhs.is_object() or rhs.is_object(): 216 if not lhs.is_object() or not rhs.is_object(): 217 return False 218 var rhs_keys = rhs.object_keys() 219 if len(lhs.object_keys()) != len(rhs_keys): 220 return False 221 for key in rhs_keys: 222 if not _has_key(lhs, key): 223 return False 224 if not _json_values_equal(lhs[key], rhs[key]): 225 return False 226 return True 227 228 return dumps(lhs) == dumps(rhs) 229 230 231 def _assert_json_equal(actual: Value, expected: Value) raises: 232 if expected.is_null(): 233 assert_true(actual.is_null()) 234 return 235 236 if expected.is_bool(): 237 assert_true(actual.is_bool()) 238 assert_equal(actual.bool_value(), expected.bool_value()) 239 return 240 241 if expected.is_int(): 242 assert_true(actual.is_int()) 243 assert_equal(Int(actual.int_value()), Int(expected.int_value())) 244 return 245 246 if expected.is_float(): 247 assert_true(actual.is_float()) 248 assert_equal(actual.float_value(), expected.float_value()) 249 return 250 251 if expected.is_string(): 252 assert_true(actual.is_string()) 253 assert_equal(actual.string_value(), expected.string_value()) 254 return 255 256 if expected.is_array(): 257 assert_true(actual.is_array()) 258 var actual_items = actual.array_items() 259 var expected_items = expected.array_items() 260 assert_equal(len(actual_items), len(expected_items)) 261 for i in range(len(expected_items)): 262 _assert_json_equal(actual_items[i], expected_items[i]) 263 return 264 265 if expected.is_object(): 266 assert_true(actual.is_object()) 267 var expected_keys = expected.object_keys() 268 assert_equal(len(actual.object_keys()), len(expected_keys)) 269 for key in expected_keys: 270 assert_true(_has_key(actual, key)) 271 _assert_json_equal(actual[key], expected[key]) 272 return 273 274 assert_equal(dumps(actual), dumps(expected)) 275 276 277 def _has_key(value: Value, key: String) -> Bool: 278 for candidate in value.object_keys(): 279 if candidate == key: 280 return True 281 return False