Mercurial > p > roundup > code
comparison test/test_liveserver.py @ 6638:e1588ae185dc issue2550923_computed_property
merge from default branch. Fix travis.ci so CI builds don't error out
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Thu, 21 Apr 2022 16:54:17 -0400 |
| parents | 198875530c04 |
| children | 6ac3667706be |
comparison
equal
deleted
inserted
replaced
| 6508:85db90cc1705 | 6638:e1588ae185dc |
|---|---|
| 1 import shutil, errno, pytest, json, gzip, os | 1 import shutil, errno, pytest, json, gzip, os, re |
| 2 | 2 |
| 3 from roundup.anypy.strings import b2s | 3 from roundup.anypy.strings import b2s |
| 4 from roundup.cgi.wsgi_handler import RequestDispatcher | 4 from roundup.cgi.wsgi_handler import RequestDispatcher |
| 5 from .wsgi_liveserver import LiveServerTestCase | 5 from .wsgi_liveserver import LiveServerTestCase |
| 6 from . import db_test_base | 6 from . import db_test_base |
| 7 | |
| 8 from wsgiref.validate import validator | |
| 7 | 9 |
| 8 try: | 10 try: |
| 9 import requests | 11 import requests |
| 10 skip_requests = lambda func, *args, **kwargs: func | 12 skip_requests = lambda func, *args, **kwargs: func |
| 11 except ImportError: | 13 except ImportError: |
| 27 skip_zstd = lambda func, *args, **kwargs: func | 29 skip_zstd = lambda func, *args, **kwargs: func |
| 28 except ImportError: | 30 except ImportError: |
| 29 from .pytest_patcher import mark_class | 31 from .pytest_patcher import mark_class |
| 30 skip_zstd = mark_class(pytest.mark.skip( | 32 skip_zstd = mark_class(pytest.mark.skip( |
| 31 reason='Skipping zstd tests: zstd library not available')) | 33 reason='Skipping zstd tests: zstd library not available')) |
| 34 | |
| 35 import sys | |
| 36 | |
| 37 _py3 = sys.version_info[0] > 2 | |
| 32 | 38 |
| 33 @skip_requests | 39 @skip_requests |
| 34 class SimpleTest(LiveServerTestCase): | 40 class SimpleTest(LiveServerTestCase): |
| 35 # have chicken and egg issue here. Need to encode the base_url | 41 # have chicken and egg issue here. Need to encode the base_url |
| 36 # in the config file but we don't know it until after | 42 # in the config file but we don't know it until after |
| 58 # set the url the test instance will run at. | 64 # set the url the test instance will run at. |
| 59 cls.db.config['TRACKER_WEB'] = "http://localhost:9001/" | 65 cls.db.config['TRACKER_WEB'] = "http://localhost:9001/" |
| 60 # set up mailhost so errors get reported to debuging capture file | 66 # set up mailhost so errors get reported to debuging capture file |
| 61 cls.db.config.MAILHOST = "localhost" | 67 cls.db.config.MAILHOST = "localhost" |
| 62 cls.db.config.MAIL_HOST = "localhost" | 68 cls.db.config.MAIL_HOST = "localhost" |
| 63 cls.db.config.MAIL_DEBUG = "../mail.log.t" | 69 cls.db.config.MAIL_DEBUG = "../_test_tracker_mail.log" |
| 64 | 70 |
| 65 # enable static precompressed files | 71 # enable static precompressed files |
| 66 cls.db.config.WEB_USE_PRECOMPRESSED_FILES = 1 | 72 cls.db.config.WEB_USE_PRECOMPRESSED_FILES = 1 |
| 67 | 73 |
| 68 cls.db.config.save() | 74 cls.db.config.save() |
| 82 except OSError as error: | 88 except OSError as error: |
| 83 if error.errno not in (errno.ENOENT, errno.ESRCH): raise | 89 if error.errno not in (errno.ENOENT, errno.ESRCH): raise |
| 84 | 90 |
| 85 def create_app(self): | 91 def create_app(self): |
| 86 '''The wsgi app to start''' | 92 '''The wsgi app to start''' |
| 87 return RequestDispatcher(self.dirname) | 93 if _py3: |
| 94 return validator(RequestDispatcher(self.dirname)) | |
| 95 else: | |
| 96 # wsgiref/validator.py InputWrapper::readline is broke and | |
| 97 # doesn't support the max bytes to read argument. | |
| 98 return RequestDispatcher(self.dirname) | |
| 99 | |
| 88 | 100 |
| 89 def test_start_page(self): | 101 def test_start_page(self): |
| 90 """ simple test that verifies that the server can serve a start page. | 102 """ simple test that verifies that the server can serve a start page. |
| 91 """ | 103 """ |
| 92 f = requests.get(self.url_base()) | 104 f = requests.get(self.url_base()) |
| 93 self.assertEqual(f.status_code, 200) | 105 self.assertEqual(f.status_code, 200) |
| 94 self.assertTrue(b'Roundup' in f.content) | 106 self.assertTrue(b'Roundup' in f.content) |
| 95 self.assertTrue(b'Creator' in f.content) | 107 self.assertTrue(b'Creator' in f.content) |
| 96 | 108 |
| 109 | |
| 110 def test_rest_invalid_method_collection(self): | |
| 111 # use basic auth for rest endpoint | |
| 112 f = requests.put(self.url_base() + '/rest/data/user', | |
| 113 auth=('admin', 'sekrit'), | |
| 114 headers = {'content-type': "", | |
| 115 'x-requested-with': "rest"}) | |
| 116 print(f.status_code) | |
| 117 print(f.headers) | |
| 118 print(f.content) | |
| 119 | |
| 120 self.assertEqual(f.status_code, 405) | |
| 121 expected = { 'Access-Control-Allow-Origin': '*', | |
| 122 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', | |
| 123 'Allow': 'DELETE, GET, OPTIONS, POST', | |
| 124 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', | |
| 125 } | |
| 126 | |
| 127 print(f.headers) | |
| 128 # use dict comprehension to remove fields like date, | |
| 129 # content-length etc. from f.headers. | |
| 130 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | |
| 131 | |
| 132 content = json.loads(f.content) | |
| 133 | |
| 134 exp_content = "Method PUT not allowed. Allowed: DELETE, GET, OPTIONS, POST" | |
| 135 self.assertEqual(exp_content, content['error']['msg']) | |
| 97 | 136 |
| 98 def test_http_options(self): | 137 def test_http_options(self): |
| 99 """ options returns an unimplemented error for this case.""" | 138 """ options returns an unimplemented error for this case.""" |
| 100 | 139 |
| 101 # do not send content-type header for options | 140 # do not send content-type header for options |
| 111 headers = {'content-type': ""}) | 150 headers = {'content-type': ""}) |
| 112 print(f.status_code) | 151 print(f.status_code) |
| 113 print(f.headers) | 152 print(f.headers) |
| 114 | 153 |
| 115 self.assertEqual(f.status_code, 204) | 154 self.assertEqual(f.status_code, 204) |
| 116 expected = { 'Content-Type': 'application/json', | 155 expected = { 'Access-Control-Allow-Origin': '*', |
| 117 'Access-Control-Allow-Origin': '*', | 156 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 118 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | |
| 119 'Allow': 'OPTIONS, GET', | 157 'Allow': 'OPTIONS, GET', |
| 120 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 158 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 121 } | 159 } |
| 122 | 160 |
| 123 # use dict comprehension to remove fields like date, | 161 # use dict comprehension to remove fields like date, |
| 124 # content-length etc. from f.headers. | 162 # content-length etc. from f.headers. |
| 125 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 163 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 132 ) | 170 ) |
| 133 print(f.status_code) | 171 print(f.status_code) |
| 134 print(f.headers) | 172 print(f.headers) |
| 135 | 173 |
| 136 self.assertEqual(f.status_code, 204) | 174 self.assertEqual(f.status_code, 204) |
| 137 expected = { 'Content-Type': 'application/json', | 175 expected = { 'Access-Control-Allow-Origin': '*', |
| 138 'Access-Control-Allow-Origin': '*', | 176 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 139 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | |
| 140 'Allow': 'OPTIONS, GET', | 177 'Allow': 'OPTIONS, GET', |
| 141 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 178 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 142 } | 179 } |
| 143 | 180 |
| 144 # use dict comprehension to remove fields like date, | 181 # use dict comprehension to remove fields like date, |
| 145 # content-length etc. from f.headers. | 182 # content-length etc. from f.headers. |
| 146 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 183 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 152 headers = {'content-type': ""}) | 189 headers = {'content-type': ""}) |
| 153 print(f.status_code) | 190 print(f.status_code) |
| 154 print(f.headers) | 191 print(f.headers) |
| 155 | 192 |
| 156 self.assertEqual(f.status_code, 204) | 193 self.assertEqual(f.status_code, 204) |
| 157 expected = { 'Content-Type': 'application/json', | 194 expected = { 'Access-Control-Allow-Origin': '*', |
| 158 'Access-Control-Allow-Origin': '*', | 195 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 159 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | |
| 160 'Allow': 'OPTIONS, GET, POST', | 196 'Allow': 'OPTIONS, GET, POST', |
| 161 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 197 'Access-Control-Allow-Methods': 'OPTIONS, GET, POST', |
| 162 } | 198 } |
| 163 | 199 |
| 164 # use dict comprehension to remove fields like date, | 200 # use dict comprehension to remove fields like date, |
| 165 # content-length etc. from f.headers. | 201 # content-length etc. from f.headers. |
| 166 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 202 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 173 headers = {'content-type': ""}) | 209 headers = {'content-type': ""}) |
| 174 print(f.status_code) | 210 print(f.status_code) |
| 175 print(f.headers) | 211 print(f.headers) |
| 176 | 212 |
| 177 self.assertEqual(f.status_code, 204) | 213 self.assertEqual(f.status_code, 204) |
| 178 expected = { 'Content-Type': 'application/json', | 214 expected = { 'Access-Control-Allow-Origin': '*', |
| 179 'Access-Control-Allow-Origin': '*', | 215 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 180 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | |
| 181 'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH', | 216 'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH', |
| 182 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 217 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 183 } | 218 } |
| 184 | 219 |
| 185 # use dict comprehension to remove fields like date, | 220 # use dict comprehension to remove fields like date, |
| 186 # content-length etc. from f.headers. | 221 # content-length etc. from f.headers. |
| 187 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 222 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 193 headers = {'content-type': ""}) | 228 headers = {'content-type': ""}) |
| 194 print(f.status_code) | 229 print(f.status_code) |
| 195 print(f.headers) | 230 print(f.headers) |
| 196 | 231 |
| 197 self.assertEqual(f.status_code, 204) | 232 self.assertEqual(f.status_code, 204) |
| 198 expected = { 'Content-Type': 'application/json', | 233 expected = { 'Access-Control-Allow-Origin': '*', |
| 199 'Access-Control-Allow-Origin': '*', | 234 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 200 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | |
| 201 'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH', | 235 'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH', |
| 202 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 236 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 203 } | 237 } |
| 204 | 238 |
| 205 # use dict comprehension to remove fields like date, | 239 # use dict comprehension to remove fields like date, |
| 206 # content-length etc. from f.headers. | 240 # content-length etc. from f.headers. |
| 207 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 241 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 262 'Accept': '*/*'}) | 296 'Accept': '*/*'}) |
| 263 print(f.status_code) | 297 print(f.status_code) |
| 264 print(f.headers) | 298 print(f.headers) |
| 265 | 299 |
| 266 self.assertEqual(f.status_code, 304) | 300 self.assertEqual(f.status_code, 304) |
| 267 expected = { 'Content-Type': 'application/javascript', | 301 expected = { 'Vary': 'Accept-Encoding', |
| 268 'Vary': 'Accept-Encoding', | |
| 269 'Content-Length': '0', | 302 'Content-Length': '0', |
| 270 } | 303 } |
| 271 | 304 |
| 272 # use dict comprehension to remove fields like date, etag | 305 # use dict comprehension to remove fields like date, etag |
| 273 # etc. from f.headers. | 306 # etc. from f.headers. |
| 372 self.assertEqual(b2s(f.content)[0:25], '// User Editing Utilities') | 405 self.assertEqual(b2s(f.content)[0:25], '// User Editing Utilities') |
| 373 | 406 |
| 374 # cleanup | 407 # cleanup |
| 375 os.remove(gzfile) | 408 os.remove(gzfile) |
| 376 | 409 |
| 377 def test_compression_gzip(self): | 410 def test_compression_none_etag(self): |
| 378 # use basic auth for rest endpoint | 411 # use basic auth for rest endpoint |
| 379 f = requests.get(self.url_base() + '/rest/data/user/1/username', | 412 f = requests.get(self.url_base() + '/rest/data/user/1/username', |
| 380 auth=('admin', 'sekrit'), | 413 auth=('admin', 'sekrit'), |
| 381 headers = {'content-type': "", | 414 headers = {'content-type': "", |
| 382 'Accept-Encoding': 'gzip, foo', | 415 'Accept-Encoding': "", |
| 383 'Accept': '*/*'}) | 416 'Accept': '*/*'}) |
| 384 print(f.status_code) | 417 print(f.status_code) |
| 385 print(f.headers) | 418 print(f.headers) |
| 386 | 419 |
| 387 self.assertEqual(f.status_code, 200) | 420 self.assertEqual(f.status_code, 200) |
| 388 expected = { 'Content-Type': 'application/json', | 421 expected = { 'Content-Type': 'application/json', |
| 389 'Access-Control-Allow-Origin': '*', | 422 'Access-Control-Allow-Origin': '*', |
| 390 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 423 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 391 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 424 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 392 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 425 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH' |
| 393 'Content-Encoding': 'gzip', | |
| 394 'Vary': 'Accept-Encoding', | |
| 395 } | 426 } |
| 396 | 427 |
| 397 content_str = '''{ "data": { | 428 content_str = '''{ "data": { |
| 398 "id": "1", | 429 "id": "1", |
| 399 "link": "http://localhost:9001/rest/data/user/1/username", | 430 "link": "http://localhost:9001/rest/data/user/1/username", |
| 415 # just skip comparing it. | 446 # just skip comparing it. |
| 416 del(json_dict['data']['type']) | 447 del(json_dict['data']['type']) |
| 417 | 448 |
| 418 self.assertDictEqual(json_dict, content) | 449 self.assertDictEqual(json_dict, content) |
| 419 | 450 |
| 420 # use dict comprehension to remove fields like date, | 451 # verify that ETag header has no - delimiter |
| 421 # content-length etc. from f.headers. | 452 print(f.headers['ETag']) |
| 422 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 453 with self.assertRaises(ValueError): |
| 423 | 454 f.headers['ETag'].index('-') |
| 424 | 455 |
| 425 | 456 # use dict comprehension to remove fields like date, |
| 426 # use basic auth for rest endpoint, error case, bad attribute | 457 # content-length etc. from f.headers. |
| 427 f = requests.get(self.url_base() + '/rest/data/user/1/foo', | 458 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 459 | |
| 460 | |
| 461 def test_compression_gzip(self): | |
| 462 # use basic auth for rest endpoint | |
| 463 f = requests.get(self.url_base() + '/rest/data/user/1/username', | |
| 428 auth=('admin', 'sekrit'), | 464 auth=('admin', 'sekrit'), |
| 429 headers = {'content-type': "", | 465 headers = {'content-type': "", |
| 430 'Accept-Encoding': 'gzip, foo', | 466 'Accept-Encoding': 'gzip, foo', |
| 431 'Accept': '*/*'}) | 467 'Accept': '*/*'}) |
| 432 print(f.status_code) | 468 print(f.status_code) |
| 433 print(f.headers) | 469 print(f.headers) |
| 434 | 470 |
| 435 # ERROR: attribute error turns into 405, not sure that's right. | 471 self.assertEqual(f.status_code, 200) |
| 436 # NOTE: not compressed payload too small | |
| 437 self.assertEqual(f.status_code, 405) | |
| 438 expected = { 'Content-Type': 'application/json', | 472 expected = { 'Content-Type': 'application/json', |
| 439 'Access-Control-Allow-Origin': '*', | 473 'Access-Control-Allow-Origin': '*', |
| 440 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 474 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 441 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 475 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 442 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 476 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 443 } | |
| 444 | |
| 445 content = { "error": | |
| 446 { | |
| 447 "status": 405, | |
| 448 "msg": "'foo'" | |
| 449 } | |
| 450 } | |
| 451 | |
| 452 json_dict = json.loads(b2s(f.content)) | |
| 453 self.assertDictEqual(json_dict, content) | |
| 454 | |
| 455 # use dict comprehension to remove fields like date, | |
| 456 # content-length etc. from f.headers. | |
| 457 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | |
| 458 | |
| 459 # test file x-fer | |
| 460 f = requests.get(self.url_base() + '/@@file/user_utils.js', | |
| 461 headers = { 'Accept-Encoding': 'gzip, foo', | |
| 462 'Accept': '*/*'}) | |
| 463 print(f.status_code) | |
| 464 print(f.headers) | |
| 465 | |
| 466 self.assertEqual(f.status_code, 200) | |
| 467 expected = { 'Content-Type': 'application/javascript', | |
| 468 'Content-Encoding': 'gzip', | 477 'Content-Encoding': 'gzip', |
| 469 'Vary': 'Accept-Encoding', | |
| 470 } | |
| 471 | |
| 472 # check first few bytes. | |
| 473 self.assertEqual(b2s(f.content[0:25]), '// User Editing Utilities') | |
| 474 | |
| 475 # use dict comprehension to remove fields like date, | |
| 476 # content-length etc. from f.headers. | |
| 477 self.assertDictEqual({ key: value for (key, value) in | |
| 478 f.headers.items() if key in expected }, | |
| 479 expected) | |
| 480 | |
| 481 # test file x-fer | |
| 482 f = requests.get(self.url_base() + '/user1', | |
| 483 headers = { 'Accept-Encoding': 'gzip, foo', | |
| 484 'Accept': '*/*'}) | |
| 485 print(f.status_code) | |
| 486 print(f.headers) | |
| 487 | |
| 488 self.assertEqual(f.status_code, 200) | |
| 489 expected = { 'Content-Type': 'text/html; charset=utf-8', | |
| 490 'Content-Encoding': 'gzip', | |
| 491 'Vary': 'Accept-Encoding', | |
| 492 } | |
| 493 | |
| 494 # check first few bytes. | |
| 495 self.assertEqual(b2s(f.content[0:25]), '<!-- dollarId: user.item,') | |
| 496 | |
| 497 # use dict comprehension to remove fields like date, | |
| 498 # content-length etc. from f.headers. | |
| 499 self.assertDictEqual({ key: value for (key, value) in | |
| 500 f.headers.items() if key in expected }, | |
| 501 expected) | |
| 502 | |
| 503 @skip_brotli | |
| 504 def test_compression_br(self): | |
| 505 # use basic auth for rest endpoint | |
| 506 f = requests.get(self.url_base() + '/rest/data/user/1/username', | |
| 507 auth=('admin', 'sekrit'), | |
| 508 headers = {'content-type': "", | |
| 509 'Accept-Encoding': 'br, foo', | |
| 510 'Accept': '*/*'}) | |
| 511 print(f.status_code) | |
| 512 print(f.headers) | |
| 513 | |
| 514 self.assertEqual(f.status_code, 200) | |
| 515 expected = { 'Content-Type': 'application/json', | |
| 516 'Access-Control-Allow-Origin': '*', | |
| 517 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | |
| 518 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | |
| 519 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | |
| 520 'Content-Encoding': 'br', | |
| 521 'Vary': 'Accept-Encoding', | 478 'Vary': 'Accept-Encoding', |
| 522 } | 479 } |
| 523 | 480 |
| 524 content_str = '''{ "data": { | 481 content_str = '''{ "data": { |
| 525 "id": "1", | 482 "id": "1", |
| 527 "data": "admin" | 484 "data": "admin" |
| 528 } | 485 } |
| 529 }''' | 486 }''' |
| 530 content = json.loads(content_str) | 487 content = json.loads(content_str) |
| 531 | 488 |
| 489 | |
| 490 if (type("") == type(f.content)): | |
| 491 json_dict = json.loads(f.content) | |
| 492 else: | |
| 493 json_dict = json.loads(b2s(f.content)) | |
| 494 | |
| 495 # etag wil not match, creation date different | |
| 496 del(json_dict['data']['@etag']) | |
| 497 | |
| 498 # type is "class 'str'" under py3, "type 'str'" py2 | |
| 499 # just skip comparing it. | |
| 500 del(json_dict['data']['type']) | |
| 501 | |
| 502 self.assertDictEqual(json_dict, content) | |
| 503 | |
| 504 # verify that ETag header ends with -gzip | |
| 505 try: | |
| 506 self.assertRegex(f.headers['ETag'], r'^"[0-9a-f]{32}-gzip"$') | |
| 507 except AttributeError: | |
| 508 # python2 no assertRegex so try substring match | |
| 509 self.assertEqual(33, f.headers['ETag'].rindex('-gzip"')) | |
| 510 | |
| 511 # use dict comprehension to remove fields like date, | |
| 512 # content-length etc. from f.headers. | |
| 513 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | |
| 514 | |
| 515 | |
| 516 | |
| 517 # use basic auth for rest endpoint, error case, bad attribute | |
| 518 f = requests.get(self.url_base() + '/rest/data/user/1/foo', | |
| 519 auth=('admin', 'sekrit'), | |
| 520 headers = {'content-type': "", | |
| 521 'Accept-Encoding': 'gzip, foo', | |
| 522 'Accept': '*/*'}) | |
| 523 print(f.status_code) | |
| 524 print(f.headers) | |
| 525 | |
| 526 # NOTE: not compressed payload too small | |
| 527 self.assertEqual(f.status_code, 400) | |
| 528 expected = { 'Content-Type': 'application/json', | |
| 529 'Access-Control-Allow-Origin': '*', | |
| 530 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', | |
| 531 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | |
| 532 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', | |
| 533 } | |
| 534 | |
| 535 content = { "error": | |
| 536 { | |
| 537 "status": 400, | |
| 538 "msg": "Invalid attribute foo" | |
| 539 } | |
| 540 } | |
| 541 | |
| 542 json_dict = json.loads(b2s(f.content)) | |
| 543 self.assertDictEqual(json_dict, content) | |
| 544 | |
| 545 # use dict comprehension to remove fields like date, | |
| 546 # content-length etc. from f.headers. | |
| 547 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | |
| 548 | |
| 549 # test file x-fer | |
| 550 f = requests.get(self.url_base() + '/@@file/user_utils.js', | |
| 551 headers = { 'Accept-Encoding': 'gzip, foo', | |
| 552 'Accept': '*/*'}) | |
| 553 print(f.status_code) | |
| 554 print(f.headers) | |
| 555 | |
| 556 self.assertEqual(f.status_code, 200) | |
| 557 expected = { 'Content-Type': 'application/javascript', | |
| 558 'Content-Encoding': 'gzip', | |
| 559 'Vary': 'Accept-Encoding', | |
| 560 } | |
| 561 | |
| 562 # check first few bytes. | |
| 563 self.assertEqual(b2s(f.content[0:25]), '// User Editing Utilities') | |
| 564 | |
| 565 # use dict comprehension to remove fields like date, | |
| 566 # content-length etc. from f.headers. | |
| 567 self.assertDictEqual({ key: value for (key, value) in | |
| 568 f.headers.items() if key in expected }, | |
| 569 expected) | |
| 570 | |
| 571 # test file x-fer | |
| 572 f = requests.get(self.url_base() + '/user1', | |
| 573 headers = { 'Accept-Encoding': 'gzip, foo', | |
| 574 'Accept': '*/*'}) | |
| 575 print(f.status_code) | |
| 576 print(f.headers) | |
| 577 | |
| 578 self.assertEqual(f.status_code, 200) | |
| 579 expected = { 'Content-Type': 'text/html; charset=utf-8', | |
| 580 'Content-Encoding': 'gzip', | |
| 581 'Vary': 'Accept-Encoding', | |
| 582 } | |
| 583 | |
| 584 # check first few bytes. | |
| 585 self.assertEqual(b2s(f.content[0:25]), '<!-- dollarId: user.item,') | |
| 586 | |
| 587 # use dict comprehension to remove fields like date, | |
| 588 # content-length etc. from f.headers. | |
| 589 self.assertDictEqual({ key: value for (key, value) in | |
| 590 f.headers.items() if key in expected }, | |
| 591 expected) | |
| 592 | |
| 593 @skip_brotli | |
| 594 def test_compression_br(self): | |
| 595 # use basic auth for rest endpoint | |
| 596 f = requests.get(self.url_base() + '/rest/data/user/1/username', | |
| 597 auth=('admin', 'sekrit'), | |
| 598 headers = {'content-type': "", | |
| 599 'Accept-Encoding': 'br, foo', | |
| 600 'Accept': '*/*'}) | |
| 601 print(f.status_code) | |
| 602 print(f.headers) | |
| 603 | |
| 604 self.assertEqual(f.status_code, 200) | |
| 605 expected = { 'Content-Type': 'application/json', | |
| 606 'Access-Control-Allow-Origin': '*', | |
| 607 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', | |
| 608 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | |
| 609 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', | |
| 610 'Content-Encoding': 'br', | |
| 611 'Vary': 'Accept-Encoding', | |
| 612 } | |
| 613 | |
| 614 content_str = '''{ "data": { | |
| 615 "id": "1", | |
| 616 "link": "http://localhost:9001/rest/data/user/1/username", | |
| 617 "data": "admin" | |
| 618 } | |
| 619 }''' | |
| 620 content = json.loads(content_str) | |
| 621 | |
| 532 print(f.content) | 622 print(f.content) |
| 533 print(type(f.content)) | 623 print(type(f.content)) |
| 534 | 624 |
| 535 try: | 625 try: |
| 536 json_dict = json.loads(f.content) | 626 json_dict = json.loads(f.content) |
| 545 # just skip comparing it. | 635 # just skip comparing it. |
| 546 del(json_dict['data']['type']) | 636 del(json_dict['data']['type']) |
| 547 | 637 |
| 548 self.assertDictEqual(json_dict, content) | 638 self.assertDictEqual(json_dict, content) |
| 549 | 639 |
| 640 # verify that ETag header ends with -br | |
| 641 try: | |
| 642 self.assertRegex(f.headers['ETag'], r'^"[0-9a-f]{32}-br"$') | |
| 643 except AttributeError: | |
| 644 # python2 no assertRegex so try substring match | |
| 645 self.assertEqual(33, f.headers['ETag'].rindex('-br"')) | |
| 646 | |
| 550 # use dict comprehension to remove fields like date, | 647 # use dict comprehension to remove fields like date, |
| 551 # content-length etc. from f.headers. | 648 # content-length etc. from f.headers. |
| 552 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 649 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 553 | 650 |
| 554 | 651 |
| 558 auth=('admin', 'sekrit'), | 655 auth=('admin', 'sekrit'), |
| 559 headers = {'Accept-Encoding': 'br, foo', | 656 headers = {'Accept-Encoding': 'br, foo', |
| 560 'Accept': '*/*'}) | 657 'Accept': '*/*'}) |
| 561 print(f.status_code) | 658 print(f.status_code) |
| 562 print(f.headers) | 659 print(f.headers) |
| 563 # ERROR: attribute error turns into 405, not sure that's right. | 660 |
| 564 # Note: not compressed payload too small | 661 # Note: not compressed payload too small |
| 565 self.assertEqual(f.status_code, 405) | 662 self.assertEqual(f.status_code, 400) |
| 566 expected = { 'Content-Type': 'application/json', | 663 expected = { 'Content-Type': 'application/json', |
| 567 'Access-Control-Allow-Origin': '*', | 664 'Access-Control-Allow-Origin': '*', |
| 568 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 665 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 569 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 666 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 570 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 667 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 571 } | 668 } |
| 572 | 669 |
| 573 content = { "error": | 670 content = { "error": |
| 574 { | 671 { |
| 575 "status": 405, | 672 "status": 400, |
| 576 "msg": "'foo'" | 673 "msg": "Invalid attribute foo" |
| 577 } | 674 } |
| 578 } | 675 } |
| 579 json_dict = json.loads(b2s(f.content)) | 676 json_dict = json.loads(b2s(f.content)) |
| 580 | 677 |
| 581 self.assertDictEqual(json_dict, content) | 678 self.assertDictEqual(json_dict, content) |
| 658 print(f.headers) | 755 print(f.headers) |
| 659 | 756 |
| 660 self.assertEqual(f.status_code, 200) | 757 self.assertEqual(f.status_code, 200) |
| 661 expected = { 'Content-Type': 'application/json', | 758 expected = { 'Content-Type': 'application/json', |
| 662 'Access-Control-Allow-Origin': '*', | 759 'Access-Control-Allow-Origin': '*', |
| 663 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 760 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 664 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 761 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 665 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 762 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 666 'Content-Encoding': 'zstd', | 763 'Content-Encoding': 'zstd', |
| 667 'Vary': 'Accept-Encoding', | 764 'Vary': 'Accept-Encoding', |
| 668 } | 765 } |
| 669 | 766 |
| 670 content_str = '''{ "data": { | 767 content_str = '''{ "data": { |
| 691 # just skip comparing it. | 788 # just skip comparing it. |
| 692 del(json_dict['data']['type']) | 789 del(json_dict['data']['type']) |
| 693 | 790 |
| 694 self.assertDictEqual(json_dict, content) | 791 self.assertDictEqual(json_dict, content) |
| 695 | 792 |
| 793 # verify that ETag header ends with -zstd | |
| 794 try: | |
| 795 self.assertRegex(f.headers['ETag'], r'^"[0-9a-f]{32}-zstd"$') | |
| 796 except AttributeError: | |
| 797 # python2 no assertRegex so try substring match | |
| 798 self.assertEqual(33, f.headers['ETag'].rindex('-zstd"')) | |
| 799 | |
| 696 # use dict comprehension to remove fields like date, | 800 # use dict comprehension to remove fields like date, |
| 697 # content-length etc. from f.headers. | 801 # content-length etc. from f.headers. |
| 698 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) | 802 self.assertDictEqual({ key: value for (key, value) in f.headers.items() if key in expected }, expected) |
| 699 | 803 |
| 700 | 804 |
| 706 'Accept-Encoding': 'zstd, foo', | 810 'Accept-Encoding': 'zstd, foo', |
| 707 'Accept': '*/*'}) | 811 'Accept': '*/*'}) |
| 708 print(f.status_code) | 812 print(f.status_code) |
| 709 print(f.headers) | 813 print(f.headers) |
| 710 | 814 |
| 711 # ERROR: attribute error turns into 405, not sure that's right. | |
| 712 # Note: not compressed, payload too small | 815 # Note: not compressed, payload too small |
| 713 self.assertEqual(f.status_code, 405) | 816 self.assertEqual(f.status_code, 400) |
| 714 expected = { 'Content-Type': 'application/json', | 817 expected = { 'Content-Type': 'application/json', |
| 715 'Access-Control-Allow-Origin': '*', | 818 'Access-Control-Allow-Origin': '*', |
| 716 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override', | 819 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', |
| 717 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', | 820 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 718 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH', | 821 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, POST, PUT, DELETE, PATCH', |
| 719 } | 822 } |
| 720 | 823 |
| 721 content = { "error": | 824 content = { "error": |
| 722 { | 825 { |
| 723 "status": 405, | 826 "status": 400, |
| 724 "msg": "'foo'" | 827 "msg": "Invalid attribute foo" |
| 725 } | 828 } |
| 726 } | 829 } |
| 727 | 830 |
| 728 json_dict = json.loads(b2s(f.content)) | 831 json_dict = json.loads(b2s(f.content)) |
| 729 self.assertDictEqual(json_dict, content) | 832 self.assertDictEqual(json_dict, content) |
| 774 # use dict comprehension to remove fields like date, | 877 # use dict comprehension to remove fields like date, |
| 775 # content-length etc. from f.headers. | 878 # content-length etc. from f.headers. |
| 776 self.assertDictEqual({ key: value for (key, value) in | 879 self.assertDictEqual({ key: value for (key, value) in |
| 777 f.headers.items() if key in expected }, | 880 f.headers.items() if key in expected }, |
| 778 expected) | 881 expected) |
| 882 | |
| 883 @pytest.mark.xfail(reason="Fails with 3600 age on circle ci not sure why") | |
| 884 def test_cache_control_css(self): | |
| 885 f = requests.get(self.url_base() + '/@@file/style.css', | |
| 886 headers = {'content-type': "", | |
| 887 'Accept': '*/*'}) | |
| 888 print(f.status_code) | |
| 889 print(f.headers) | |
| 890 | |
| 891 self.assertEqual(f.status_code, 200) | |
| 892 self.assertEqual(f.headers['Cache-Control'], 'public, max-age=4838400') | |
| 893 | |
| 894 def test_cache_control_js(self): | |
| 895 f = requests.get(self.url_base() + '/@@file/help_controls.js', | |
| 896 headers = {'content-type': "", | |
| 897 'Accept': '*/*'}) | |
| 898 print(f.status_code) | |
| 899 print(f.headers) | |
| 900 | |
| 901 self.assertEqual(f.status_code, 200) | |
| 902 self.assertEqual(f.headers['Cache-Control'], 'public, max-age=1209600') | |
| 903 | |
| 904 def test_new_issue_with_file_upload(self): | |
| 905 # Set up session to manage cookies <insert blue monster here> | |
| 906 session = requests.Session() | |
| 907 | |
| 908 # login using form | |
| 909 login = {"__login_name": 'admin', '__login_password': 'sekrit', | |
| 910 "@action": "login"} | |
| 911 f = session.post(self.url_base()+'/', data=login) | |
| 912 # look for change in text in sidebar post login | |
| 913 self.assertIn('Hello, admin', f.text) | |
| 914 | |
| 915 # create a new issue and upload a file | |
| 916 file_content = 'this is a test file\n' | |
| 917 file = {"@file": ('test1.txt', file_content, "text/plain") } | |
| 918 issue = {"title": "my title", "priority": "1", "@action": "new"} | |
| 919 f = session.post(self.url_base()+'/issue?@template=item', data=issue, files=file) | |
| 920 | |
| 921 # use redirected url to determine which issue and file were created. | |
| 922 m = re.search(r'[0-9]/issue(?P<issue>[0-9]+)\?@ok_message.*file%20(?P<file>[0-9]+)%20', f.url) | |
| 923 | |
| 924 # verify message in redirected url: file 1 created\nissue 1 created | |
| 925 # warning may fail if another test loads tracker with files. | |
| 926 # Escape % signs in string by doubling them. This verifies the | |
| 927 # search is working correctly. | |
| 928 # use groupdict for python2. | |
| 929 self.assertEqual('http://localhost:9001/issue%(issue)s?@ok_message=file%%20%(file)s%%20created%%0Aissue%%20%(issue)s%%20created&@template=item'%m.groupdict(), f.url) | |
| 930 | |
| 931 # we have an issue display, verify filename is listed there | |
| 932 # seach for unique filename given to it. | |
| 933 self.assertIn("test1.txt", f.text) | |
| 934 | |
| 935 # download file and verify content | |
| 936 f = session.get(self.url_base()+'/file%(file)s/text1.txt'%m.groupdict()) | |
| 937 self.assertEqual(f.text, file_content) | |
| 938 print(f.text) | |
| 939 | |
| 940 def test_new_file_via_rest(self): | |
| 941 | |
| 942 session = requests.Session() | |
| 943 session.auth = ('admin', 'sekrit') | |
| 944 | |
| 945 url = self.url_base() + '/rest/data/' | |
| 946 fname = 'a-bigger-testfile' | |
| 947 d = dict(name = fname, type='application/octet-stream') | |
| 948 c = dict (content = r'xyzzy') | |
| 949 r = session.post(url + 'file', files = c, data = d, | |
| 950 headers = {'x-requested-with': "rest"} | |
| 951 ) | |
| 952 | |
| 953 # was a 500 before fix for issue2551178 | |
| 954 self.assertEqual(r.status_code, 201) | |
| 955 # just compare the path leave off the number | |
| 956 self.assertIn('http://localhost:9001/rest/data/file/', | |
| 957 r.headers["location"]) | |
| 958 json_dict = json.loads(r.text) | |
| 959 self.assertEqual(json_dict["data"]["link"], r.headers["location"]) | |
| 960 | |
| 961 # download file and verify content | |
| 962 r = session.get(r.headers["location"] +'/content') | |
| 963 json_dict = json.loads(r.text) | |
| 964 self.assertEqual(json_dict['data']['data'], c["content"]) | |
| 965 print(r.text) | |
| 966 | |
| 967 # Upload a file via rest interface - no auth | |
| 968 session.auth = None | |
| 969 r = session.post(url + 'file', files = c, data = d, | |
| 970 headers = {'x-requested-with': "rest"} | |
| 971 ) | |
| 972 self.assertEqual(r.status_code, 403) | |
| 973 | |
| 974 # get session variable from web form login | |
| 975 # and use it to upload file | |
| 976 # login using form | |
| 977 login = {"__login_name": 'admin', '__login_password': 'sekrit', | |
| 978 "@action": "login"} | |
| 979 f = session.post(self.url_base()+'/', data=login) | |
| 980 # look for change in text in sidebar post login | |
| 981 self.assertIn('Hello, admin', f.text) | |
| 982 | |
| 983 r = session.post(url + 'file', files = c, data = d, | |
| 984 headers = {'x-requested-with': "rest"} | |
| 985 ) | |
| 986 self.assertEqual(r.status_code, 201) | |
| 987 print(r.status_code) | |
| 988 | |
| 989 |
