Coverage for jsonurl_test.py: 100%

308 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-20 21:12 +0000

1import json 

2import string 

3 

4import pytest 

5 

6import jsonurl_py as jsonurl 

7from conftest import ( 

8 assert_load, 

9 assert_load_fail, 

10 assert_roundtrip, 

11 assert_roundtrip_data, 

12) 

13 

14 

15def test_dumps(): 

16 assert jsonurl.dumps(dict(a=1)) == "(a:1)" 

17 assert jsonurl.dumps(dict(a="b c")) == "(a:b+c)" 

18 assert jsonurl.dumps(dict(a="b$c")) == "(a:b%24c)" 

19 

20 

21def test_dump_empty_string(): 

22 assert "''" == jsonurl.dumps("") 

23 assert "(a:'')" == jsonurl.dumps(dict(a="")) 

24 

25 

26def test_percent_caps(): 

27 assert_load("ll", "%6c%6C") 

28 assert_load("jklmno", r"%6A%6B%6C%6D%6E%6F") 

29 

30 

31def test_percent_error(): 

32 assert_load_fail(r"%6") 

33 assert_load_fail(r"%6c%") 

34 assert_load_fail(r"%6c%6") 

35 

36 

37def test_dump_escape_aqf(): 

38 assert "a!!" == jsonurl.dumps("a!", aqf=True) 

39 

40 

41def test_dump_escape_nonaqf(): 

42 assert "a%21" == jsonurl.dumps("a!", aqf=False) 

43 

44 

45def test_dump_null_aqf(): 

46 assert "!null" == jsonurl.dumps("null", aqf=True) 

47 

48 

49def test_load_null_aqf(): 

50 assert "null" == jsonurl.loads("!null", aqf=True) 

51 

52 

53def test_aqf_escape_once(): 

54 assert_load(["!", ""], "!!,!e", aqf=True, implied_list=True) 

55 assert_load(["!", ""], "(!!,!e)", aqf=True, implied_list=False) 

56 assert_load([",", ")"], "!,,!)", aqf=True, implied_list=True) 

57 

58 

59def test_roundtrip_aqf_escapes(): 

60 assert_roundtrip("a!!", "a!", aqf=True) 

61 assert_roundtrip("!!", "!", aqf=True) 

62 

63 

64def test_roundtrip_aqf_escape_paren(): 

65 assert_roundtrip("!(", "(", aqf=True) 

66 

67 

68def test_roundtrip_aqf_structural(): 

69 assert_roundtrip_data( 

70 ["!", "+", "(", ")", ",", ":"], 

71 aqf=True, 

72 implied_list=True, 

73 ) 

74 

75 

76def test_roundtrip_aqf_escape_many(): 

77 assert_roundtrip_data( 

78 ["!", "a!", "!a", "!e", "e!", "!(", "", None, True, "true"], 

79 aqf=True, 

80 implied_list=True, 

81 ) 

82 

83 

84def test_dump_empty_string_aqf(): 

85 assert_roundtrip("(!e:a)", {"": "a"}, aqf=True) 

86 assert_roundtrip("(a:!e)", {"a": ""}, aqf=True) 

87 assert_roundtrip("!e:a", {"": "a"}, aqf=True, implied_dict=True) 

88 assert_roundtrip("a:!e", {"a": ""}, aqf=True, implied_dict=True) 

89 

90 

91def test_dump_numlike_string(): 

92 assert "(a:'123')" == jsonurl.dumps(dict(a="123")) 

93 assert "(a:'1e3')" == jsonurl.dumps(dict(a="1e3")) 

94 assert "(a:'1e-3')" == jsonurl.dumps(dict(a="1e-3")) 

95 assert "(a:%2B123)" == jsonurl.dumps(dict(a="+123")) 

96 

97 

98def test_percent(): 

99 jsonurl._load_percent("%31", 0)[0] == chr(0x11) 

100 jsonurl._load_percent("%41%42", 0)[0] == chr(0x41) + chr(0x42) 

101 

102 

103def test_loads_atoms(): 

104 assert jsonurl.loads("aaa") == "aaa" 

105 assert jsonurl.loads("123") == 123 

106 assert jsonurl.loads("true") == True 

107 assert jsonurl.loads("false") == False 

108 assert jsonurl.loads("null") == None 

109 

110 

111def test_loads_dict(): 

112 assert jsonurl.loads("(a:b)") == dict(a="b") 

113 assert jsonurl.loads("(a:1)") == dict(a=1) 

114 

115 

116def test_loads_dict_many(): 

117 assert jsonurl.loads("(a:1,b:2,c:3)") == dict(a=1, b=2, c=3) 

118 

119 

120def test_loads_list(): 

121 assert jsonurl.loads("(a,b)") == ["a", "b"] 

122 assert jsonurl.loads("(true,false,1,null)") == [True, False, 1, None] 

123 assert jsonurl.loads("(a,(1,2),b)") == ["a", [1, 2], "b"] 

124 

125 

126def test_empty_input(): 

127 with pytest.raises(jsonurl.ParseError): 

128 jsonurl.loads("") 

129 

130 

131def test_unenc(): 

132 assert "a~/*b" == jsonurl.loads("a~/*b") 

133 

134 

135def test_empty_composite(): 

136 assert {} == jsonurl.loads("()") 

137 

138 

139def test_one_item_list(): 

140 assert [1] == jsonurl.loads("(1)") 

141 

142 

143def test_one_item_nested_list(): 

144 assert [[1]] == jsonurl.loads("((1))") 

145 

146 

147def test_number(): 

148 assert 1.2 == jsonurl.loads("1.2") 

149 assert -1e3 == jsonurl.loads("-1e3") 

150 assert 3.2e-5 == jsonurl.loads("3.2e-5") 

151 assert 3.2e5 == jsonurl.loads("3.2e+5") 

152 

153 

154def test_dumps_float(): 

155 assert "1.2" == jsonurl.dumps(1.2) 

156 assert "1000.0" == jsonurl.dumps(1e3) 

157 assert "1000.0" == json.dumps(1e3) 

158 

159 

160def test_error_on_plus_number(): 

161 with pytest.raises(Exception): 

162 json.loads("+123") 

163 assert jsonurl.loads("+123") == " 123" 

164 

165 

166def test_nonumber(): 

167 assert "1" == jsonurl.loads("%31") 

168 

169 

170def test_qstr(): 

171 assert "abc" == jsonurl.loads("'abc'") 

172 

173 

174def test_load_quote_percent(): 

175 assert "'" == jsonurl.loads(r"%27") 

176 assert "''" == jsonurl.loads(r"%27%27") 

177 assert "'true'" == jsonurl.loads(r"%27true%27") 

178 

179 

180def test_save_implied_list(): 

181 assert "a,'1'" == jsonurl.dumps(["a", "1"], implied_list=True) 

182 

183 

184def test_load_implied_list(): 

185 assert ["a", "b"] == jsonurl.loads("a,b", implied_list=True) 

186 assert ["a", {"b": "c"}] == jsonurl.loads("a,(b:c)", implied_list=True) 

187 with pytest.raises(jsonurl.ParseError): 

188 jsonurl.loads("a,b:c,", implied_list=True) 

189 with pytest.raises(jsonurl.ParseError): 

190 jsonurl.loads("a,,", implied_list=True) 

191 

192 

193def test_save_implied_dict(): 

194 assert "a:'1',b:''" == jsonurl.dumps(dict(a="1", b=""), implied_dict=True) 

195 

196 

197def test_load_implied_dict(): 

198 assert dict(a="1", b="") == jsonurl.loads("a:'1',b:''", implied_dict=True) 

199 

200 

201def test_empty_implied_list_save(): 

202 assert "" == jsonurl.dumps([], implied_list=True) 

203 

204 

205def test_empty_implied_dict_save(): 

206 assert "" == jsonurl.dumps({}, implied_dict=True) 

207 

208 

209def test_empty_implied_list_load(): 

210 assert [] == jsonurl.loads("", implied_list=True) 

211 

212 

213def test_empty_implied_dict_load(): 

214 assert {} == jsonurl.loads("", implied_dict=True) 

215 

216 

217def test_unquote_aqf(): 

218 assert "true" == jsonurl._unquote_aqf("true") 

219 assert "true" == jsonurl._unquote_aqf("!true") 

220 assert "1e23" == jsonurl._unquote_aqf("1e!23") 

221 with pytest.raises(jsonurl.ParseError): 

222 jsonurl._unquote_aqf("1!e23") 

223 assert "1e-23" == jsonurl._unquote_aqf("1e!-23") 

224 assert "1e+23" == jsonurl._unquote_aqf("1e!+23") 

225 assert "hi!ho?" == jsonurl._unquote_aqf("hi!!ho?") 

226 with pytest.raises(jsonurl.ParseError): 

227 jsonurl._unquote_aqf("hi!ho?") 

228 

229 

230def test_bool_percent(): 

231 assert True == jsonurl.loads("true", aqf=False) 

232 assert True == jsonurl.loads("true", aqf=True) 

233 assert "true" == jsonurl.loads("%74rue", aqf=False) 

234 assert "true" == jsonurl.loads("%74rue", aqf=True) 

235 assert "!true" == jsonurl.loads("%21%74rue", aqf=False) 

236 assert "!true" == jsonurl.loads("!%74rue", aqf=False) 

237 assert "!true" == jsonurl.loads("%21true", aqf=False) 

238 assert "!true" == jsonurl.loads("!true", aqf=False) 

239 assert "true" == jsonurl.loads("%21%74rue", aqf=True) 

240 assert "true" == jsonurl.loads("!%74rue", aqf=True) 

241 assert "true" == jsonurl.loads("%21true", aqf=True) 

242 assert "true" == jsonurl.loads("!true", aqf=True) 

243 

244 

245def test_more_aqf(): 

246 assert_load("", "!e", aqf=True) 

247 assert_load("true", "!true", aqf=True) 

248 assert_load("n", "!n", aqf=True) 

249 assert_load("n", "%21n", aqf=True) 

250 assert_load("n", "!%6e", aqf=True) 

251 assert_load(")", "!)", aqf=True) 

252 assert_load("true", "%74rue", aqf=True) 

253 assert_load("!true", "!!true", aqf=True) 

254 assert_load("false", "!false", aqf=True) 

255 assert_load(True, "true", aqf=True) 

256 assert_load(False, "false", aqf=True) 

257 assert_load("hi!", "hi!!", aqf=True) 

258 assert_load("hi)", "hi!)", aqf=True) 

259 

260 

261def test_structural_aqf(): 

262 assert_load("(", "!(", aqf=True) 

263 assert_load("(", "!%28", aqf=True) 

264 assert_load("(", "%21(", aqf=True) 

265 assert_load("(", "%21%28", aqf=True) 

266 with pytest.raises(jsonurl.ParseError): 

267 jsonurl.loads("%28%21", aqf=True) 

268 assert_load("(!", "%28%21", aqf=False) 

269 assert_load("(", "%21(", aqf=True) 

270 assert_load("z(", "%7a%21%28", aqf=True) 

271 

272 

273def test_unterminated_qstr(): 

274 assert_load_fail("'ab") 

275 assert_load_fail("a,'ab", implied_list=True) 

276 

277 

278def test_percent_qstr(): 

279 assert_load("a'b", "a%27b") 

280 assert_load("a'b", "'a%27b'") 

281 assert_load("abc", "'ab%63'") 

282 assert_load_fail("'ab%6'") 

283 assert_load_fail("'ab%'") 

284 

285 

286def test_aqf_escape_after_percent(): 

287 assert_load("true", "%74rue", aqf=True) 

288 assert_load("trun", "%74ru!n", aqf=True) 

289 

290 

291def test_aqf_e_invalid_escape(): 

292 assert_load_fail("a!eb", aqf=True) 

293 assert_load_fail("a!e", aqf=True) 

294 assert_load_fail("!ea", aqf=True) 

295 

296 

297def test_plus_in_qstr(): 

298 assert_load("a b", "'a+b'") 

299 

300 

301def test_space_in_qstr(): 

302 assert_load_fail("'a b'") 

303 

304 

305def test_unterminated_dict(): 

306 assert_load_fail("(a:b,") 

307 assert_load_fail("(a:b,c") 

308 assert_load_fail("(a:b,c:") 

309 assert_load_fail("(a:b,c:d") 

310 

311 

312def test_unterminated_dict_implied(): 

313 assert_load_fail("a:b,", implied_dict=True) 

314 assert_load_fail("a:b,c", implied_dict=True) 

315 assert_load_fail("a:b,c:", implied_dict=True) 

316 assert_load_fail("a:b,c,", implied_dict=True) 

317 assert_load_fail("a:b,c:d:", implied_dict=True) 

318 

319 

320def test_unencoded_ascii_digits(): 

321 text = string.ascii_letters + string.digits 

322 assert text == jsonurl.dumps(text) 

323 assert text == jsonurl.loads(text) 

324 

325 

326def test_load_unencoded_special(): 

327 text = "-._~!$*/;?@" 

328 assert text == jsonurl.loads(text) 

329 

330 

331def test_dump_unencoded_special(): 

332 assert "%21%24%2A%2F%3B%3F%40" == jsonurl.dumps("!$*/;?@") 

333 assert "-._~" == jsonurl.dumps("-._~") 

334 assert "%24%2A%2F%3B%3F%40" == jsonurl.dumps("$*/;?@", aqf=True) 

335 assert "%24%2A%2F%3B%3F%40%27" == jsonurl.dumps("$*/;?@'", aqf=True) 

336 

337 

338def test_aqf_single_quote_safe(): 

339 assert "a%27b" == jsonurl.dumps("a'b", aqf=True) 

340 assert "a'b" == jsonurl.dumps("a'b", safe="'", aqf=True) 

341 assert "a'b" == jsonurl.loads("a'b", aqf=True) 

342 

343 

344def test_noaqf_single_quote_safe(): 

345 with pytest.raises(ValueError): 

346 jsonurl.dumps("a'b", safe="'") 

347 assert "a%27b" == jsonurl.dumps("a'b", aqf=False) 

348 

349 

350def test_bad_safe(): 

351 with pytest.raises(ValueError): 

352 jsonurl.dumps("a^b", safe="^") 

353 with pytest.raises(jsonurl.ParseError): 

354 jsonurl.loads("a^b") 

355 

356 

357def test_fail_load_brackets(): 

358 for char in r"[](){}": 

359 assert_load_fail(char) 

360 

361 

362def test_dict_with_list_as_first_value(): 

363 assert_load({"a": [1, 2]}, "(a:(1,2))") 

364 

365 

366def test_nested_list(): 

367 assert_load([[1, 2], 3], "((1,2),3)") 

368 assert_load([1, [2, 3]], "(1,(2,3))") 

369 

370 

371def test_aqf_escape_colon(): 

372 assert_load(":", "!:", aqf=True) 

373 

374 

375def test_aqf_escape_semicolon(): 

376 assert_load_fail("!;", aqf=True) 

377 

378 

379def test_aqf_load_apos(): 

380 assert_load("'ab", "'ab", aqf=True) 

381 assert_load("a'b", "a'b", aqf=True) 

382 assert_load("ab'", "ab'", aqf=True) 

383 

384 

385def test_notaqf_load_apos_mid_fail(): 

386 assert_load_fail("'ab", aqf=False) 

387 assert_load("a'b", "a'b", aqf=False) 

388 assert_load("ab'", "ab'", aqf=False) 

389 

390 

391def test_dump_badvalue(): 

392 import datetime 

393 

394 d = datetime.datetime.now() 

395 with pytest.raises(TypeError): 

396 json.dumps(dict(d=d)) 

397 with pytest.raises(TypeError): 

398 jsonurl.dumps(dict(d=d)) 

399 

400 

401def test_badargs(): 

402 with pytest.raises(ValueError): 

403 jsonurl.dumps("aaa", jsonurl.LoadOpts(), aqf=True) # type: ignore 

404 with pytest.raises(ValueError): 

405 jsonurl.loads("aaa", jsonurl.LoadOpts(), aqf=True) # type: ignore 

406 

407 

408def test_aqf_percent_structural(): 

409 assert_load(["a", "b"], r"%28a%2cb%29", aqf=True) 

410 assert_load({"a": "b"}, r"%28a%3ab%29", aqf=True) 

411 

412 

413def test_aqf_ampersand(): 

414 assert_load("a&b", r"a%26b", aqf=True) 

415 assert_load_fail(r"a&b", aqf=True) 

416 assert_load("a=b", r"a%3db", aqf=True) 

417 assert_load_fail(r"a=b", aqf=True) 

418 

419 

420def test_unterminated_percent_message(): 

421 with pytest.raises(jsonurl.ParseError, match="Unterminated percent at pos 2"): 

422 jsonurl.loads("ab%a") 

423 with pytest.raises(jsonurl.ParseError, match="Unterminated percent at pos 1"): 

424 jsonurl.loads("a%a", aqf=True) 

425 

426 

427def test_invalid_bang_escape_message(): 

428 with pytest.raises(jsonurl.ParseError, match="Invalid !-escaped char 0x61"): 

429 jsonurl.loads("!a", aqf=True) 

430 

431 

432ERROR_STRINGS = [ 

433 "(", 

434 ")", 

435 "{", 

436 "}", 

437 ",", 

438 ":", 

439 "(1", 

440 "(1,", 

441 "(a:", 

442 "(a:b", 

443 "1,", 

444 "()a", 

445 "(1)a", 

446 "(|", 

447 "((1)", 

448 "(1(", 

449 "((1,2,)", 

450 "(1,1", 

451 "(1,a,()", 

452 "(((1,1(", 

453 "(((1))", 

454 "((a:b)", 

455 "(a:b,'')", 

456 "((a:b(", 

457 "(a:b,c)", 

458 "(a:b,c:)", 

459 "(a:b,c:,)", 

460 "(a&b)", 

461 "(a=b)", 

462 "'a=b'", 

463 "'a&b'", 

464 "(a:)", 

465 "(:a)", 

466 "(a,,c)", 

467] 

468 

469 

470@pytest.mark.parametrize("arg", ERROR_STRINGS) 

471def test_errors_strings(arg: str): 

472 with pytest.raises(jsonurl.ParseError): 

473 jsonurl.loads(arg) 

474 

475 

476PARSE_DATA = [ 

477 ["()", {}], 

478 [ 

479 "(true:true,false:false,null:null,empty:(),single:(0),nested:((1)),many:(-1,2.0,3e1,4e-2,5e0))", 

480 { 

481 True: True, 

482 False: False, 

483 None: None, 

484 "empty": {}, 

485 "single": [0], 

486 "nested": [[1]], 

487 "many": [-1, 2.0, 3e1, 4e-2, 5], 

488 }, 

489 ], 

490 ["(1)", [1]], 

491 ["(1,(2))", [1, [2]]], 

492 ["(1,(a:2),3)", [1, {"a": 2}, 3]], 

493 [ 

494 "(age:64,name:(first:Fred))", 

495 { 

496 "age": 64, 

497 "name": {"first": "Fred"}, 

498 }, 

499 ], 

500 ["(null,null)", [None, None]], 

501 ["(a:b,c:d,e:f)", {"a": "b", "c": "d", "e": "f"}], 

502 ["Bob's+house", "Bob's house"], 

503 ["(%26true)", ["&true"]], 

504 ["((%26true))", [["&true"]]], 

505] 

506 

507 

508@pytest.mark.parametrize("arg_out", PARSE_DATA) 

509def test_parse_data(arg_out): 

510 arg, out = arg_out 

511 assert jsonurl.loads(arg) == out 

512 

513 

514def test_dump_safe(): 

515 assert jsonurl.dumps("a'b", aqf=True) == "a%27b" 

516 assert jsonurl.dumps("a^b", aqf=True) == "a%5Eb" 

517 assert jsonurl.dumps("a/b", aqf=True) == "a%2Fb" 

518 assert jsonurl.dumps("a@b", aqf=True) == "a%40b" 

519 assert jsonurl.dumps("a/b", aqf=True, safe="/@") == "a/b" 

520 assert jsonurl.dumps("a@b", aqf=True, safe="@/") == "a@b" 

521 assert jsonurl.dumps("a/b", aqf=False, safe="/@") == "a/b" 

522 assert jsonurl.dumps("a@b", aqf=False, safe="@/") == "a@b" 

523 assert jsonurl.dumps("a-b", aqf=True) == "a-b" 

524 assert jsonurl.dumps("a}{b", aqf=True) == "a%7D%7Bb" 

525 assert jsonurl.dumps("a,b", aqf=True) == "a!,b" 

526 

527 

528def test_distinguish_empty(): 

529 assert jsonurl.loads("(:)", distinguish_empty_list_dict=True) == {} 

530 assert jsonurl.loads("()", distinguish_empty_list_dict=True) == [] 

531 assert jsonurl.loads("()", distinguish_empty_list_dict=False) == {} 

532 with pytest.raises(jsonurl.ParseError): 

533 assert jsonurl.loads("(:)", distinguish_empty_list_dict=False) 

534 

535 

536def test_distinguish_error(): 

537 assert_load_fail("a:(:x)", distinguish_empty_list_dict=True, implied_dict=True) 

538 assert_load_fail("a:(:", distinguish_empty_list_dict=True, implied_dict=True) 

539 assert_load_fail("a:(", distinguish_empty_list_dict=True, implied_dict=True) 

540 assert_load_fail("a:(x", distinguish_empty_list_dict=True, implied_dict=True) 

541 

542 

543def test_distinguish_empty_complex(): 

544 assert_roundtrip( 

545 "a:(:),b:(),c:null", 

546 dict(a={}, b=[], c=None), 

547 distinguish_empty_list_dict=True, 

548 implied_dict=True, 

549 )