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 """

Roundup Issue Tracker: http://roundup-tracker.org/