Mercurial > p > roundup > code
comparison test/test_liveserver.py @ 6525:c505c774a94d
Mutiple changes to REST code.
Requesting an invalid attribut via rest/data/class/id/attrib used to
return a 405, it now returns a 400 and a better error message.
/rest/ response scans the registered endpoints rather than using a
hard coded description. So new endpoints added in interfaces.py are
listed.
Fix a number of Allow headers that were listing invalid methods. Also
when invalid method is used, report valid methods in response. Extract
methods from Route list.
Fix Access-Control-Allow-Methods. Add X-Requested-With to
Access-Control-Allow-Headers.
Add decorator openapi_doc to add openapi annotations for the rest
endpoints. Added a couple of examples. Returning this info to a
client is still a work in progress.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Sun, 07 Nov 2021 01:04:43 -0500 |
| parents | 1fc765ef6379 |
| children | 3c8322e3fe25 |
comparison
equal
deleted
inserted
replaced
| 6524:f961dbbc3573 | 6525:c505c774a94d |
|---|---|
| 93 self.assertEqual(f.status_code, 200) | 93 self.assertEqual(f.status_code, 200) |
| 94 self.assertTrue(b'Roundup' in f.content) | 94 self.assertTrue(b'Roundup' in f.content) |
| 95 self.assertTrue(b'Creator' in f.content) | 95 self.assertTrue(b'Creator' in f.content) |
| 96 | 96 |
| 97 | 97 |
| 98 def test_rest_invalid_method_collection(self): | |
| 99 # use basic auth for rest endpoint | |
| 100 f = requests.put(self.url_base() + '/rest/data/user', | |
| 101 auth=('admin', 'sekrit'), | |
| 102 headers = {'content-type': "", | |
| 103 'x-requested-with': "rest"}) | |
| 104 import pdb; pdb.set_trace() | |
| 105 print(f.status_code) | |
| 106 print(f.headers) | |
| 107 print(f.content) | |
| 108 | |
| 109 self.assertEqual(f.status_code, 405) | |
| 110 expected = { 'Access-Control-Allow-Origin': '*', | |
| 111 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', | |
| 112 'Allow': 'DELETE, GET, OPTIONS, POST', | |
| 113 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', | |
| 114 } | |
| 115 | |
| 116 print(f.headers) | |
| 117 # use dict comprehension to remove fields like date, | |
| 118 # content-length etc. from f.headers. | |
| 119 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | |
| 120 | |
| 121 content = json.loads(f.content) | |
| 122 | |
| 123 exp_content = "Method PUT not allowed. Allowed: DELETE, GET, OPTIONS, POST" | |
| 124 self.assertEqual(exp_content, content['error']['msg']) | |
| 125 | |
| 98 def test_http_options(self): | 126 def test_http_options(self): |
| 99 """ options returns an unimplemented error for this case.""" | 127 """ options returns an unimplemented error for this case.""" |
| 100 | 128 |
| 101 # do not send content-type header for options | 129 # do not send content-type header for options |
| 102 f = requests.options(self.url_base() + '/', | 130 f = requests.options(self.url_base() + '/', |
| 112 print(f.status_code) | 140 print(f.status_code) |
| 113 print(f.headers) | 141 print(f.headers) |
| 114 | 142 |
| 115 self.assertEqual(f.status_code, 204) | 143 self.assertEqual(f.status_code, 204) |
| 116 expected = { 'Access-Control-Allow-Origin': '*', | 144 expected = { 'Access-Control-Allow-Origin': '*', |
| 117 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 145 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 118 'Allow': 'OPTIONS, GET', | 146 'Allow': 'OPTIONS, GET', |
| 119 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 147 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 120 } | 148 } |
| 121 | 149 |
| 122 # use dict comprehension to remove fields like date, | 150 # use dict comprehension to remove fields like date, |
| 123 # content-length etc. from f.headers. | 151 # content-length etc. from f.headers. |
| 124 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 152 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 132 print(f.status_code) | 160 print(f.status_code) |
| 133 print(f.headers) | 161 print(f.headers) |
| 134 | 162 |
| 135 self.assertEqual(f.status_code, 204) | 163 self.assertEqual(f.status_code, 204) |
| 136 expected = { 'Access-Control-Allow-Origin': '*', | 164 expected = { 'Access-Control-Allow-Origin': '*', |
| 137 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 165 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 138 'Allow': 'OPTIONS, GET', | 166 'Allow': 'OPTIONS, GET', |
| 139 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 167 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 140 } | 168 } |
| 141 | 169 |
| 142 # use dict comprehension to remove fields like date, | 170 # use dict comprehension to remove fields like date, |
| 143 # content-length etc. from f.headers. | 171 # content-length etc. from f.headers. |
| 144 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 172 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 151 print(f.status_code) | 179 print(f.status_code) |
| 152 print(f.headers) | 180 print(f.headers) |
| 153 | 181 |
| 154 self.assertEqual(f.status_code, 204) | 182 self.assertEqual(f.status_code, 204) |
| 155 expected = { 'Access-Control-Allow-Origin': '*', | 183 expected = { 'Access-Control-Allow-Origin': '*', |
| 156 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 184 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 157 'Allow': 'OPTIONS, GET, POST', | 185 'Allow': 'OPTIONS, GET, POST', |
| 158 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 186 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 159 } | 187 } |
| 160 | 188 |
| 161 # use dict comprehension to remove fields like date, | 189 # use dict comprehension to remove fields like date, |
| 162 # content-length etc. from f.headers. | 190 # content-length etc. from f.headers. |
| 163 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 191 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 171 print(f.status_code) | 199 print(f.status_code) |
| 172 print(f.headers) | 200 print(f.headers) |
| 173 | 201 |
| 174 self.assertEqual(f.status_code, 204) | 202 self.assertEqual(f.status_code, 204) |
| 175 expected = { 'Access-Control-Allow-Origin': '*', | 203 expected = { 'Access-Control-Allow-Origin': '*', |
| 176 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 204 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 177 'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH', | 205 'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH', |
| 178 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 206 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 179 } | 207 } |
| 180 | 208 |
| 181 # use dict comprehension to remove fields like date, | 209 # use dict comprehension to remove fields like date, |
| 182 # content-length etc. from f.headers. | 210 # content-length etc. from f.headers. |
| 183 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 211 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 190 print(f.status_code) | 218 print(f.status_code) |
| 191 print(f.headers) | 219 print(f.headers) |
| 192 | 220 |
| 193 self.assertEqual(f.status_code, 204) | 221 self.assertEqual(f.status_code, 204) |
| 194 expected = { 'Access-Control-Allow-Origin': '*', | 222 expected = { 'Access-Control-Allow-Origin': '*', |
| 195 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 223 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 196 'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH', | 224 'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH', |
| 197 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 225 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 198 } | 226 } |
| 199 | 227 |
| 200 # use dict comprehension to remove fields like date, | 228 # use dict comprehension to remove fields like date, |
| 201 # content-length etc. from f.headers. | 229 # content-length etc. from f.headers. |
| 202 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 230 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 380 print(f.headers) | 408 print(f.headers) |
| 381 | 409 |
| 382 self.assertEqual(f.status_code, 200) | 410 self.assertEqual(f.status_code, 200) |
| 383 expected = { 'Content-Type': 'application/json', | 411 expected = { 'Content-Type': 'application/json', |
| 384 'Access-Control-Allow-Origin': '*', | 412 'Access-Control-Allow-Origin': '*', |
| 385 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 413 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 386 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 414 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 387 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 415 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 388 'Content-Encoding': 'gzip', | 416 'Content-Encoding': 'gzip', |
| 389 'Vary': 'Accept-Encoding', | 417 'Vary': 'Accept-Encoding', |
| 390 } | 418 } |
| 391 | 419 |
| 392 content_str = '''{ "data": { | 420 content_str = '''{ "data": { |
| 425 'Accept-Encoding': 'gzip, foo', | 453 'Accept-Encoding': 'gzip, foo', |
| 426 'Accept': '*/*'}) | 454 'Accept': '*/*'}) |
| 427 print(f.status_code) | 455 print(f.status_code) |
| 428 print(f.headers) | 456 print(f.headers) |
| 429 | 457 |
| 430 # ERROR: attribute error turns into 405, not sure that's right. | |
| 431 # NOTE: not compressed payload too small | 458 # NOTE: not compressed payload too small |
| 432 self.assertEqual(f.status_code, 405) | 459 self.assertEqual(f.status_code, 400) |
| 433 expected = { 'Content-Type': 'application/json', | 460 expected = { 'Content-Type': 'application/json', |
| 434 'Access-Control-Allow-Origin': '*', | 461 'Access-Control-Allow-Origin': '*', |
| 435 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 462 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 436 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 463 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 437 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 464 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 438 } | 465 } |
| 439 | 466 |
| 440 content = { "error": | 467 content = { "error": |
| 441 { | 468 { |
| 442 "status": 405, | 469 "status": 400, |
| 443 "msg": "'foo'" | 470 "msg": "Invalid attribute foo" |
| 444 } | 471 } |
| 445 } | 472 } |
| 446 | 473 |
| 447 json_dict = json.loads(b2s(f.content)) | 474 json_dict = json.loads(b2s(f.content)) |
| 448 self.assertDictEqual(json_dict, content) | 475 self.assertDictEqual(json_dict, content) |
| 507 print(f.headers) | 534 print(f.headers) |
| 508 | 535 |
| 509 self.assertEqual(f.status_code, 200) | 536 self.assertEqual(f.status_code, 200) |
| 510 expected = { 'Content-Type': 'application/json', | 537 expected = { 'Content-Type': 'application/json', |
| 511 'Access-Control-Allow-Origin': '*', | 538 'Access-Control-Allow-Origin': '*', |
| 512 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 539 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 513 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 540 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 514 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 541 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 515 'Content-Encoding': 'br', | 542 'Content-Encoding': 'br', |
| 516 'Vary': 'Accept-Encoding', | 543 'Vary': 'Accept-Encoding', |
| 517 } | 544 } |
| 518 | 545 |
| 519 content_str = '''{ "data": { | 546 content_str = '''{ "data": { |
| 553 auth=('admin', 'sekrit'), | 580 auth=('admin', 'sekrit'), |
| 554 headers = {'Accept-Encoding': 'br, foo', | 581 headers = {'Accept-Encoding': 'br, foo', |
| 555 'Accept': '*/*'}) | 582 'Accept': '*/*'}) |
| 556 print(f.status_code) | 583 print(f.status_code) |
| 557 print(f.headers) | 584 print(f.headers) |
| 558 # ERROR: attribute error turns into 405, not sure that's right. | 585 |
| 559 # Note: not compressed payload too small | 586 # Note: not compressed payload too small |
| 560 self.assertEqual(f.status_code, 405) | 587 self.assertEqual(f.status_code, 400) |
| 561 expected = { 'Content-Type': 'application/json', | 588 expected = { 'Content-Type': 'application/json', |
| 562 'Access-Control-Allow-Origin': '*', | 589 'Access-Control-Allow-Origin': '*', |
| 563 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 590 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 564 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 591 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 565 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 592 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 566 } | 593 } |
| 567 | 594 |
| 568 content = { "error": | 595 content = { "error": |
| 569 { | 596 { |
| 570 "status": 405, | 597 "status": 400, |
| 571 "msg": "'foo'" | 598 "msg": "Invalid attribute foo" |
| 572 } | 599 } |
| 573 } | 600 } |
| 574 json_dict = json.loads(b2s(f.content)) | 601 json_dict = json.loads(b2s(f.content)) |
| 575 | 602 |
| 576 self.assertDictEqual(json_dict, content) | 603 self.assertDictEqual(json_dict, content) |
| 653 print(f.headers) | 680 print(f.headers) |
| 654 | 681 |
| 655 self.assertEqual(f.status_code, 200) | 682 self.assertEqual(f.status_code, 200) |
| 656 expected = { 'Content-Type': 'application/json', | 683 expected = { 'Content-Type': 'application/json', |
| 657 'Access-Control-Allow-Origin': '*', | 684 'Access-Control-Allow-Origin': '*', |
| 658 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 685 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 659 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 686 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 660 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 687 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 661 'Content-Encoding': 'zstd', | 688 'Content-Encoding': 'zstd', |
| 662 'Vary': 'Accept-Encoding', | 689 'Vary': 'Accept-Encoding', |
| 663 } | 690 } |
| 664 | 691 |
| 665 content_str = '''{ "data": { | 692 content_str = '''{ "data": { |
| 701 'Accept-Encoding': 'zstd, foo', | 728 'Accept-Encoding': 'zstd, foo', |
| 702 'Accept': '*/*'}) | 729 'Accept': '*/*'}) |
| 703 print(f.status_code) | 730 print(f.status_code) |
| 704 print(f.headers) | 731 print(f.headers) |
| 705 | 732 |
| 706 # ERROR: attribute error turns into 405, not sure that's right. | |
| 707 # Note: not compressed, payload too small | 733 # Note: not compressed, payload too small |
| 708 self.assertEqual(f.status_code, 405) | 734 self.assertEqual(f.status_code, 400) |
| 709 expected = { 'Content-Type': 'application/json', | 735 expected = { 'Content-Type': 'application/json', |
| 710 'Access-Control-Allow-Origin': '*', | 736 'Access-Control-Allow-Origin': '*', |
| 711 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 737 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 712 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 738 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 713 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 739 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 714 } | 740 } |
| 715 | 741 |
| 716 content = { "error": | 742 content = { "error": |
| 717 { | 743 { |
| 718 "status": 405, | 744 "status": 400, |
| 719 "msg": "'foo'" | 745 "msg": "Invalid attribute foo" |
| 720 } | 746 } |
| 721 } | 747 } |
| 722 | 748 |
| 723 json_dict = json.loads(b2s(f.content)) | 749 json_dict = json.loads(b2s(f.content)) |
| 724 self.assertDictEqual(json_dict, content) | 750 self.assertDictEqual(json_dict, content) |
