Mercurial > p > roundup > code
comparison test/rest_common.py @ 7587:8f29e4ea05ce
fix: issue2551278 - datetime.datetime.utcnow deprecation.
Replace calls with equivalent that produces timezone aware dates
rather than naive dates.
Also some flake8 fixes for test/rest_common.py.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Tue, 25 Jul 2023 16:30:10 -0400 |
| parents | 978285986b2c |
| children | be6cb2e0d471 |
comparison
equal
deleted
inserted
replaced
| 7586:859c57bc8d91 | 7587:8f29e4ea05ce |
|---|---|
| 1 import pytest | 1 import pytest |
| 2 import unittest | 2 import unittest |
| 3 import os | |
| 4 import shutil | 3 import shutil |
| 5 import errno | 4 import errno |
| 6 | 5 |
| 7 from time import sleep | 6 from time import sleep |
| 8 from datetime import datetime, timedelta | 7 from datetime import datetime, timedelta |
| 8 from roundup.anypy.cgi_ import cgi | |
| 9 from roundup.anypy.datetime_ import utcnow | |
| 9 from roundup.test.tx_Source_detector import init as tx_Source_init | 10 from roundup.test.tx_Source_detector import init as tx_Source_init |
| 10 from roundup.anypy.cgi_ import cgi | 11 |
| 11 | 12 |
| 12 try: | 13 try: |
| 13 from datetime import timezone | 14 from datetime import timezone |
| 14 myutc = timezone.utc | 15 myutc = timezone.utc |
| 15 except ImportError: | 16 except ImportError: |
| 16 # python 2 | 17 # python 2 |
| 17 from datetime import tzinfo | 18 from datetime import tzinfo |
| 18 ZERO = timedelta(0) | 19 ZERO = timedelta(0) |
| 20 | |
| 19 class UTC(tzinfo): | 21 class UTC(tzinfo): |
| 20 """UTC""" | 22 """UTC""" |
| 21 def utcoffset(self, dt): | 23 def utcoffset(self, dt): |
| 22 return ZERO | 24 return ZERO |
| 23 | 25 |
| 24 def tzname(self, dt): | 26 def tzname(self, dt): |
| 25 return "UTC" | 27 return "UTC" |
| 26 | 28 |
| 27 def dst(self, dt): | 29 def dst(self, dt): |
| 28 return ZERO | 30 return ZERO |
| 29 | 31 |
| 30 myutc = UTC() | 32 myutc = UTC() |
| 31 | 33 |
| 32 from roundup.cgi.exceptions import * | 34 from roundup.cgi.exceptions import * |
| 33 from roundup.hyperdb import HyperdbValueError | 35 from roundup.hyperdb import HyperdbValueError |
| 34 from roundup.exceptions import * | 36 from roundup.exceptions import * |
| 35 from roundup import password, hyperdb | 37 from roundup import password, hyperdb |
| 36 from roundup.rest import RestfulInstance, calculate_etag | 38 from roundup.rest import RestfulInstance, calculate_etag |
| 37 from roundup.backends import list_backends | |
| 38 from roundup.cgi import client | 39 from roundup.cgi import client |
| 39 from roundup.anypy.strings import b2s, s2b, us2u | 40 from roundup.anypy.strings import b2s, s2b, us2u |
| 40 import random | 41 import random |
| 41 | 42 |
| 42 from roundup.backends.sessions_dbm import OneTimeKeys | 43 from roundup.backends.sessions_dbm import OneTimeKeys |
| 43 from roundup.anypy.dbm_ import anydbm, whichdb | 44 from roundup.anypy.dbm_ import whichdb |
| 44 | 45 |
| 45 from .db_test_base import setupTracker | 46 from .db_test_base import setupTracker |
| 46 | 47 |
| 47 from roundup.test.mocknull import MockNull | 48 from roundup.test.mocknull import MockNull |
| 48 | 49 |
| 54 try: | 55 try: |
| 55 import jwt | 56 import jwt |
| 56 skip_jwt = lambda func, *args, **kwargs: func | 57 skip_jwt = lambda func, *args, **kwargs: func |
| 57 except ImportError: | 58 except ImportError: |
| 58 from .pytest_patcher import mark_class | 59 from .pytest_patcher import mark_class |
| 59 jwt=None | 60 jwt = None |
| 60 skip_jwt = mark_class(pytest.mark.skip( | 61 skip_jwt = mark_class(pytest.mark.skip( |
| 61 reason='Skipping JWT tests: jwt library not available')) | 62 reason='Skipping JWT tests: jwt library not available')) |
| 62 | 63 |
| 63 NEEDS_INSTANCE = 1 | 64 NEEDS_INSTANCE = 1 |
| 64 | 65 |
| 65 | 66 |
| 66 class TestCase(): | 67 class TestCase(): |
| 67 | 68 |
| 107 p = self.db.security.addPermission(name='Retire', klass='issue') | 108 p = self.db.security.addPermission(name='Retire', klass='issue') |
| 108 self.db.security.addPermissionToRole('User', p) | 109 self.db.security.addPermissionToRole('User', p) |
| 109 | 110 |
| 110 # add set of roles for testing jwt's. | 111 # add set of roles for testing jwt's. |
| 111 self.db.security.addRole(name="User:email", | 112 self.db.security.addRole(name="User:email", |
| 112 description="allow email by jwt") | 113 description="allow email by jwt") |
| 113 # allow the jwt to access everybody's email addresses. | 114 # allow the jwt to access everybody's email addresses. |
| 114 # this makes it easier to differentiate between User and | 115 # this makes it easier to differentiate between User and |
| 115 # User:email roles by accessing the /rest/data/user | 116 # User:email roles by accessing the /rest/data/user |
| 116 # endpoint | 117 # endpoint |
| 117 jwt_perms = self.db.security.addPermission(name='View', | 118 jwt_perms = self.db.security.addPermission( |
| 118 klass='user', | 119 name='View', |
| 119 properties=('id', 'realname', 'address', 'username'), | 120 klass='user', |
| 120 description="Allow jwt access to email", | 121 properties=('id', 'realname', 'address', 'username'), |
| 121 props_only=False) | 122 description="Allow jwt access to email", |
| 123 props_only=False) | |
| 122 self.db.security.addPermissionToRole("User:email", jwt_perms) | 124 self.db.security.addPermissionToRole("User:email", jwt_perms) |
| 123 self.db.security.addPermissionToRole("User:email", "Rest Access") | 125 self.db.security.addPermissionToRole("User:email", "Rest Access") |
| 124 | 126 |
| 125 # add set of roles for testing jwt's. | 127 # add set of roles for testing jwt's. |
| 126 # this is like the user:email role, but it missing access to the rest endpoint. | 128 # this is like the user:email role, but it missing access to the rest endpoint. |
| 127 self.db.security.addRole(name="User:emailnorest", | 129 self.db.security.addRole(name="User:emailnorest", |
| 128 description="allow email by jwt") | 130 description="allow email by jwt") |
| 129 jwt_perms = self.db.security.addPermission(name='View', | 131 jwt_perms = self.db.security.addPermission( |
| 130 klass='user', | 132 name='View', |
| 131 properties=('id', 'realname', 'address', 'username'), | 133 klass='user', |
| 132 description="Allow jwt access to email but forget to allow rest", | 134 properties=('id', 'realname', 'address', 'username'), |
| 133 props_only=False) | 135 description="Allow jwt access to email but forget to allow rest", |
| 136 props_only=False) | |
| 134 self.db.security.addPermissionToRole("User:emailnorest", jwt_perms) | 137 self.db.security.addPermissionToRole("User:emailnorest", jwt_perms) |
| 135 | |
| 136 | 138 |
| 137 if jwt: | 139 if jwt: |
| 138 # must be 32 chars in length minimum (I think this is at least | 140 # must be 32 chars in length minimum (I think this is at least |
| 139 # 256 bits of data) | 141 # 256 bits of data) |
| 140 | 142 |
| 141 secret = "TestingTheJwtSecretTestingTheJwtSecret" | 143 secret = "TestingTheJwtSecretTestingTheJwtSecret" |
| 142 self.db.config['WEB_JWT_SECRET'] = secret | 144 self.db.config['WEB_JWT_SECRET'] = secret |
| 143 | 145 |
| 144 # generate all timestamps in UTC. | 146 # generate all timestamps in UTC. |
| 145 base_datetime = datetime(1970,1,1, tzinfo=myutc) | 147 base_datetime = datetime(1970, 1, 1, tzinfo=myutc) |
| 146 | 148 |
| 147 # A UTC timestamp for now. | 149 # A UTC timestamp for now. |
| 148 dt = datetime.now(myutc) | 150 dt = datetime.now(myutc) |
| 149 now_ts = int((dt - base_datetime).total_seconds()) | 151 now_ts = int((dt - base_datetime).total_seconds()) |
| 150 | 152 |
| 156 dt = dt - timedelta(seconds=120) | 158 dt = dt - timedelta(seconds=120) |
| 157 expired_ts = int((dt - base_datetime).total_seconds()) | 159 expired_ts = int((dt - base_datetime).total_seconds()) |
| 158 | 160 |
| 159 # claims match what cgi/client.py::determine_user | 161 # claims match what cgi/client.py::determine_user |
| 160 # is looking for | 162 # is looking for |
| 161 claim= { 'sub': self.db.getuid(), | 163 claim = {'sub': self.db.getuid(), |
| 162 'iss': self.db.config.TRACKER_WEB, | 164 'iss': self.db.config.TRACKER_WEB, |
| 163 'aud': self.db.config.TRACKER_WEB, | 165 'aud': self.db.config.TRACKER_WEB, |
| 164 'roles': [ 'User' ], | 166 'roles': ['User'], |
| 165 'iat': now_ts, | 167 'iat': now_ts, |
| 166 'exp': plus1min_ts, | 168 'exp': plus1min_ts} |
| 167 } | |
| 168 | 169 |
| 169 # in version 2.0.0 and newer jwt.encode returns string | 170 # in version 2.0.0 and newer jwt.encode returns string |
| 170 # not bytestring. So we have to skip b2s conversion | 171 # not bytestring. So we have to skip b2s conversion |
| 171 | 172 |
| 172 if version.parse(jwt.__version__) >= version.parse('2.0.0'): | 173 if version.parse(jwt.__version__) >= version.parse('2.0.0'): |
| 173 tostr = lambda x: x | 174 tostr = lambda x: x |
| 174 else: | 175 else: |
| 175 tostr = b2s | 176 tostr = b2s |
| 176 | 177 |
| 177 self.jwt = {} | 178 self.jwt = {} |
| 178 self.claim = {} | 179 self.claim = {} |
| 179 # generate invalid claim with expired timestamp | 180 # generate invalid claim with expired timestamp |
| 180 self.claim['expired'] = copy(claim) | 181 self.claim['expired'] = copy(claim) |
| 181 self.claim['expired']['exp'] = expired_ts | 182 self.claim['expired']['exp'] = expired_ts |
| 182 self.jwt['expired'] = tostr(jwt.encode(self.claim['expired'], secret, | 183 self.jwt['expired'] = tostr(jwt.encode( |
| 183 algorithm='HS256')) | 184 self.claim['expired'], secret, |
| 184 | 185 algorithm='HS256')) |
| 186 | |
| 185 # generate valid claim with user role | 187 # generate valid claim with user role |
| 186 self.claim['user'] = copy(claim) | 188 self.claim['user'] = copy(claim) |
| 187 self.claim['user']['exp'] = plus1min_ts | 189 self.claim['user']['exp'] = plus1min_ts |
| 188 self.jwt['user'] = tostr(jwt.encode(self.claim['user'], secret, | 190 self.jwt['user'] = tostr(jwt.encode( |
| 189 algorithm='HS256')) | 191 self.claim['user'], secret, |
| 192 algorithm='HS256')) | |
| 190 # generate invalid claim bad issuer | 193 # generate invalid claim bad issuer |
| 191 self.claim['badiss'] = copy(claim) | 194 self.claim['badiss'] = copy(claim) |
| 192 self.claim['badiss']['iss'] = "http://someissuer/bugs" | 195 self.claim['badiss']['iss'] = "http://someissuer/bugs" |
| 193 self.jwt['badiss'] = tostr(jwt.encode(self.claim['badiss'], secret, | 196 self.jwt['badiss'] = tostr(jwt.encode( |
| 194 algorithm='HS256')) | 197 self.claim['badiss'], secret, |
| 198 algorithm='HS256')) | |
| 195 # generate invalid claim bad aud(ience) | 199 # generate invalid claim bad aud(ience) |
| 196 self.claim['badaud'] = copy(claim) | 200 self.claim['badaud'] = copy(claim) |
| 197 self.claim['badaud']['aud'] = "http://someaudience/bugs" | 201 self.claim['badaud']['aud'] = "http://someaudience/bugs" |
| 198 self.jwt['badaud'] = tostr(jwt.encode(self.claim['badaud'], secret, | 202 self.jwt['badaud'] = tostr(jwt.encode( |
| 199 algorithm='HS256')) | 203 self.claim['badaud'], secret, |
| 204 algorithm='HS256')) | |
| 200 # generate invalid claim bad sub(ject) | 205 # generate invalid claim bad sub(ject) |
| 201 self.claim['badsub'] = copy(claim) | 206 self.claim['badsub'] = copy(claim) |
| 202 self.claim['badsub']['sub'] = str("99") | 207 self.claim['badsub']['sub'] = str("99") |
| 203 self.jwt['badsub'] = tostr(jwt.encode(self.claim['badsub'], secret, | 208 self.jwt['badsub'] = tostr( |
| 204 algorithm='HS256')) | 209 jwt.encode(self.claim['badsub'], secret, |
| 210 algorithm='HS256')) | |
| 205 # generate invalid claim bad roles | 211 # generate invalid claim bad roles |
| 206 self.claim['badroles'] = copy(claim) | 212 self.claim['badroles'] = copy(claim) |
| 207 self.claim['badroles']['roles'] = [ "badrole1", "badrole2" ] | 213 self.claim['badroles']['roles'] = ["badrole1", "badrole2"] |
| 208 self.jwt['badroles'] = tostr(jwt.encode(self.claim['badroles'], secret, | 214 self.jwt['badroles'] = tostr(jwt.encode( |
| 209 algorithm='HS256')) | 215 self.claim['badroles'], secret, |
| 216 algorithm='HS256')) | |
| 210 # generate valid claim with limited user:email role | 217 # generate valid claim with limited user:email role |
| 211 self.claim['user:email'] = copy(claim) | 218 self.claim['user:email'] = copy(claim) |
| 212 self.claim['user:email']['roles'] = [ "user:email" ] | 219 self.claim['user:email']['roles'] = ["user:email"] |
| 213 self.jwt['user:email'] = tostr(jwt.encode(self.claim['user:email'], secret, | 220 self.jwt['user:email'] = tostr(jwt.encode( |
| 214 algorithm='HS256')) | 221 self.claim['user:email'], secret, |
| 222 algorithm='HS256')) | |
| 215 | 223 |
| 216 # generate valid claim with limited user:emailnorest role | 224 # generate valid claim with limited user:emailnorest role |
| 217 self.claim['user:emailnorest'] = copy(claim) | 225 self.claim['user:emailnorest'] = copy(claim) |
| 218 self.claim['user:emailnorest']['roles'] = [ "user:emailnorest" ] | 226 self.claim['user:emailnorest']['roles'] = ["user:emailnorest"] |
| 219 self.jwt['user:emailnorest'] = tostr(jwt.encode(self.claim['user:emailnorest'], secret, | 227 self.jwt['user:emailnorest'] = tostr(jwt.encode( |
| 220 algorithm='HS256')) | 228 self.claim['user:emailnorest'], secret, |
| 229 algorithm='HS256')) | |
| 221 | 230 |
| 222 self.db.tx_Source = 'web' | 231 self.db.tx_Source = 'web' |
| 223 | 232 |
| 224 self.db.issue.addprop(tx_Source=hyperdb.String()) | 233 self.db.issue.addprop(tx_Source=hyperdb.String()) |
| 225 self.db.issue.addprop(anint=hyperdb.Integer()) | 234 self.db.issue.addprop(anint=hyperdb.Integer()) |
| 269 shutil.rmtree(self.dirname) | 278 shutil.rmtree(self.dirname) |
| 270 except OSError as error: | 279 except OSError as error: |
| 271 if error.errno not in (errno.ENOENT, errno.ESRCH): | 280 if error.errno not in (errno.ENOENT, errno.ESRCH): |
| 272 raise | 281 raise |
| 273 | 282 |
| 274 def get_header (self, header, not_found=None): | 283 def get_header(self, header, not_found=None): |
| 275 try: | 284 try: |
| 276 return self.headers[header.lower()] | 285 return self.headers[header.lower()] |
| 277 except (AttributeError, KeyError, TypeError): | 286 except (AttributeError, KeyError, TypeError): |
| 278 if header.upper() in self.client_env: | 287 if header.upper() in self.client_env: |
| 279 return self.client_env[header.upper()] | 288 return self.client_env[header.upper()] |
| 303 self.create_stati() | 312 self.create_stati() |
| 304 self.db.issue.create( | 313 self.db.issue.create( |
| 305 title='foo1', | 314 title='foo1', |
| 306 status=self.db.status.lookup('open'), | 315 status=self.db.status.lookup('open'), |
| 307 priority=self.db.priority.lookup('normal'), | 316 priority=self.db.priority.lookup('normal'), |
| 308 nosy = [ "1", "2" ] | 317 nosy=["1", "2"] |
| 309 ) | 318 ) |
| 310 issue_open_norm = self.db.issue.create( | 319 issue_open_norm = self.db.issue.create( |
| 311 title='foo2', | 320 title='foo2', |
| 312 status=self.db.status.lookup('open'), | 321 status=self.db.status.lookup('open'), |
| 313 priority=self.db.priority.lookup('normal'), | 322 priority=self.db.priority.lookup('normal'), |
| 314 assignedto = "3" | 323 assignedto="3" |
| 315 ) | 324 ) |
| 316 issue_open_crit = self.db.issue.create( | 325 issue_open_crit = self.db.issue.create( |
| 317 title='foo5', | 326 title='foo5', |
| 318 status=self.db.status.lookup('open'), | 327 status=self.db.status.lookup('open'), |
| 319 priority=self.db.priority.lookup('critical') | 328 priority=self.db.priority.lookup('critical') |
| 369 """ | 378 """ |
| 370 Retrieve all issues with an 'o' in status | 379 Retrieve all issues with an 'o' in status |
| 371 sort by status.name (not order) | 380 sort by status.name (not order) |
| 372 """ | 381 """ |
| 373 base_path = self.db.config['TRACKER_WEB'] + 'rest/data/' | 382 base_path = self.db.config['TRACKER_WEB'] + 'rest/data/' |
| 374 #self.maxDiff=None | 383 # self.maxDiff=None |
| 375 self.create_sampledata() | 384 self.create_sampledata() |
| 376 self.db.issue.set('2', status=self.db.status.lookup('closed')) | 385 self.db.issue.set('2', status=self.db.status.lookup('closed')) |
| 377 self.db.issue.set('3', status=self.db.status.lookup('chatting')) | 386 self.db.issue.set('3', status=self.db.status.lookup('chatting')) |
| 378 expected={'data': | 387 expected = {'data': |
| 379 {'@total_size': 2, | 388 {'@total_size': 2, |
| 380 'collection': [ | 389 'collection': [ |
| 381 { 'id': '2', | 390 {'id': '2', |
| 382 'link': base_path + 'issue/2', | 391 'link': base_path + 'issue/2', |
| 383 'assignedto.issue': None, | 392 'assignedto.issue': None, |
| 384 'status': | 393 'status': |
| 385 { 'id': '10', | 394 {'id': '10', |
| 386 'link': base_path + 'status/10' | 395 'link': base_path + 'status/10' |
| 387 } | 396 } |
| 388 }, | 397 }, |
| 389 { 'id': '1', | 398 {'id': '1', |
| 390 'link': base_path + 'issue/1', | 399 'link': base_path + 'issue/1', |
| 391 'assignedto.issue': None, | 400 'assignedto.issue': None, |
| 392 'status': | 401 'status': |
| 393 { 'id': '9', | 402 {'id': '9', |
| 394 'link': base_path + 'status/9' | 403 'link': base_path + 'status/9' |
| 395 } | 404 } |
| 396 }, | 405 }, |
| 397 ]} | 406 ]} |
| 398 } | 407 } |
| 399 form = cgi.FieldStorage() | 408 form = cgi.FieldStorage() |
| 400 form.list = [ | 409 form.list = [ |
| 401 cgi.MiniFieldStorage('status.name', 'o'), | 410 cgi.MiniFieldStorage('status.name', 'o'), |
| 402 cgi.MiniFieldStorage('@fields', 'status,assignedto.issue'), | 411 cgi.MiniFieldStorage('@fields', 'status,assignedto.issue'), |
| 403 cgi.MiniFieldStorage('@sort', 'status.name'), | 412 cgi.MiniFieldStorage('@sort', 'status.name'), |
| 409 """ | 418 """ |
| 410 Mess up the names of various properties and make sure we get a 400 | 419 Mess up the names of various properties and make sure we get a 400 |
| 411 and a somewhat useful error message. | 420 and a somewhat useful error message. |
| 412 """ | 421 """ |
| 413 base_path = self.db.config['TRACKER_WEB'] + 'rest/data/' | 422 base_path = self.db.config['TRACKER_WEB'] + 'rest/data/' |
| 414 #self.maxDiff=None | 423 # self.maxDiff=None |
| 415 self.create_sampledata() | 424 self.create_sampledata() |
| 416 self.db.issue.set('2', status=self.db.status.lookup('closed')) | 425 self.db.issue.set('2', status=self.db.status.lookup('closed')) |
| 417 self.db.issue.set('3', status=self.db.status.lookup('chatting')) | 426 self.db.issue.set('3', status=self.db.status.lookup('chatting')) |
| 418 expected = [ | 427 expected = [ |
| 419 {'error': {'msg': KeyError('Unknown property: assignedto.isse',), | 428 {'error': {'msg': KeyError('Unknown property: assignedto.isse',), |
| 469 | 478 |
| 470 def testGetExactMatch(self): | 479 def testGetExactMatch(self): |
| 471 """ Retrieve all issues with an exact title | 480 """ Retrieve all issues with an exact title |
| 472 """ | 481 """ |
| 473 base_path = self.db.config['TRACKER_WEB'] + 'rest/data/' | 482 base_path = self.db.config['TRACKER_WEB'] + 'rest/data/' |
| 474 #self.maxDiff=None | 483 # self.maxDiff=None |
| 475 self.create_sampledata() | 484 self.create_sampledata() |
| 476 self.db.issue.set('2', title='This is an exact match') | 485 self.db.issue.set('2', title='This is an exact match') |
| 477 self.db.issue.set('3', title='This is an exact match') | 486 self.db.issue.set('3', title='This is an exact match') |
| 478 self.db.issue.set('1', title='This is AN exact match') | 487 self.db.issue.set('1', title='This is AN exact match') |
| 479 expected={'data': | 488 expected = {'data': |
| 480 {'@total_size': 2, | 489 {'@total_size': 2, |
| 481 'collection': [ | 490 'collection': [ |
| 482 { 'id': '2', | 491 {'id': '2', |
| 483 'link': base_path + 'issue/2', | 492 'link': base_path + 'issue/2', |
| 484 }, | 493 }, |
| 485 { 'id': '3', | 494 {'id': '3', |
| 486 'link': base_path + 'issue/3', | 495 'link': base_path + 'issue/3', |
| 487 }, | 496 }, |
| 488 ]} | 497 ]} |
| 489 } | 498 } |
| 490 form = cgi.FieldStorage() | 499 form = cgi.FieldStorage() |
| 491 form.list = [ | 500 form.list = [ |
| 492 cgi.MiniFieldStorage('title:', 'This is an exact match'), | 501 cgi.MiniFieldStorage('title:', 'This is an exact match'), |
| 493 cgi.MiniFieldStorage('@sort', 'status.name'), | 502 cgi.MiniFieldStorage('@sort', 'status.name'), |
| 494 ] | 503 ] |
| 500 | 509 |
| 501 self.maxDiff = 4000 | 510 self.maxDiff = 4000 |
| 502 self.create_sampledata() | 511 self.create_sampledata() |
| 503 base_path = self.db.config['TRACKER_WEB'] + 'rest/data/issue/' | 512 base_path = self.db.config['TRACKER_WEB'] + 'rest/data/issue/' |
| 504 | 513 |
| 505 | |
| 506 # Check formating for issues status=open; @fields and verbose tests | 514 # Check formating for issues status=open; @fields and verbose tests |
| 507 form = cgi.FieldStorage() | 515 form = cgi.FieldStorage() |
| 508 form.list = [ | 516 form.list = [ |
| 509 cgi.MiniFieldStorage('status', 'open'), | 517 cgi.MiniFieldStorage('status', 'open'), |
| 510 cgi.MiniFieldStorage('@fields', 'nosy,status,creator'), | 518 cgi.MiniFieldStorage('@fields', 'nosy,status,creator'), |
| 511 cgi.MiniFieldStorage('@verbose', '2') | 519 cgi.MiniFieldStorage('@verbose', '2') |
| 512 ] | 520 ] |
| 513 | 521 |
| 514 expected={'data': | 522 expected = {'data': |
| 515 {'@total_size': 3, | 523 {'@total_size': 3, |
| 516 'collection': [ { | 524 'collection': [ { |
| 517 'creator': {'id': '3', | 525 'creator': {'id': '3', |
| 518 'link': 'http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/user/3', | 526 'link': 'http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/user/3', |
| 519 'username': 'joe'}, | 527 'username': 'joe'}, |
| 520 'status': {'id': '9', | 528 'status': {'id': '9', |
| 521 'name': 'open', | 529 'name': 'open', |
| 522 'link': 'http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/status/9'}, | 530 'link': 'http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/status/9'}, |
| 523 'id': '1', | 531 'id': '1', |
| 524 'nosy': [ | 532 'nosy': [ |
| 525 {'username': 'admin', | 533 {'username': 'admin', |
| 1092 except AttributeError: | 1100 except AttributeError: |
| 1093 # if dir attribute doesn't exist the primary db is not | 1101 # if dir attribute doesn't exist the primary db is not |
| 1094 # sqlite or anydbm. So don't need to exercise code. | 1102 # sqlite or anydbm. So don't need to exercise code. |
| 1095 pass | 1103 pass |
| 1096 | 1104 |
| 1097 start_time = datetime.utcnow() | 1105 start_time = utcnow() |
| 1098 # don't set an accept header; json should be the default | 1106 # don't set an accept header; json should be the default |
| 1099 # use up all our allowed api calls | 1107 # use up all our allowed api calls |
| 1100 for i in range(calls_per_interval): | 1108 for i in range(calls_per_interval): |
| 1101 # i is 0 ... calls_per_interval | 1109 # i is 0 ... calls_per_interval |
| 1102 self.client_error_message = [] | 1110 self.client_error_message = [] |
| 1103 self.server.client.env.update({'REQUEST_METHOD': 'GET'}) | 1111 self.server.client.env.update({'REQUEST_METHOD': 'GET'}) |
| 1104 results = self.server.dispatch('GET', | 1112 results = self.server.dispatch('GET', |
| 1105 "/rest/data/user/%s/realname"%self.joeid, | 1113 "/rest/data/user/%s/realname"%self.joeid, |
| 1106 self.empty_form) | 1114 self.empty_form) |
| 1107 | 1115 |
| 1108 loop_time = datetime.utcnow() | 1116 loop_time = utcnow() |
| 1109 self.assertLess((loop_time-start_time).total_seconds(), | 1117 self.assertLess((loop_time-start_time).total_seconds(), |
| 1110 int(wait_time_str), | 1118 int(wait_time_str), |
| 1111 "Test system is too slow to complete test as configured") | 1119 "Test system is too slow to complete test as configured") |
| 1112 | 1120 |
| 1113 # is successful | 1121 # is successful |
| 1146 self.assertEqual( | 1154 self.assertEqual( |
| 1147 str(self.server.client.additional_headers["Retry-After"]), | 1155 str(self.server.client.additional_headers["Retry-After"]), |
| 1148 wait_time_str) # check as string | 1156 wait_time_str) # check as string |
| 1149 | 1157 |
| 1150 print("Reset:", self.server.client.additional_headers["X-RateLimit-Reset"]) | 1158 print("Reset:", self.server.client.additional_headers["X-RateLimit-Reset"]) |
| 1151 print("Now realtime pre-sleep:", datetime.utcnow()) | 1159 print("Now realtime pre-sleep:", utcnow()) |
| 1152 # sleep as requested so we can do another login | 1160 # sleep as requested so we can do another login |
| 1153 sleep(float(wait_time_str) + 0.1) | 1161 sleep(float(wait_time_str) + 0.1) |
| 1154 print("Now realtime post-sleep:", datetime.utcnow()) | 1162 print("Now realtime post-sleep:", utcnow()) |
| 1155 | 1163 |
| 1156 # this should succeed | 1164 # this should succeed |
| 1157 self.server.client.additional_headers.clear() | 1165 self.server.client.additional_headers.clear() |
| 1158 results = self.server.dispatch('GET', | 1166 results = self.server.dispatch('GET', |
| 1159 "/rest/data/user/%s/realname"%self.joeid, | 1167 "/rest/data/user/%s/realname"%self.joeid, |
| 1160 self.empty_form) | 1168 self.empty_form) |
| 1161 print(results) | 1169 print(results) |
| 1162 print("Reset:", self.server.client.additional_headers["X-RateLimit-Reset-date"]) | 1170 print("Reset:", self.server.client.additional_headers["X-RateLimit-Reset-date"]) |
| 1163 print("Now realtime:", datetime.utcnow()) | 1171 print("Now realtime:", utcnow()) |
| 1164 print("Now ts header:", self.server.client.additional_headers["Now"]) | 1172 print("Now ts header:", self.server.client.additional_headers["Now"]) |
| 1165 print("Now date header:", self.server.client.additional_headers["Now-date"]) | 1173 print("Now date header:", self.server.client.additional_headers["Now-date"]) |
| 1166 | 1174 |
| 1167 self.assertEqual(self.server.client.response_code, 200) | 1175 self.assertEqual(self.server.client.response_code, 200) |
| 1168 | 1176 |
