Mercurial > p > roundup > code
comparison test/rest_common.py @ 5655:207e0f5d551c
Merge in non-conflicting changes from ba67e397f063
including workaround for:
https://bugs.python.org/issue27777
1) cgi/client.py: override cgi.FieldStorage's make_file so that file
is always created in binary/byte mode. This means that json (and
xml) are bytes not strings.
2) rest.py: try harder to find dicttoxml in roundup directory or on
sys.path. This just worked under python 2 but python 3 only
searches sys.path by default and does not search relative like
python 2.
3) test/rest_common.py: workaround for issue27777
Also removed an unneeded case insensitive dict implementation.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Mon, 18 Mar 2019 21:42:33 -0400 |
| parents | a02ef29b4242 ba67e397f063 |
| children | d26d2590cd8c |
comparison
equal
deleted
inserted
replaced
| 5654:5cba4fdfe818 | 5655:207e0f5d551c |
|---|---|
| 83 raise | 83 raise |
| 84 | 84 |
| 85 def get_header (self, header, not_found=None): | 85 def get_header (self, header, not_found=None): |
| 86 try: | 86 try: |
| 87 return self.headers[header.lower()] | 87 return self.headers[header.lower()] |
| 88 except (AttributeError, KeyError): | 88 except (AttributeError, KeyError, TypeError): |
| 89 return not_found | 89 return not_found |
| 90 | 90 |
| 91 def testGet(self): | 91 def testGet(self): |
| 92 """ | 92 """ |
| 93 Retrieve all three users | 93 Retrieve all three users |
| 350 Both will be checked if availble. If either one | 350 Both will be checked if availble. If either one |
| 351 fails, the etag check will fail. | 351 fails, the etag check will fail. |
| 352 | 352 |
| 353 Run over header only, etag in form only, both, | 353 Run over header only, etag in form only, both, |
| 354 each one broke and no etag. Use the put command | 354 each one broke and no etag. Use the put command |
| 355 to triger the etag checking code. | 355 to trigger the etag checking code. |
| 356 ''' | 356 ''' |
| 357 for mode in ('header', 'etag', 'both', | 357 for mode in ('header', 'etag', 'both', |
| 358 'brokenheader', 'brokenetag', 'none'): | 358 'brokenheader', 'brokenetag', 'none'): |
| 359 try: | 359 try: |
| 360 # clean up any old header | 360 # clean up any old header |
| 397 if mode not in ('brokenheader', 'brokenetag', 'none'): | 397 if mode not in ('brokenheader', 'brokenetag', 'none'): |
| 398 self.assertEqual(self.dummy_client.response_code, 200) | 398 self.assertEqual(self.dummy_client.response_code, 200) |
| 399 else: | 399 else: |
| 400 self.assertEqual(self.dummy_client.response_code, 412) | 400 self.assertEqual(self.dummy_client.response_code, 412) |
| 401 | 401 |
| 402 def make_file(self, arg=None): | |
| 403 ''' work around https://bugs.python.org/issue27777 ''' | |
| 404 import tempfile | |
| 405 return tempfile.TemporaryFile("wb+") | |
| 406 | |
| 402 def testDispatch(self): | 407 def testDispatch(self): |
| 403 """ | 408 """ |
| 404 run changes through rest dispatch(). This also tests | 409 run changes through rest dispatch(). This also tests |
| 405 sending json payload through code as dispatch is the | 410 sending json payload through code as dispatch is the |
| 406 code that changes json payload into something we can | 411 code that changes json payload into something we can |
| 407 process. | 412 process. |
| 408 """ | 413 """ |
| 409 # Set joe's 'realname' using json data. | 414 |
| 415 # Override the make_file so it is always set to binary | |
| 416 # read mode. This is needed so we can send a json | |
| 417 # body. | |
| 418 saved_make_file = cgi.FieldStorage.make_file | |
| 419 cgi.FieldStorage.make_file = self.make_file | |
| 420 | |
| 421 # TEST #1 | |
| 422 # PUT: joe's 'realname' using json data. | |
| 410 # simulate: /rest/data/user/<id>/realname | 423 # simulate: /rest/data/user/<id>/realname |
| 411 # use etag in header | 424 # use etag in header |
| 412 etag = calculate_etag(self.db.user.getnode(self.joeid)) | 425 etag = calculate_etag(self.db.user.getnode(self.joeid)) |
| 413 body=b'{ "data": "Joe Doe 1" }' | 426 body=b'{ "data": "Joe Doe 1" }' |
| 414 env = { "CONTENT_TYPE": "application/json", | 427 env = { "CONTENT_TYPE": "application/json", |
| 415 "CONTENT_LENGTH": len(body), | 428 "CONTENT_LENGTH": len(body), |
| 416 "REQUEST_METHOD": "PUT" | 429 "REQUEST_METHOD": "PUT" |
| 417 } | 430 } |
| 418 headers={"accept": "application/json", | 431 headers={"accept": "application/json", |
| 419 "content-type": env['CONTENT_TYPE'], | 432 "content-type": env['CONTENT_TYPE'], |
| 433 "content-length": env['CONTENT_LENGTH'], | |
| 420 "etag": etag | 434 "etag": etag |
| 421 } | 435 } |
| 422 self.headers=headers | 436 self.headers=headers |
| 423 # we need to generate a FieldStorage the looks like | 437 # we need to generate a FieldStorage the looks like |
| 424 # FieldStorage(None, None, 'string') rather than | 438 # FieldStorage(None, None, 'string') rather than |
| 437 self.assertEqual(self.dummy_client.response_code, 200) | 451 self.assertEqual(self.dummy_client.response_code, 200) |
| 438 self.assertEqual(results['data']['attributes']['realname'], | 452 self.assertEqual(results['data']['attributes']['realname'], |
| 439 'Joe Doe 1') | 453 'Joe Doe 1') |
| 440 del(self.headers) | 454 del(self.headers) |
| 441 | 455 |
| 456 # TEST #2 | |
| 442 # Set joe's 'realname' using json data. | 457 # Set joe's 'realname' using json data. |
| 443 # simulate: /rest/data/user/<id>/realname | 458 # simulate: /rest/data/user/<id>/realname |
| 444 # use etag in payload | 459 # use etag in payload |
| 445 etag = calculate_etag(self.db.user.getnode(self.joeid)) | 460 etag = calculate_etag(self.db.user.getnode(self.joeid)) |
| 446 body=s2b('{ "@etag": "%s", "data": "Joe Doe 2" }'%etag) | 461 body=s2b('{ "@etag": "%s", "data": "Joe Doe 2" }'%etag) |
| 447 env = { "CONTENT_TYPE": "application/json", | 462 env = { "CONTENT_TYPE": "application/json", |
| 448 "CONTENT_LENGTH": len(body), | 463 "CONTENT_LENGTH": len(body), |
| 449 "REQUEST_METHOD": "PUT" | 464 "REQUEST_METHOD": "PUT", |
| 450 } | 465 } |
| 451 headers={"accept": "application/json", | 466 self.headers=None # have FieldStorage get len from env. |
| 452 "content-type": env['CONTENT_TYPE'] | |
| 453 } | |
| 454 self.headers=headers | |
| 455 body_file=BytesIO(body) # FieldStorage needs a file | 467 body_file=BytesIO(body) # FieldStorage needs a file |
| 456 form = cgi.FieldStorage(body_file, | 468 form = cgi.FieldStorage(body_file, |
| 457 headers=headers, | 469 headers=None, |
| 458 environ=env) | 470 environ=env) |
| 459 self.server.client.request.headers.get=self.get_header | 471 self.server.client.request.headers.get=self.get_header |
| 472 | |
| 473 headers={"accept": "application/json", | |
| 474 "content-type": env['CONTENT_TYPE'], | |
| 475 "etag": etag | |
| 476 } | |
| 477 self.headers=headers # set for dispatch | |
| 478 | |
| 460 results = self.server.dispatch('PUT', | 479 results = self.server.dispatch('PUT', |
| 461 "/rest/data/user/%s/realname"%self.joeid, | 480 "/rest/data/user/%s/realname"%self.joeid, |
| 462 form) | 481 form) |
| 463 | 482 |
| 464 self.assertEqual(self.server.client.response_code, 200) | 483 self.assertEqual(self.server.client.response_code, 200) |
| 466 self.assertEqual(self.dummy_client.response_code, 200) | 485 self.assertEqual(self.dummy_client.response_code, 200) |
| 467 self.assertEqual(results['data']['attributes']['realname'], | 486 self.assertEqual(results['data']['attributes']['realname'], |
| 468 'Joe Doe 2') | 487 'Joe Doe 2') |
| 469 del(self.headers) | 488 del(self.headers) |
| 470 | 489 |
| 490 # TEST #3 | |
| 471 # change Joe's realname via a normal web form | 491 # change Joe's realname via a normal web form |
| 472 # This generates a FieldStorage that looks like: | 492 # This generates a FieldStorage that looks like: |
| 473 # FieldStorage(None, None, []) | 493 # FieldStorage(None, None, []) |
| 474 # use etag from header | 494 # use etag from header |
| 475 # | 495 # |
| 476 # also use a GET on the uri via the dispatch to get | 496 # Also use GET on the uri via the dispatch to retrieve |
| 477 # the results from the db. | 497 # the results from the db. |
| 478 etag = calculate_etag(self.db.user.getnode(self.joeid)) | 498 etag = calculate_etag(self.db.user.getnode(self.joeid)) |
| 479 headers={"etag": etag, | 499 headers={"etag": etag, |
| 480 "accept": "application/json", | 500 "accept": "application/json", |
| 481 } | 501 } |
| 497 | 517 |
| 498 self.assertEqual(json_dict['data']['data'], 'Joe Doe') | 518 self.assertEqual(json_dict['data']['data'], 'Joe Doe') |
| 499 self.assertEqual(json_dict['data']['link'], | 519 self.assertEqual(json_dict['data']['link'], |
| 500 "http://tracker.example/cgi-bin/" | 520 "http://tracker.example/cgi-bin/" |
| 501 "roundup.cgi/bugs/rest/data/user/3/realname") | 521 "roundup.cgi/bugs/rest/data/user/3/realname") |
| 502 self.assertEqual(json_dict['data']['type'], "<type 'str'>") | 522 self.assertIn(json_dict['data']['type'], ("<class 'str'>", |
| 523 "<type 'str'>")) | |
| 503 self.assertEqual(json_dict['data']["id"], "3") | 524 self.assertEqual(json_dict['data']["id"], "3") |
| 504 del(self.headers) | 525 del(self.headers) |
| 505 | 526 |
| 506 | 527 |
| 507 # PATCH joe's email address with json | 528 # TEST #4 |
| 529 # PATCH: joe's email address with json | |
| 508 # save address so we can use it later | 530 # save address so we can use it later |
| 509 stored_results = self.server.get_element('user', self.joeid, | 531 stored_results = self.server.get_element('user', self.joeid, |
| 510 self.empty_form) | 532 self.empty_form) |
| 511 self.assertEqual(self.dummy_client.response_code, 200) | 533 self.assertEqual(self.dummy_client.response_code, 200) |
| 512 | 534 |
| 515 env = { "CONTENT_TYPE": "application/json", | 537 env = { "CONTENT_TYPE": "application/json", |
| 516 "CONTENT_LENGTH": len(body), | 538 "CONTENT_LENGTH": len(body), |
| 517 "REQUEST_METHOD": "PATCH" | 539 "REQUEST_METHOD": "PATCH" |
| 518 } | 540 } |
| 519 headers={"accept": "application/json", | 541 headers={"accept": "application/json", |
| 520 "content-type": env['CONTENT_TYPE'] | 542 "content-type": env['CONTENT_TYPE'], |
| 543 "content-length": len(body) | |
| 521 } | 544 } |
| 522 self.headers=headers | 545 self.headers=headers |
| 523 body_file=BytesIO(body) # FieldStorage needs a file | 546 body_file=BytesIO(body) # FieldStorage needs a file |
| 524 form = cgi.FieldStorage(body_file, | 547 form = cgi.FieldStorage(body_file, |
| 525 headers=headers, | 548 headers=headers, |
| 533 results = self.server.get_element('user', self.joeid, self.empty_form) | 556 results = self.server.get_element('user', self.joeid, self.empty_form) |
| 534 self.assertEqual(self.dummy_client.response_code, 200) | 557 self.assertEqual(self.dummy_client.response_code, 200) |
| 535 self.assertEqual(results['data']['attributes']['address'], | 558 self.assertEqual(results['data']['attributes']['address'], |
| 536 'demo2@example.com') | 559 'demo2@example.com') |
| 537 | 560 |
| 538 # and set it back | 561 # and set it back reusing env and headers from last test |
| 539 etag = calculate_etag(self.db.user.getnode(self.joeid)) | 562 etag = calculate_etag(self.db.user.getnode(self.joeid)) |
| 540 body=s2b('{ "address": "%s", "@etag": "%s"}'%( | 563 body=s2b('{ "address": "%s", "@etag": "%s"}'%( |
| 541 stored_results['data']['attributes']['address'], | 564 stored_results['data']['attributes']['address'], |
| 542 etag)) | 565 etag)) |
| 543 # reuse env and headers from prior test. | 566 # reuse env and headers from prior test. |
| 555 self.assertEqual(self.dummy_client.response_code, 200) | 578 self.assertEqual(self.dummy_client.response_code, 200) |
| 556 self.assertEqual(results['data']['attributes']['address'], | 579 self.assertEqual(results['data']['attributes']['address'], |
| 557 'random@home.org') | 580 'random@home.org') |
| 558 del(self.headers) | 581 del(self.headers) |
| 559 | 582 |
| 560 # POST to create new issue | 583 # TEST #5 |
| 584 # POST: create new issue | |
| 585 # no etag needed | |
| 586 # FIXME at some point we probably want to implement | |
| 587 # Post Once Only, so we need to add a Post Once Exactly | |
| 588 # test and a resubmit as well. | |
| 589 etag = "not needed" | |
| 561 body=b'{ "title": "foo bar", "priority": "critical" }' | 590 body=b'{ "title": "foo bar", "priority": "critical" }' |
| 562 | |
| 563 env = { "CONTENT_TYPE": "application/json", | 591 env = { "CONTENT_TYPE": "application/json", |
| 564 "CONTENT_LENGTH": len(body), | 592 "CONTENT_LENGTH": len(body), |
| 565 "REQUEST_METHOD": "POST" | 593 "REQUEST_METHOD": "POST" |
| 566 } | 594 } |
| 567 headers={"accept": "application/json", | 595 headers={"accept": "application/json", |
| 568 "content-type": env['CONTENT_TYPE'] | 596 "content-type": env['CONTENT_TYPE'], |
| 597 "content-length": len(body) | |
| 569 } | 598 } |
| 570 self.headers=headers | 599 self.headers=headers |
| 571 body_file=BytesIO(body) # FieldStorage needs a file | 600 body_file=BytesIO(body) # FieldStorage needs a file |
| 572 form = cgi.FieldStorage(body_file, | 601 form = cgi.FieldStorage(body_file, |
| 573 headers=headers, | 602 headers=headers, |
| 586 self.assertEqual(self.dummy_client.response_code, 200) | 615 self.assertEqual(self.dummy_client.response_code, 200) |
| 587 self.assertEqual(results['data']['attributes']['title'], | 616 self.assertEqual(results['data']['attributes']['title'], |
| 588 'foo bar') | 617 'foo bar') |
| 589 del(self.headers) | 618 del(self.headers) |
| 590 | 619 |
| 620 # reset the make_file method in the class | |
| 621 cgi.FieldStorage.make_file = saved_make_file | |
| 622 | |
| 591 def testPut(self): | 623 def testPut(self): |
| 592 """ | 624 """ |
| 593 Change joe's 'realname' | 625 Change joe's 'realname' |
| 594 Check if we can't change admin's detail | 626 Check if we can't change admin's detail |
| 595 """ | 627 """ |
