diff test/rest_common.py @ 6325:1a15089c2e49 issue2550923_computed_property

Merge trunk into branch
author John Rouillard <rouilj@ieee.org>
date Sat, 06 Feb 2021 20:15:26 -0500
parents ec853cef2f09
children 6a69584d117e
line wrap: on
line diff
--- a/test/rest_common.py	Fri Jan 15 16:34:30 2021 -0500
+++ b/test/rest_common.py	Sat Feb 06 20:15:26 2021 -0500
@@ -68,6 +68,8 @@
     url_pfx = 'http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/'
 
     def setUp(self):
+        from packaging import version
+
         self.dirname = '_test_rest'
         # set up and open a tracker
         # Set optimize=True as code under test (Client.main()::determine_user)
@@ -162,50 +164,58 @@
                      'iat': now_ts,
                      'exp': plus1min_ts,
             }
+
+            # in version 2.0.0 and newer jwt.encode returns string
+            # not bytestring. So we have to skip b2s conversion
             
+            if version.parse(jwt.__version__) >= version.parse('2.0.0'):
+                tostr = lambda x: x
+            else:
+                tostr = b2s
+
             self.jwt = {}
             self.claim = {}
             # generate invalid claim with expired timestamp
             self.claim['expired'] = copy(claim)
             self.claim['expired']['exp'] = expired_ts
-            self.jwt['expired'] = b2s(jwt.encode(self.claim['expired'], secret,
+            self.jwt['expired'] = tostr(jwt.encode(self.claim['expired'], secret,
                                              algorithm='HS256'))
          
             # generate valid claim with user role
             self.claim['user'] = copy(claim)
             self.claim['user']['exp'] = plus1min_ts
-            self.jwt['user'] = b2s(jwt.encode(self.claim['user'], secret,
+            self.jwt['user'] = tostr(jwt.encode(self.claim['user'], secret,
                                           algorithm='HS256'))
             # generate invalid claim bad issuer
             self.claim['badiss'] = copy(claim)
             self.claim['badiss']['iss'] = "http://someissuer/bugs"
-            self.jwt['badiss'] = b2s(jwt.encode(self.claim['badiss'], secret,
+            self.jwt['badiss'] = tostr(jwt.encode(self.claim['badiss'], secret,
                                           algorithm='HS256'))
             # generate invalid claim bad aud(ience)
             self.claim['badaud'] = copy(claim)
             self.claim['badaud']['aud'] = "http://someaudience/bugs"
-            self.jwt['badaud'] = b2s(jwt.encode(self.claim['badaud'], secret,
+            self.jwt['badaud'] = tostr(jwt.encode(self.claim['badaud'], secret,
                                           algorithm='HS256'))            
             # generate invalid claim bad sub(ject)
             self.claim['badsub'] = copy(claim)
             self.claim['badsub']['sub'] = str("99")
-            self.jwt['badsub'] = b2s(jwt.encode(self.claim['badsub'], secret,
+            self.jwt['badsub'] = tostr(jwt.encode(self.claim['badsub'], secret,
                                           algorithm='HS256'))            
             # generate invalid claim bad roles
             self.claim['badroles'] = copy(claim)
             self.claim['badroles']['roles'] = [ "badrole1", "badrole2" ]
-            self.jwt['badroles'] = b2s(jwt.encode(self.claim['badroles'], secret,
+            self.jwt['badroles'] = tostr(jwt.encode(self.claim['badroles'], secret,
                                           algorithm='HS256'))            
             # generate valid claim with limited user:email role
             self.claim['user:email'] = copy(claim)
             self.claim['user:email']['roles'] = [ "user:email" ]
-            self.jwt['user:email'] = b2s(jwt.encode(self.claim['user:email'], secret,
+            self.jwt['user:email'] = tostr(jwt.encode(self.claim['user:email'], secret,
                                           algorithm='HS256'))
 
             # generate valid claim with limited user:emailnorest role
             self.claim['user:emailnorest'] = copy(claim)
             self.claim['user:emailnorest']['roles'] = [ "user:emailnorest" ]
-            self.jwt['user:emailnorest'] = b2s(jwt.encode(self.claim['user:emailnorest'], secret,
+            self.jwt['user:emailnorest'] = tostr(jwt.encode(self.claim['user:emailnorest'], secret,
                                           algorithm='HS256'))
 
         self.db.tx_Source = 'web'
@@ -1322,6 +1332,231 @@
            "http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/user/2")
 
 
+    def testDispatchDelete(self):
+        """
+        run Delete through rest dispatch().
+        """
+
+        # TEST #0
+        # Delete class raises unauthorized error
+        # simulate: /rest/data/issue
+        env = { "REQUEST_METHOD": "DELETE"
+        }
+        headers={"accept": "application/json; version=1",
+        }
+        self.headers=headers
+        self.server.client.request.headers.get=self.get_header
+        results = self.server.dispatch(env["REQUEST_METHOD"],
+                            "/rest/data/issue",
+                            self.empty_form)
+
+        print(results)
+        self.assertEqual(self.server.client.response_code, 403)
+        json_dict = json.loads(b2s(results))
+
+        self.assertEqual(json_dict['error']['msg'],
+                         "Deletion of a whole class disabled")
+
+
+    def testDispatchBadContent(self):
+        """
+        runthrough rest dispatch() with bad content_type patterns.
+        """
+
+        # simulate: /rest/data/issue
+        body=b'{ "title": "Joe Doe has problems", \
+                 "nosy": [ "1", "3" ], \
+                 "assignedto": "2", \
+                 "abool": true, \
+                 "afloat": 2.3, \
+                 "anint": 567890 \
+        }'
+        env = { "CONTENT_TYPE": "application/jzot",
+                "CONTENT_LENGTH": len(body),
+                "REQUEST_METHOD": "POST"
+        }
+
+        headers={"accept": "application/json; version=1",
+                 "content-type": env['CONTENT_TYPE'],
+                 "content-length": env['CONTENT_LENGTH'],
+        }
+
+        self.headers=headers
+        # we need to generate a FieldStorage the looks like
+        #  FieldStorage(None, None, 'string') rather than
+        #  FieldStorage(None, None, [])
+        body_file=BytesIO(body)  # FieldStorage needs a file
+        form = client.BinaryFieldStorage(body_file,
+                                headers=headers,
+                                environ=env)
+        self.server.client.request.headers.get=self.get_header
+        results = self.server.dispatch(env["REQUEST_METHOD"],
+                            "/rest/data/issue",
+                            form)
+
+        print(results)
+        self.assertEqual(self.server.client.response_code, 415)
+        json_dict = json.loads(b2s(results))
+        self.assertEqual(json_dict['error']['msg'],
+                         "Unable to process input of type application/jzot")
+
+        # Test GET as well. I am not sure if this should pass or not.
+        # Arguably GET doesn't use any form/json input but....
+        results = self.server.dispatch('GET',
+                            "/rest/data/issue",
+                            form)
+        print(results)
+        self.assertEqual(self.server.client.response_code, 415)
+
+
+
+    def testDispatchBadAccept(self):
+        # simulate: /rest/data/issue expect failure unknown accept settings
+        body=b'{ "title": "Joe Doe has problems", \
+                 "nosy": [ "1", "3" ], \
+                 "assignedto": "2", \
+                 "abool": true, \
+                 "afloat": 2.3, \
+                 "anint": 567890 \
+        }'
+        env = { "CONTENT_TYPE": "application/json",
+                "CONTENT_LENGTH": len(body),
+                "REQUEST_METHOD": "POST"
+        }
+
+        headers={"accept": "application/zot; version=1; q=0.5",
+                 "content-type": env['CONTENT_TYPE'],
+                 "content-length": env['CONTENT_LENGTH'],
+        }
+
+        self.headers=headers
+        # we need to generate a FieldStorage the looks like
+        #  FieldStorage(None, None, 'string') rather than
+        #  FieldStorage(None, None, [])
+        body_file=BytesIO(body)  # FieldStorage needs a file
+        form = client.BinaryFieldStorage(body_file,
+                                headers=headers,
+                                environ=env)
+        self.server.client.request.headers.get=self.get_header
+        results = self.server.dispatch(env["REQUEST_METHOD"],
+                            "/rest/data/issue",
+                            form)
+
+        print(results)
+        self.assertEqual(self.server.client.response_code, 406)
+        self.assertIn(b"Requested content type 'application/zot; version=1; q=0.5' is not available.\nAcceptable types: */*, application/json", results)
+
+        # simulate: /rest/data/issue works, multiple acceptable output, one
+        # is valid
+        env = { "CONTENT_TYPE": "application/json",
+                "CONTENT_LENGTH": len(body),
+                "REQUEST_METHOD": "POST"
+        }
+
+        headers={"accept": "application/zot; version=1; q=0.75, "
+                           "application/json; version=1; q=0.5",
+                 "content-type": env['CONTENT_TYPE'],
+                 "content-length": env['CONTENT_LENGTH'],
+        }
+
+        self.headers=headers
+        # we need to generate a FieldStorage the looks like
+        #  FieldStorage(None, None, 'string') rather than
+        #  FieldStorage(None, None, [])
+        body_file=BytesIO(body)  # FieldStorage needs a file
+        form = client.BinaryFieldStorage(body_file,
+                                headers=headers,
+                                environ=env)
+        self.server.client.request.headers.get=self.get_header
+        results = self.server.dispatch(env["REQUEST_METHOD"],
+                            "/rest/data/issue",
+                            form)
+
+        print(results)
+        self.assertEqual(self.server.client.response_code, 201)
+        json_dict = json.loads(b2s(results))
+        # ERROR this should be 1. What's happening is that the code
+        # for handling 406 error code runs through everything and creates
+        # the item. Then it throws a 406 after the work is done when it
+        # realizes it can't respond as requested. So the 406 post above
+        # creates issue 1 and this one creates issue 2.
+        self.assertEqual(json_dict['data']['id'], "2")
+
+
+        # test 3 accept is empty. This triggers */* so passes
+        headers={"accept": "",
+                 "content-type": env['CONTENT_TYPE'],
+                 "content-length": env['CONTENT_LENGTH'],
+        }
+
+        self.headers=headers
+        # we need to generate a FieldStorage the looks like
+        #  FieldStorage(None, None, 'string') rather than
+        #  FieldStorage(None, None, [])
+        body_file=BytesIO(body)  # FieldStorage needs a file
+        form = client.BinaryFieldStorage(body_file,
+                                headers=headers,
+                                environ=env)
+        self.server.client.request.headers.get=self.get_header
+        results = self.server.dispatch(env["REQUEST_METHOD"],
+                            "/rest/data/issue",
+                            form)
+
+        print(results)
+        self.assertEqual(self.server.client.response_code, 201)
+        json_dict = json.loads(b2s(results))
+        # This is one more than above. Will need to be fixed
+        # When error above is fixed.
+        self.assertEqual(json_dict['data']['id'], "3")
+
+        # test 4 accept is random junk.
+        headers={"accept": "Xyzzy I am not a mime, type;",
+                 "content-type": env['CONTENT_TYPE'],
+                 "content-length": env['CONTENT_LENGTH'],
+        }
+
+        self.headers=headers
+        # we need to generate a FieldStorage the looks like
+        #  FieldStorage(None, None, 'string') rather than
+        #  FieldStorage(None, None, [])
+        body_file=BytesIO(body)  # FieldStorage needs a file
+        form = client.BinaryFieldStorage(body_file,
+                                headers=headers,
+                                environ=env)
+        self.server.client.request.headers.get=self.get_header
+        results = self.server.dispatch(env["REQUEST_METHOD"],
+                            "/rest/data/issue",
+                            form)
+
+        print(results)
+        self.assertEqual(self.server.client.response_code, 406)
+        json_dict = json.loads(b2s(results))
+        self.assertIn('Unable to parse Accept Header. Invalid media type: Xyzzy I am not a mime. Acceptable types: */* application/json', json_dict['error']['msg'])
+
+        # test 5 accept mimetype is ok, param is not
+        headers={"accept": "*/*; foo",
+                 "content-type": env['CONTENT_TYPE'],
+                 "content-length": env['CONTENT_LENGTH'],
+        }
+
+        self.headers=headers
+        # we need to generate a FieldStorage the looks like
+        #  FieldStorage(None, None, 'string') rather than
+        #  FieldStorage(None, None, [])
+        body_file=BytesIO(body)  # FieldStorage needs a file
+        form = client.BinaryFieldStorage(body_file,
+                                headers=headers,
+                                environ=env)
+        self.server.client.request.headers.get=self.get_header
+        results = self.server.dispatch(env["REQUEST_METHOD"],
+                            "/rest/data/issue",
+                            form)
+
+        print(results)
+        self.assertEqual(self.server.client.response_code, 406)
+        json_dict = json.loads(b2s(results))
+        self.assertIn('Unable to parse Accept Header. Invalid param: foo. Acceptable types: */* application/json', json_dict['error']['msg'])
+
     def testStatsGen(self):
         # check stats being returned by put and get ops
         # using dispatch which parses the @stats query param
@@ -2034,6 +2269,23 @@
                          "application/xml")
         '''
 
+        # TEST #8
+        # invalid api version
+        # application/json is selected with invalid version
+        self.server.client.request.headers.get=self.get_header
+        headers={"accept": "application/json; version=99"
+        }
+        self.headers=headers
+        with self.assertRaises(UsageError) as ctx:
+            results = self.server.dispatch('GET',
+                                           "/rest/data/status/1",
+                                           self.empty_form)
+        print(results)
+        self.assertEqual(self.server.client.response_code, 200)
+        self.assertEqual(ctx.exception.args[0],
+                         "Unrecognized version: 99. See /rest without "
+                         "specifying version for supported versions.")
+
     def testMethodOverride(self):
         # TEST #1
         # Use GET, PUT, PATCH to tunnel DELETE expect error
@@ -2518,12 +2770,15 @@
         # File content is only shown with verbose=3
         form = cgi.FieldStorage()
         form.list = [
-            cgi.MiniFieldStorage('@verbose', '3')
+            cgi.MiniFieldStorage('@verbose', '3'),
+            cgi.MiniFieldStorage('@protected', 'true')
         ]
         results = self.server.get_element('file', fileid, form)
         results = results['data']
         self.assertEqual(self.dummy_client.response_code, 200)
         self.assertEqual(results['attributes']['content'], 'hello\r\nthere')
+        self.assertIn('creator', results['attributes']) # added by @protected
+        self.assertEqual(results['attributes']['creator']['username'], "joe")
 
     def testAuthDeniedPut(self):
         """
@@ -3022,6 +3277,29 @@
         # verify the result
         self.assertTrue(not self.db.issue.is_retired(issue_id))
 
+    def testPatchBadAction(self):
+        """
+        Test Patch Action 'Unknown'
+        """
+        # create a new issue with userid 1 and 2 in the nosy list
+        issue_id = self.db.issue.create(title='foo')
+
+        # execute action retire
+        form = cgi.FieldStorage()
+        etag = calculate_etag(self.db.issue.getnode(issue_id),
+                              self.db.config['WEB_SECRET_KEY'])
+        form.list = [
+            cgi.MiniFieldStorage('@op', 'action'),
+            cgi.MiniFieldStorage('@action_name', 'unknown'),
+            cgi.MiniFieldStorage('@etag', etag)
+        ]
+        results = self.server.patch_element('issue', issue_id, form)
+        self.assertEqual(self.dummy_client.response_code, 400)
+        # verify the result, note order of allowed elements changes
+        # for python2/3 so just check prefix.
+        self.assertIn('action "unknown" is not supported, allowed: ',
+                       results['error']['msg'].args[0])
+
     def testPatchRemove(self):
         """
         Test Patch Action 'Remove' only some element from a list

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