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

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