Package cherrypy :: Package test :: Module test_core
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.test.test_core

  1  """Basic tests for the CherryPy core: request handling.""" 
  2   
  3  import os 
  4  localDir = os.path.dirname(__file__) 
  5  import sys 
  6  import types 
  7   
  8  import cherrypy 
  9  from cherrypy._cpcompat import IncompleteRead, itervalues, ntob 
 10  from cherrypy import _cptools, tools 
 11  from cherrypy.lib import httputil, static 
 12   
 13   
 14  favicon_path = os.path.join(os.getcwd(), localDir, "../favicon.ico") 
 15   
 16  #                             Client-side code                             # 
 17   
 18  from cherrypy.test import helper 
 19   
20 -class CoreRequestHandlingTest(helper.CPWebCase):
21
22 - def setup_server():
23 class Root: 24 25 def index(self): 26 return "hello"
27 index.exposed = True 28 29 favicon_ico = tools.staticfile.handler(filename=favicon_path) 30 31 def defct(self, newct): 32 newct = "text/%s" % newct 33 cherrypy.config.update({'tools.response_headers.on': True, 34 'tools.response_headers.headers': 35 [('Content-Type', newct)]})
36 defct.exposed = True 37 38 def baseurl(self, path_info, relative=None): 39 return cherrypy.url(path_info, relative=bool(relative)) 40 baseurl.exposed = True 41 42 root = Root() 43 44 if sys.version_info >= (2, 5): 45 from cherrypy.test._test_decorators import ExposeExamples 46 root.expose_dec = ExposeExamples() 47 48 49 class TestType(type): 50 """Metaclass which automatically exposes all functions in each subclass, 51 and adds an instance of the subclass as an attribute of root. 52 """ 53 def __init__(cls, name, bases, dct): 54 type.__init__(cls, name, bases, dct) 55 for value in itervalues(dct): 56 if isinstance(value, types.FunctionType): 57 value.exposed = True 58 setattr(root, name.lower(), cls()) 59 Test = TestType('Test', (object, ), {}) 60 61 62 class URL(Test): 63 64 _cp_config = {'tools.trailing_slash.on': False} 65 66 def index(self, path_info, relative=None): 67 if relative != 'server': 68 relative = bool(relative) 69 return cherrypy.url(path_info, relative=relative) 70 71 def leaf(self, path_info, relative=None): 72 if relative != 'server': 73 relative = bool(relative) 74 return cherrypy.url(path_info, relative=relative) 75 76 77 def log_status(): 78 Status.statuses.append(cherrypy.response.status) 79 cherrypy.tools.log_status = cherrypy.Tool('on_end_resource', log_status) 80 81 82 class Status(Test): 83 84 def index(self): 85 return "normal" 86 87 def blank(self): 88 cherrypy.response.status = "" 89 90 # According to RFC 2616, new status codes are OK as long as they 91 # are between 100 and 599. 92 93 # Here is an illegal code... 94 def illegal(self): 95 cherrypy.response.status = 781 96 return "oops" 97 98 # ...and here is an unknown but legal code. 99 def unknown(self): 100 cherrypy.response.status = "431 My custom error" 101 return "funky" 102 103 # Non-numeric code 104 def bad(self): 105 cherrypy.response.status = "error" 106 return "bad news" 107 108 statuses = [] 109 def on_end_resource_stage(self): 110 return repr(self.statuses) 111 on_end_resource_stage._cp_config = {'tools.log_status.on': True} 112 113 114 class Redirect(Test): 115 116 class Error: 117 _cp_config = {"tools.err_redirect.on": True, 118 "tools.err_redirect.url": "/errpage", 119 "tools.err_redirect.internal": False, 120 } 121 122 def index(self): 123 raise NameError("redirect_test") 124 index.exposed = True 125 error = Error() 126 127 def index(self): 128 return "child" 129 130 def custom(self, url, code): 131 raise cherrypy.HTTPRedirect(url, code) 132 133 def by_code(self, code): 134 raise cherrypy.HTTPRedirect("somewhere%20else", code) 135 by_code._cp_config = {'tools.trailing_slash.extra': True} 136 137 def nomodify(self): 138 raise cherrypy.HTTPRedirect("", 304) 139 140 def proxy(self): 141 raise cherrypy.HTTPRedirect("proxy", 305) 142 143 def stringify(self): 144 return str(cherrypy.HTTPRedirect("/")) 145 146 def fragment(self, frag): 147 raise cherrypy.HTTPRedirect("/some/url#%s" % frag) 148 149 def login_redir(): 150 if not getattr(cherrypy.request, "login", None): 151 raise cherrypy.InternalRedirect("/internalredirect/login") 152 tools.login_redir = _cptools.Tool('before_handler', login_redir) 153 154 def redir_custom(): 155 raise cherrypy.InternalRedirect("/internalredirect/custom_err") 156 157 class InternalRedirect(Test): 158 159 def index(self): 160 raise cherrypy.InternalRedirect("/") 161 162 def choke(self): 163 return 3 / 0 164 choke.exposed = True 165 choke._cp_config = {'hooks.before_error_response': redir_custom} 166 167 def relative(self, a, b): 168 raise cherrypy.InternalRedirect("cousin?t=6") 169 170 def cousin(self, t): 171 assert cherrypy.request.prev.closed 172 return cherrypy.request.prev.query_string 173 174 def petshop(self, user_id): 175 if user_id == "parrot": 176 # Trade it for a slug when redirecting 177 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=slug') 178 elif user_id == "terrier": 179 # Trade it for a fish when redirecting 180 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=fish') 181 else: 182 # This should pass the user_id through to getImagesByUser 183 raise cherrypy.InternalRedirect( 184 '/image/getImagesByUser?user_id=%s' % str(user_id)) 185 186 # We support Python 2.3, but the @-deco syntax would look like this: 187 # @tools.login_redir() 188 def secure(self): 189 return "Welcome!" 190 secure = tools.login_redir()(secure) 191 # Since calling the tool returns the same function you pass in, 192 # you could skip binding the return value, and just write: 193 # tools.login_redir()(secure) 194 195 def login(self): 196 return "Please log in" 197 198 def custom_err(self): 199 return "Something went horribly wrong." 200 201 def early_ir(self, arg): 202 return "whatever" 203 early_ir._cp_config = {'hooks.before_request_body': redir_custom} 204 205 206 class Image(Test): 207 208 def getImagesByUser(self, user_id): 209 return "0 images for %s" % user_id 210 211 212 class Flatten(Test): 213 214 def as_string(self): 215 return "content" 216 217 def as_list(self): 218 return ["con", "tent"] 219 220 def as_yield(self): 221 yield ntob("content") 222 223 def as_dblyield(self): 224 yield self.as_yield() 225 as_dblyield._cp_config = {'tools.flatten.on': True} 226 227 def as_refyield(self): 228 for chunk in self.as_yield(): 229 yield chunk 230 231 232 class Ranges(Test): 233 234 def get_ranges(self, bytes): 235 return repr(httputil.get_ranges('bytes=%s' % bytes, 8)) 236 237 def slice_file(self): 238 path = os.path.join(os.getcwd(), os.path.dirname(__file__)) 239 return static.serve_file(os.path.join(path, "static/index.html")) 240 241 242 class Cookies(Test): 243 244 def single(self, name): 245 cookie = cherrypy.request.cookie[name] 246 # Python2's SimpleCookie.__setitem__ won't take unicode keys. 247 cherrypy.response.cookie[str(name)] = cookie.value 248 249 def multiple(self, names): 250 for name in names: 251 cookie = cherrypy.request.cookie[name] 252 # Python2's SimpleCookie.__setitem__ won't take unicode keys. 253 cherrypy.response.cookie[str(name)] = cookie.value 254 255 def append_headers(header_list, debug=False): 256 if debug: 257 cherrypy.log( 258 "Extending response headers with %s" % repr(header_list), 259 "TOOLS.APPEND_HEADERS") 260 cherrypy.serving.response.header_list.extend(header_list) 261 cherrypy.tools.append_headers = cherrypy.Tool('on_end_resource', append_headers) 262 263 class MultiHeader(Test): 264 265 def header_list(self): 266 pass 267 header_list = cherrypy.tools.append_headers(header_list=[ 268 (ntob('WWW-Authenticate'), ntob('Negotiate')), 269 (ntob('WWW-Authenticate'), ntob('Basic realm="foo"')), 270 ])(header_list) 271 272 def commas(self): 273 cherrypy.response.headers['WWW-Authenticate'] = 'Negotiate,Basic realm="foo"' 274 275 276 cherrypy.tree.mount(root) 277 setup_server = staticmethod(setup_server) 278 279
280 - def testStatus(self):
281 self.getPage("/status/") 282 self.assertBody('normal') 283 self.assertStatus(200) 284 285 self.getPage("/status/blank") 286 self.assertBody('') 287 self.assertStatus(200) 288 289 self.getPage("/status/illegal") 290 self.assertStatus(500) 291 msg = "Illegal response status from server (781 is out of range)." 292 self.assertErrorPage(500, msg) 293 294 if not getattr(cherrypy.server, 'using_apache', False): 295 self.getPage("/status/unknown") 296 self.assertBody('funky') 297 self.assertStatus(431) 298 299 self.getPage("/status/bad") 300 self.assertStatus(500) 301 msg = "Illegal response status from server ('error' is non-numeric)." 302 self.assertErrorPage(500, msg)
303
304 - def test_on_end_resource_status(self):
305 self.getPage('/status/on_end_resource_stage') 306 self.assertBody('[]') 307 self.getPage('/status/on_end_resource_stage') 308 self.assertBody(repr(["200 OK"]))
309
310 - def testSlashes(self):
311 # Test that requests for index methods without a trailing slash 312 # get redirected to the same URI path with a trailing slash. 313 # Make sure GET params are preserved. 314 self.getPage("/redirect?id=3") 315 self.assertStatus(301) 316 self.assertInBody("<a href='%s/redirect/?id=3'>" 317 "%s/redirect/?id=3</a>" % (self.base(), self.base())) 318 319 if self.prefix(): 320 # Corner case: the "trailing slash" redirect could be tricky if 321 # we're using a virtual root and the URI is "/vroot" (no slash). 322 self.getPage("") 323 self.assertStatus(301) 324 self.assertInBody("<a href='%s/'>%s/</a>" % 325 (self.base(), self.base())) 326 327 # Test that requests for NON-index methods WITH a trailing slash 328 # get redirected to the same URI path WITHOUT a trailing slash. 329 # Make sure GET params are preserved. 330 self.getPage("/redirect/by_code/?code=307") 331 self.assertStatus(301) 332 self.assertInBody("<a href='%s/redirect/by_code?code=307'>" 333 "%s/redirect/by_code?code=307</a>" 334 % (self.base(), self.base())) 335 336 # If the trailing_slash tool is off, CP should just continue 337 # as if the slashes were correct. But it needs some help 338 # inside cherrypy.url to form correct output. 339 self.getPage('/url?path_info=page1') 340 self.assertBody('%s/url/page1' % self.base()) 341 self.getPage('/url/leaf/?path_info=page1') 342 self.assertBody('%s/url/page1' % self.base())
343
344 - def testRedirect(self):
345 self.getPage("/redirect/") 346 self.assertBody('child') 347 self.assertStatus(200) 348 349 self.getPage("/redirect/by_code?code=300") 350 self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>") 351 self.assertStatus(300) 352 353 self.getPage("/redirect/by_code?code=301") 354 self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>") 355 self.assertStatus(301) 356 357 self.getPage("/redirect/by_code?code=302") 358 self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>") 359 self.assertStatus(302) 360 361 self.getPage("/redirect/by_code?code=303") 362 self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>") 363 self.assertStatus(303) 364 365 self.getPage("/redirect/by_code?code=307") 366 self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>") 367 self.assertStatus(307) 368 369 self.getPage("/redirect/nomodify") 370 self.assertBody('') 371 self.assertStatus(304) 372 373 self.getPage("/redirect/proxy") 374 self.assertBody('') 375 self.assertStatus(305) 376 377 # HTTPRedirect on error 378 self.getPage("/redirect/error/") 379 self.assertStatus(('302 Found', '303 See Other')) 380 self.assertInBody('/errpage') 381 382 # Make sure str(HTTPRedirect()) works. 383 self.getPage("/redirect/stringify", protocol="HTTP/1.0") 384 self.assertStatus(200) 385 self.assertBody("(['%s/'], 302)" % self.base()) 386 if cherrypy.server.protocol_version == "HTTP/1.1": 387 self.getPage("/redirect/stringify", protocol="HTTP/1.1") 388 self.assertStatus(200) 389 self.assertBody("(['%s/'], 303)" % self.base()) 390 391 # check that #fragments are handled properly 392 # http://skrb.org/ietf/http_errata.html#location-fragments 393 frag = "foo" 394 self.getPage("/redirect/fragment/%s" % frag) 395 self.assertMatchesBody(r"<a href='(.*)\/some\/url\#%s'>\1\/some\/url\#%s</a>" % (frag, frag)) 396 loc = self.assertHeader('Location') 397 assert loc.endswith("#%s" % frag) 398 self.assertStatus(('302 Found', '303 See Other')) 399 400 # check injection protection 401 # See http://www.cherrypy.org/ticket/1003 402 self.getPage("/redirect/custom?code=303&url=/foobar/%0d%0aSet-Cookie:%20somecookie=someval") 403 self.assertStatus(303) 404 loc = self.assertHeader('Location') 405 assert 'Set-Cookie' in loc 406 self.assertNoHeader('Set-Cookie')
407
408 - def test_InternalRedirect(self):
409 # InternalRedirect 410 self.getPage("/internalredirect/") 411 self.assertBody('hello') 412 self.assertStatus(200) 413 414 # Test passthrough 415 self.getPage("/internalredirect/petshop?user_id=Sir-not-appearing-in-this-film") 416 self.assertBody('0 images for Sir-not-appearing-in-this-film') 417 self.assertStatus(200) 418 419 # Test args 420 self.getPage("/internalredirect/petshop?user_id=parrot") 421 self.assertBody('0 images for slug') 422 self.assertStatus(200) 423 424 # Test POST 425 self.getPage("/internalredirect/petshop", method="POST", 426 body="user_id=terrier") 427 self.assertBody('0 images for fish') 428 self.assertStatus(200) 429 430 # Test ir before body read 431 self.getPage("/internalredirect/early_ir", method="POST", 432 body="arg=aha!") 433 self.assertBody("Something went horribly wrong.") 434 self.assertStatus(200) 435 436 self.getPage("/internalredirect/secure") 437 self.assertBody('Please log in') 438 self.assertStatus(200) 439 440 # Relative path in InternalRedirect. 441 # Also tests request.prev. 442 self.getPage("/internalredirect/relative?a=3&b=5") 443 self.assertBody("a=3&b=5") 444 self.assertStatus(200) 445 446 # InternalRedirect on error 447 self.getPage("/internalredirect/choke") 448 self.assertStatus(200) 449 self.assertBody("Something went horribly wrong.")
450
451 - def testFlatten(self):
452 for url in ["/flatten/as_string", "/flatten/as_list", 453 "/flatten/as_yield", "/flatten/as_dblyield", 454 "/flatten/as_refyield"]: 455 self.getPage(url) 456 self.assertBody('content')
457
458 - def testRanges(self):
459 self.getPage("/ranges/get_ranges?bytes=3-6") 460 self.assertBody("[(3, 7)]") 461 462 # Test multiple ranges and a suffix-byte-range-spec, for good measure. 463 self.getPage("/ranges/get_ranges?bytes=2-4,-1") 464 self.assertBody("[(2, 5), (7, 8)]") 465 466 # Get a partial file. 467 if cherrypy.server.protocol_version == "HTTP/1.1": 468 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')]) 469 self.assertStatus(206) 470 self.assertHeader("Content-Type", "text/html;charset=utf-8") 471 self.assertHeader("Content-Range", "bytes 2-5/14") 472 self.assertBody("llo,") 473 474 # What happens with overlapping ranges (and out of order, too)? 475 self.getPage("/ranges/slice_file", [('Range', 'bytes=4-6,2-5')]) 476 self.assertStatus(206) 477 ct = self.assertHeader("Content-Type") 478 expected_type = "multipart/byteranges; boundary=" 479 self.assert_(ct.startswith(expected_type)) 480 boundary = ct[len(expected_type):] 481 expected_body = ("\r\n--%s\r\n" 482 "Content-type: text/html\r\n" 483 "Content-range: bytes 4-6/14\r\n" 484 "\r\n" 485 "o, \r\n" 486 "--%s\r\n" 487 "Content-type: text/html\r\n" 488 "Content-range: bytes 2-5/14\r\n" 489 "\r\n" 490 "llo,\r\n" 491 "--%s--\r\n" % (boundary, boundary, boundary)) 492 self.assertBody(expected_body) 493 self.assertHeader("Content-Length") 494 495 # Test "416 Requested Range Not Satisfiable" 496 self.getPage("/ranges/slice_file", [('Range', 'bytes=2300-2900')]) 497 self.assertStatus(416) 498 # "When this status code is returned for a byte-range request, 499 # the response SHOULD include a Content-Range entity-header 500 # field specifying the current length of the selected resource" 501 self.assertHeader("Content-Range", "bytes */14") 502 elif cherrypy.server.protocol_version == "HTTP/1.0": 503 # Test Range behavior with HTTP/1.0 request 504 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')]) 505 self.assertStatus(200) 506 self.assertBody("Hello, world\r\n")
507
508 - def testFavicon(self):
509 # favicon.ico is served by staticfile. 510 icofilename = os.path.join(localDir, "../favicon.ico") 511 icofile = open(icofilename, "rb") 512 data = icofile.read() 513 icofile.close() 514 515 self.getPage("/favicon.ico") 516 self.assertBody(data)
517
518 - def testCookies(self):
519 if sys.version_info >= (2, 5): 520 header_value = lambda x: x 521 else: 522 header_value = lambda x: x+';' 523 524 self.getPage("/cookies/single?name=First", 525 [('Cookie', 'First=Dinsdale;')]) 526 self.assertHeader('Set-Cookie', header_value('First=Dinsdale')) 527 528 self.getPage("/cookies/multiple?names=First&names=Last", 529 [('Cookie', 'First=Dinsdale; Last=Piranha;'), 530 ]) 531 self.assertHeader('Set-Cookie', header_value('First=Dinsdale')) 532 self.assertHeader('Set-Cookie', header_value('Last=Piranha')) 533 534 self.getPage("/cookies/single?name=Something-With:Colon", 535 [('Cookie', 'Something-With:Colon=some-value')]) 536 self.assertStatus(400)
537
538 - def testDefaultContentType(self):
539 self.getPage('/') 540 self.assertHeader('Content-Type', 'text/html;charset=utf-8') 541 self.getPage('/defct/plain') 542 self.getPage('/') 543 self.assertHeader('Content-Type', 'text/plain;charset=utf-8') 544 self.getPage('/defct/html')
545
546 - def test_multiple_headers(self):
547 self.getPage('/multiheader/header_list') 548 self.assertEqual([(k, v) for k, v in self.headers if k == 'WWW-Authenticate'], 549 [('WWW-Authenticate', 'Negotiate'), 550 ('WWW-Authenticate', 'Basic realm="foo"'), 551 ]) 552 self.getPage('/multiheader/commas') 553 self.assertHeader('WWW-Authenticate', 'Negotiate,Basic realm="foo"')
554
555 - def test_cherrypy_url(self):
556 # Input relative to current 557 self.getPage('/url/leaf?path_info=page1') 558 self.assertBody('%s/url/page1' % self.base()) 559 self.getPage('/url/?path_info=page1') 560 self.assertBody('%s/url/page1' % self.base()) 561 # Other host header 562 host = 'www.mydomain.example' 563 self.getPage('/url/leaf?path_info=page1', 564 headers=[('Host', host)]) 565 self.assertBody('%s://%s/url/page1' % (self.scheme, host)) 566 567 # Input is 'absolute'; that is, relative to script_name 568 self.getPage('/url/leaf?path_info=/page1') 569 self.assertBody('%s/page1' % self.base()) 570 self.getPage('/url/?path_info=/page1') 571 self.assertBody('%s/page1' % self.base()) 572 573 # Single dots 574 self.getPage('/url/leaf?path_info=./page1') 575 self.assertBody('%s/url/page1' % self.base()) 576 self.getPage('/url/leaf?path_info=other/./page1') 577 self.assertBody('%s/url/other/page1' % self.base()) 578 self.getPage('/url/?path_info=/other/./page1') 579 self.assertBody('%s/other/page1' % self.base()) 580 581 # Double dots 582 self.getPage('/url/leaf?path_info=../page1') 583 self.assertBody('%s/page1' % self.base()) 584 self.getPage('/url/leaf?path_info=other/../page1') 585 self.assertBody('%s/url/page1' % self.base()) 586 self.getPage('/url/leaf?path_info=/other/../page1') 587 self.assertBody('%s/page1' % self.base()) 588 589 # Output relative to current path or script_name 590 self.getPage('/url/?path_info=page1&relative=True') 591 self.assertBody('page1') 592 self.getPage('/url/leaf?path_info=/page1&relative=True') 593 self.assertBody('../page1') 594 self.getPage('/url/leaf?path_info=page1&relative=True') 595 self.assertBody('page1') 596 self.getPage('/url/leaf?path_info=leaf/page1&relative=True') 597 self.assertBody('leaf/page1') 598 self.getPage('/url/leaf?path_info=../page1&relative=True') 599 self.assertBody('../page1') 600 self.getPage('/url/?path_info=other/../page1&relative=True') 601 self.assertBody('page1') 602 603 # Output relative to / 604 self.getPage('/baseurl?path_info=ab&relative=True') 605 self.assertBody('ab') 606 # Output relative to / 607 self.getPage('/baseurl?path_info=/ab&relative=True') 608 self.assertBody('ab') 609 610 # absolute-path references ("server-relative") 611 # Input relative to current 612 self.getPage('/url/leaf?path_info=page1&relative=server') 613 self.assertBody('/url/page1') 614 self.getPage('/url/?path_info=page1&relative=server') 615 self.assertBody('/url/page1') 616 # Input is 'absolute'; that is, relative to script_name 617 self.getPage('/url/leaf?path_info=/page1&relative=server') 618 self.assertBody('/page1') 619 self.getPage('/url/?path_info=/page1&relative=server') 620 self.assertBody('/page1')
621
622 - def test_expose_decorator(self):
623 if not sys.version_info >= (2, 5): 624 return self.skip("skipped (Python 2.5+ only) ") 625 626 # Test @expose 627 self.getPage("/expose_dec/no_call") 628 self.assertStatus(200) 629 self.assertBody("Mr E. R. Bradshaw") 630 631 # Test @expose() 632 self.getPage("/expose_dec/call_empty") 633 self.assertStatus(200) 634 self.assertBody("Mrs. B.J. Smegma") 635 636 # Test @expose("alias") 637 self.getPage("/expose_dec/call_alias") 638 self.assertStatus(200) 639 self.assertBody("Mr Nesbitt") 640 # Does the original name work? 641 self.getPage("/expose_dec/nesbitt") 642 self.assertStatus(200) 643 self.assertBody("Mr Nesbitt") 644 645 # Test @expose(["alias1", "alias2"]) 646 self.getPage("/expose_dec/alias1") 647 self.assertStatus(200) 648 self.assertBody("Mr Ken Andrews") 649 self.getPage("/expose_dec/alias2") 650 self.assertStatus(200) 651 self.assertBody("Mr Ken Andrews") 652 # Does the original name work? 653 self.getPage("/expose_dec/andrews") 654 self.assertStatus(200) 655 self.assertBody("Mr Ken Andrews") 656 657 # Test @expose(alias="alias") 658 self.getPage("/expose_dec/alias3") 659 self.assertStatus(200) 660 self.assertBody("Mr. and Mrs. Watson")
661 662
663 -class ErrorTests(helper.CPWebCase):
664
665 - def setup_server():
666 def break_header(): 667 # Add a header after finalize that is invalid 668 cherrypy.serving.response.header_list.append((2, 3))
669 cherrypy.tools.break_header = cherrypy.Tool('on_end_resource', break_header) 670 671 class Root: 672 def index(self): 673 return "hello"
674 index.exposed = True 675 676 def start_response_error(self): 677 return "salud!" 678 start_response_error._cp_config = {'tools.break_header.on': True} 679 root = Root() 680 681 cherrypy.tree.mount(root) 682 setup_server = staticmethod(setup_server) 683
684 - def test_start_response_error(self):
685 self.getPage("/start_response_error") 686 self.assertStatus(500) 687 self.assertInBody("TypeError: response.header_list key 2 is not a byte string.")
688