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  from cherrypy.test import test 
   4  test.prefer_parent_path() 
   5   
   6  import os 
   7  localDir = os.path.dirname(__file__) 
   8  import sys 
   9  import types 
  10  from httplib import IncompleteRead 
  11   
  12  import cherrypy 
  13  from cherrypy import _cptools, tools 
  14  from cherrypy.lib import http, static 
  15   
  16   
  17  favicon_path = os.path.join(os.getcwd(), localDir, "../favicon.ico") 
  18   
  19  defined_http_methods = ("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", 
  20                          "TRACE", "PROPFIND") 
  21   
  22   
23 -def setup_server():
24 class Root: 25 26 def index(self): 27 return "hello"
28 index.exposed = True 29 30 favicon_ico = tools.staticfile.handler(filename=favicon_path) 31 32 def andnow(self): 33 return "the larch" 34 andnow.exposed = True 35 36 def global_(self): 37 pass 38 global_.exposed = True 39 40 def delglobal(self): 41 del self.__class__.__dict__['global_'] 42 delglobal.exposed = True 43 44 def defct(self, newct): 45 newct = "text/%s" % newct 46 cherrypy.config.update({'tools.response_headers.on': True, 47 'tools.response_headers.headers': 48 [('Content-Type', newct)]}) 49 defct.exposed = True 50 51 def upload(self, file): 52 return "Size: %s" % len(file.file.read()) 53 upload.exposed = True 54 55 def baseurl(self, path_info, relative=None): 56 return cherrypy.url(path_info, relative=bool(relative)) 57 baseurl.exposed = True 58 59 root = Root() 60 61 62 class TestType(type): 63 """Metaclass which automatically exposes all functions in each subclass, 64 and adds an instance of the subclass as an attribute of root. 65 """ 66 def __init__(cls, name, bases, dct): 67 type.__init__(cls, name, bases, dct) 68 for value in dct.itervalues(): 69 if isinstance(value, types.FunctionType): 70 value.exposed = True 71 setattr(root, name.lower(), cls()) 72 class Test(object): 73 __metaclass__ = TestType 74 75 76 class URL(Test): 77 78 _cp_config = {'tools.trailing_slash.on': False} 79 80 def index(self, path_info, relative=None): 81 if relative != 'server': 82 relative = bool(relative) 83 return cherrypy.url(path_info, relative=relative) 84 85 def leaf(self, path_info, relative=None): 86 if relative != 'server': 87 relative = bool(relative) 88 return cherrypy.url(path_info, relative=relative) 89 90 91 class Params(Test): 92 93 def index(self, thing): 94 return repr(thing) 95 96 def ismap(self, x, y): 97 return "Coordinates: %s, %s" % (x, y) 98 99 def default(self, *args, **kwargs): 100 return "args: %s kwargs: %s" % (args, kwargs) 101 102 class ParamErrors(Test): 103 104 def one_positional(self, param1): 105 return "data" 106 one_positional.exposed = True 107 108 def one_positional_args(self, param1, *args): 109 return "data" 110 one_positional_args.exposed = True 111 112 def one_positional_args_kwargs(self, param1, *args, **kwargs): 113 return "data" 114 one_positional_args_kwargs.exposed = True 115 116 def one_positional_kwargs(self, param1, **kwargs): 117 return "data" 118 one_positional_kwargs.exposed = True 119 120 def no_positional(self): 121 return "data" 122 no_positional.exposed = True 123 124 def no_positional_args(self, *args): 125 return "data" 126 no_positional_args.exposed = True 127 128 def no_positional_args_kwargs(self, *args, **kwargs): 129 return "data" 130 no_positional_args_kwargs.exposed = True 131 132 def no_positional_kwargs(self, **kwargs): 133 return "data" 134 no_positional_kwargs.exposed = True 135 136 137 class Status(Test): 138 139 def index(self): 140 return "normal" 141 142 def blank(self): 143 cherrypy.response.status = "" 144 145 # According to RFC 2616, new status codes are OK as long as they 146 # are between 100 and 599. 147 148 # Here is an illegal code... 149 def illegal(self): 150 cherrypy.response.status = 781 151 return "oops" 152 153 # ...and here is an unknown but legal code. 154 def unknown(self): 155 cherrypy.response.status = "431 My custom error" 156 return "funky" 157 158 # Non-numeric code 159 def bad(self): 160 cherrypy.response.status = "error" 161 return "bad news" 162 163 164 class Redirect(Test): 165 166 class Error: 167 _cp_config = {"tools.err_redirect.on": True, 168 "tools.err_redirect.url": "/errpage", 169 "tools.err_redirect.internal": False, 170 } 171 172 def index(self): 173 raise NameError("redirect_test") 174 index.exposed = True 175 error = Error() 176 177 def index(self): 178 return "child" 179 180 def by_code(self, code): 181 raise cherrypy.HTTPRedirect("somewhere else", code) 182 by_code._cp_config = {'tools.trailing_slash.extra': True} 183 184 def nomodify(self): 185 raise cherrypy.HTTPRedirect("", 304) 186 187 def proxy(self): 188 raise cherrypy.HTTPRedirect("proxy", 305) 189 190 def stringify(self): 191 return str(cherrypy.HTTPRedirect("/")) 192 193 def fragment(self, frag): 194 raise cherrypy.HTTPRedirect("/some/url#%s" % frag) 195 196 def login_redir(): 197 if not getattr(cherrypy.request, "login", None): 198 raise cherrypy.InternalRedirect("/internalredirect/login") 199 tools.login_redir = _cptools.Tool('before_handler', login_redir) 200 201 def redir_custom(): 202 raise cherrypy.InternalRedirect("/internalredirect/custom_err") 203 204 class InternalRedirect(Test): 205 206 def index(self): 207 raise cherrypy.InternalRedirect("/") 208 209 def choke(self): 210 return 3 / 0 211 choke.exposed = True 212 choke._cp_config = {'hooks.before_error_response': redir_custom} 213 214 def relative(self, a, b): 215 raise cherrypy.InternalRedirect("cousin?t=6") 216 217 def cousin(self, t): 218 assert cherrypy.request.prev.closed 219 return cherrypy.request.prev.query_string 220 221 def petshop(self, user_id): 222 if user_id == "parrot": 223 # Trade it for a slug when redirecting 224 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=slug') 225 elif user_id == "terrier": 226 # Trade it for a fish when redirecting 227 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=fish') 228 else: 229 # This should pass the user_id through to getImagesByUser 230 raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=%s' % user_id) 231 232 # We support Python 2.3, but the @-deco syntax would look like this: 233 # @tools.login_redir() 234 def secure(self): 235 return "Welcome!" 236 secure = tools.login_redir()(secure) 237 # Since calling the tool returns the same function you pass in, 238 # you could skip binding the return value, and just write: 239 # tools.login_redir()(secure) 240 241 def login(self): 242 return "Please log in" 243 244 def custom_err(self): 245 return "Something went horribly wrong." 246 247 def early_ir(self, arg): 248 return "whatever" 249 early_ir._cp_config = {'hooks.before_request_body': redir_custom} 250 251 252 class Image(Test): 253 254 def getImagesByUser(self, user_id): 255 return "0 images for %s" % user_id 256 257 258 class Flatten(Test): 259 260 def as_string(self): 261 return "content" 262 263 def as_list(self): 264 return ["con", "tent"] 265 266 def as_yield(self): 267 yield "content" 268 269 def as_dblyield(self): 270 yield self.as_yield() 271 as_dblyield._cp_config = {'tools.flatten.on': True} 272 273 def as_refyield(self): 274 for chunk in self.as_yield(): 275 yield chunk 276 277 278 def callable_error_page(status, **kwargs): 279 return "Error %s - Well, I'm very sorry but you haven't paid!" % status 280 281 282 class Error(Test): 283 284 _cp_config = {'tools.log_tracebacks.on': True, 285 } 286 287 def custom(self, err='404'): 288 raise cherrypy.HTTPError(int(err), "No, <b>really</b>, not found!") 289 custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html"), 290 'error_page.401': callable_error_page, 291 } 292 293 def custom_default(self): 294 return 1 + 'a' # raise an unexpected error 295 custom_default._cp_config = {'error_page.default': callable_error_page} 296 297 def noexist(self): 298 raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!") 299 noexist._cp_config = {'error_page.404': "nonexistent.html"} 300 301 def page_method(self): 302 raise ValueError() 303 304 def page_yield(self): 305 yield "howdy" 306 raise ValueError() 307 308 def page_streamed(self): 309 yield "word up" 310 raise ValueError() 311 yield "very oops" 312 page_streamed._cp_config = {"response.stream": True} 313 314 def cause_err_in_finalize(self): 315 # Since status must start with an int, this should error. 316 cherrypy.response.status = "ZOO OK" 317 cause_err_in_finalize._cp_config = {'request.show_tracebacks': False} 318 319 def rethrow(self): 320 """Test that an error raised here will be thrown out to the server.""" 321 raise ValueError() 322 rethrow._cp_config = {'request.throw_errors': True} 323 324 325 class Ranges(Test): 326 327 def get_ranges(self, bytes): 328 return repr(http.get_ranges('bytes=%s' % bytes, 8)) 329 330 def slice_file(self): 331 path = os.path.join(os.getcwd(), os.path.dirname(__file__)) 332 return static.serve_file(os.path.join(path, "static/index.html")) 333 334 335 class Expect(Test): 336 337 def expectation_failed(self): 338 expect = cherrypy.request.headers.elements("Expect") 339 if expect and expect[0].value != '100-continue': 340 raise cherrypy.HTTPError(400) 341 raise cherrypy.HTTPError(417, 'Expectation Failed') 342 343 class Headers(Test): 344 345 def default(self, headername): 346 """Spit back out the value for the requested header.""" 347 return cherrypy.request.headers[headername] 348 349 def doubledheaders(self): 350 # From http://www.cherrypy.org/ticket/165: 351 # "header field names should not be case sensitive sayes the rfc. 352 # if i set a headerfield in complete lowercase i end up with two 353 # header fields, one in lowercase, the other in mixed-case." 354 355 # Set the most common headers 356 hMap = cherrypy.response.headers 357 hMap['content-type'] = "text/html" 358 hMap['content-length'] = 18 359 hMap['server'] = 'CherryPy headertest' 360 hMap['location'] = ('%s://%s:%s/headers/' 361 % (cherrypy.request.local.ip, 362 cherrypy.request.local.port, 363 cherrypy.request.scheme)) 364 365 # Set a rare header for fun 366 hMap['Expires'] = 'Thu, 01 Dec 2194 16:00:00 GMT' 367 368 return "double header test" 369 370 def ifmatch(self): 371 val = cherrypy.request.headers['If-Match'] 372 cherrypy.response.headers['ETag'] = val 373 return repr(val) 374 375 376 class HeaderElements(Test): 377 378 def get_elements(self, headername): 379 e = cherrypy.request.headers.elements(headername) 380 return "\n".join([unicode(x) for x in e]) 381 382 383 class Method(Test): 384 385 def index(self): 386 m = cherrypy.request.method 387 if m in defined_http_methods or m == "CONNECT": 388 return m 389 390 if m == "LINK": 391 raise cherrypy.HTTPError(405) 392 else: 393 raise cherrypy.HTTPError(501) 394 395 def parameterized(self, data): 396 return data 397 398 def request_body(self): 399 # This should be a file object (temp file), 400 # which CP will just pipe back out if we tell it to. 401 return cherrypy.request.body 402 403 def reachable(self): 404 return "success" 405 406 class Divorce: 407 """HTTP Method handlers shouldn't collide with normal method names. 408 For example, a GET-handler shouldn't collide with a method named 'get'. 409 410 If you build HTTP method dispatching into CherryPy, rewrite this class 411 to use your new dispatch mechanism and make sure that: 412 "GET /divorce HTTP/1.1" maps to divorce.index() and 413 "GET /divorce/get?ID=13 HTTP/1.1" maps to divorce.get() 414 """ 415 416 documents = {} 417 418 def index(self): 419 yield "<h1>Choose your document</h1>\n" 420 yield "<ul>\n" 421 for id, contents in self.documents.iteritems(): 422 yield (" <li><a href='/divorce/get?ID=%s'>%s</a>: %s</li>\n" 423 % (id, id, contents)) 424 yield "</ul>" 425 index.exposed = True 426 427 def get(self, ID): 428 return ("Divorce document %s: %s" % 429 (ID, self.documents.get(ID, "empty"))) 430 get.exposed = True 431 432 root.divorce = Divorce() 433 434 435 class Cookies(Test): 436 437 def single(self, name): 438 cookie = cherrypy.request.cookie[name] 439 cherrypy.response.cookie[name] = cookie.value 440 441 def multiple(self, names): 442 for name in names: 443 cookie = cherrypy.request.cookie[name] 444 cherrypy.response.cookie[name] = cookie.value 445 446 447 class ThreadLocal(Test): 448 449 def index(self): 450 existing = repr(getattr(cherrypy.request, "asdf", None)) 451 cherrypy.request.asdf = "rassfrassin" 452 return existing 453 454 if sys.version_info >= (2, 5): 455 from cherrypy.test import py25 456 Root.expose_dec = py25.ExposeExamples() 457 458 cherrypy.config.update({ 459 'environment': 'test_suite', 460 'server.max_request_body_size': 200, 461 'server.max_request_header_size': 500, 462 }) 463 appconf = { 464 '/method': {'request.methods_with_bodies': ("POST", "PUT", "PROPFIND")}, 465 } 466 cherrypy.tree.mount(root, config=appconf) 467 468 469 # Client-side code # 470 471 from cherrypy.test import helper 472
473 -class CoreRequestHandlingTest(helper.CPWebCase):
474
475 - def testParams(self):
476 self.getPage("/params/?thing=a") 477 self.assertBody("'a'") 478 479 self.getPage("/params/?thing=a&thing=b&thing=c") 480 self.assertBody("['a', 'b', 'c']") 481 482 # Test friendly error message when given params are not accepted. 483 self.getPage("/params/?notathing=meeting") 484 self.assertInBody("Missing parameters: thing") 485 self.getPage("/params/?thing=meeting&notathing=meeting") 486 self.assertInBody("Unexpected query string parameters: notathing") 487 488 # Test "% HEX HEX"-encoded URL, param keys, and values 489 self.getPage("/params/%d4%20%e3/cheese?Gruy%E8re=Bulgn%e9ville") 490 self.assertBody(r"args: ('\xd4 \xe3', 'cheese') " 491 r"kwargs: {'Gruy\xe8re': 'Bulgn\xe9ville'}") 492 493 # Make sure that encoded = and & get parsed correctly 494 self.getPage("/params/code?url=http%3A//cherrypy.org/index%3Fa%3D1%26b%3D2") 495 self.assertBody(r"args: ('code',) " 496 r"kwargs: {'url': 'http://cherrypy.org/index?a=1&b=2'}") 497 498 # Test coordinates sent by <img ismap> 499 self.getPage("/params/ismap?223,114") 500 self.assertBody("Coordinates: 223, 114")
501
502 - def testParamErrors(self):
503 504 # test that all of the handlers work when given 505 # the correct parameters in order to ensure that the 506 # errors below aren't coming from some other source. 507 for uri in ( 508 '/paramerrors/one_positional?param1=foo', 509 '/paramerrors/one_positional_args?param1=foo', 510 '/paramerrors/one_positional_args/foo', 511 '/paramerrors/one_positional_args/foo/bar/baz', 512 '/paramerrors/one_positional_args_kwargs?param1=foo&param2=bar', 513 '/paramerrors/one_positional_args_kwargs/foo?param2=bar&param3=baz', 514 '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param2=bar&param3=baz', 515 '/paramerrors/one_positional_kwargs?param1=foo&param2=bar&param3=baz', 516 '/paramerrors/one_positional_kwargs/foo?param4=foo&param2=bar&param3=baz', 517 '/paramerrors/no_positional', 518 '/paramerrors/no_positional_args/foo', 519 '/paramerrors/no_positional_args/foo/bar/baz', 520 '/paramerrors/no_positional_args_kwargs?param1=foo&param2=bar', 521 '/paramerrors/no_positional_args_kwargs/foo?param2=bar', 522 '/paramerrors/no_positional_args_kwargs/foo/bar/baz?param2=bar&param3=baz', 523 '/paramerrors/no_positional_kwargs?param1=foo&param2=bar', 524 ): 525 self.getPage(uri) 526 self.assertStatus(200) 527 528 # query string parameters are part of the URI, so if they are wrong 529 # for a particular handler, the status MUST be a 404. 530 for uri in ( 531 '/paramerrors/one_positional', 532 '/paramerrors/one_positional?foo=foo', 533 '/paramerrors/one_positional/foo/bar/baz', 534 '/paramerrors/one_positional/foo?param1=foo', 535 '/paramerrors/one_positional/foo?param1=foo&param2=foo', 536 '/paramerrors/one_positional_args/foo?param1=foo&param2=foo', 537 '/paramerrors/one_positional_args/foo/bar/baz?param2=foo', 538 '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param1=bar&param3=baz', 539 '/paramerrors/one_positional_kwargs/foo?param1=foo&param2=bar&param3=baz', 540 '/paramerrors/no_positional/boo', 541 '/paramerrors/no_positional?param1=foo', 542 '/paramerrors/no_positional_args/boo?param1=foo', 543 '/paramerrors/no_positional_kwargs/boo?param1=foo', 544 ): 545 self.getPage(uri) 546 self.assertStatus(404) 547 548 # if body parameters are wrong, a 400 must be returned. 549 for uri, body in ( 550 ('/paramerrors/one_positional/foo', 'param1=foo',), 551 ('/paramerrors/one_positional/foo', 'param1=foo&param2=foo',), 552 ('/paramerrors/one_positional_args/foo', 'param1=foo&param2=foo',), 553 ('/paramerrors/one_positional_args/foo/bar/baz', 'param2=foo',), 554 ('/paramerrors/one_positional_args_kwargs/foo/bar/baz', 'param1=bar&param3=baz',), 555 ('/paramerrors/one_positional_kwargs/foo', 'param1=foo&param2=bar&param3=baz',), 556 ('/paramerrors/no_positional', 'param1=foo',), 557 ('/paramerrors/no_positional_args/boo', 'param1=foo',), 558 ): 559 self.getPage(uri, method='POST', body=body) 560 self.assertStatus(400) 561 562 563 # even if body parameters are wrong, if we get the uri wrong, then 564 # it's a 404 565 for uri, body in ( 566 ('/paramerrors/one_positional?param2=foo', 'param1=foo',), 567 ('/paramerrors/one_positional/foo/bar', 'param2=foo',), 568 ('/paramerrors/one_positional_args/foo/bar?param2=foo', 'param3=foo',), 569 ('/paramerrors/one_positional_kwargs/foo/bar', 'param2=bar&param3=baz',), 570 ('/paramerrors/no_positional?param1=foo', 'param2=foo',), 571 ('/paramerrors/no_positional_args/boo?param2=foo', 'param1=foo',), 572 ): 573 self.getPage(uri, method='POST', body=body) 574 self.assertStatus(404)
575 576
577 - def testStatus(self):
578 self.getPage("/status/") 579 self.assertBody('normal') 580 self.assertStatus(200) 581 582 self.getPage("/status/blank") 583 self.assertBody('') 584 self.assertStatus(200) 585 586 self.getPage("/status/illegal") 587 self.assertStatus(500) 588 msg = "Illegal response status from server (781 is out of range)." 589 self.assertErrorPage(500, msg) 590 591 if not getattr(cherrypy.server, 'using_apache', False): 592 self.getPage("/status/unknown") 593 self.assertBody('funky') 594 self.assertStatus(431) 595 596 self.getPage("/status/bad") 597 self.assertStatus(500) 598 msg = "Illegal response status from server ('error' is non-numeric)." 599 self.assertErrorPage(500, msg)
600
601 - def testSlashes(self):
602 # Test that requests for index methods without a trailing slash 603 # get redirected to the same URI path with a trailing slash. 604 # Make sure GET params are preserved. 605 self.getPage("/redirect?id=3") 606 self.assertStatus(('302 Found', '303 See Other')) 607 self.assertInBody("<a href='%s/redirect/?id=3'>" 608 "%s/redirect/?id=3</a>" % (self.base(), self.base())) 609 610 if self.prefix(): 611 # Corner case: the "trailing slash" redirect could be tricky if 612 # we're using a virtual root and the URI is "/vroot" (no slash). 613 self.getPage("") 614 self.assertStatus(('302 Found', '303 See Other')) 615 self.assertInBody("<a href='%s/'>%s/</a>" % 616 (self.base(), self.base())) 617 618 # Test that requests for NON-index methods WITH a trailing slash 619 # get redirected to the same URI path WITHOUT a trailing slash. 620 # Make sure GET params are preserved. 621 self.getPage("/redirect/by_code/?code=307") 622 self.assertStatus(('302 Found', '303 See Other')) 623 self.assertInBody("<a href='%s/redirect/by_code?code=307'>" 624 "%s/redirect/by_code?code=307</a>" 625 % (self.base(), self.base())) 626 627 # If the trailing_slash tool is off, CP should just continue 628 # as if the slashes were correct. But it needs some help 629 # inside cherrypy.url to form correct output. 630 self.getPage('/url?path_info=page1') 631 self.assertBody('%s/url/page1' % self.base()) 632 self.getPage('/url/leaf/?path_info=page1') 633 self.assertBody('%s/url/page1' % self.base())
634
635 - def testRedirect(self):
636 self.getPage("/redirect/") 637 self.assertBody('child') 638 self.assertStatus(200) 639 640 self.getPage("/redirect/by_code?code=300") 641 self.assertMatchesBody(r"<a href='(.*)somewhere else'>\1somewhere else</a>") 642 self.assertStatus(300) 643 644 self.getPage("/redirect/by_code?code=301") 645 self.assertMatchesBody(r"<a href='(.*)somewhere else'>\1somewhere else</a>") 646 self.assertStatus(301) 647 648 self.getPage("/redirect/by_code?code=302") 649 self.assertMatchesBody(r"<a href='(.*)somewhere else'>\1somewhere else</a>") 650 self.assertStatus(302) 651 652 self.getPage("/redirect/by_code?code=303") 653 self.assertMatchesBody(r"<a href='(.*)somewhere else'>\1somewhere else</a>") 654 self.assertStatus(303) 655 656 self.getPage("/redirect/by_code?code=307") 657 self.assertMatchesBody(r"<a href='(.*)somewhere else'>\1somewhere else</a>") 658 self.assertStatus(307) 659 660 self.getPage("/redirect/nomodify") 661 self.assertBody('') 662 self.assertStatus(304) 663 664 self.getPage("/redirect/proxy") 665 self.assertBody('') 666 self.assertStatus(305) 667 668 # HTTPRedirect on error 669 self.getPage("/redirect/error/") 670 self.assertStatus(('302 Found', '303 See Other')) 671 self.assertInBody('/errpage') 672 673 # Make sure str(HTTPRedirect()) works. 674 self.getPage("/redirect/stringify", protocol="HTTP/1.0") 675 self.assertStatus(200) 676 self.assertBody("(['%s/'], 302)" % self.base()) 677 if cherrypy.server.protocol_version == "HTTP/1.1": 678 self.getPage("/redirect/stringify", protocol="HTTP/1.1") 679 self.assertStatus(200) 680 self.assertBody("(['%s/'], 303)" % self.base()) 681 682 # check that #fragments are handled properly 683 # http://skrb.org/ietf/http_errata.html#location-fragments 684 frag = "foo" 685 self.getPage("/redirect/fragment/%s" % frag) 686 self.assertMatchesBody(r"<a href='(.*)\/some\/url\#%s'>\1\/some\/url\#%s</a>" % (frag, frag)) 687 loc = self.assertHeader('Location') 688 assert loc.endswith("#%s" % frag) 689 self.assertStatus(('302 Found', '303 See Other'))
690
691 - def test_InternalRedirect(self):
692 # InternalRedirect 693 self.getPage("/internalredirect/") 694 self.assertBody('hello') 695 self.assertStatus(200) 696 697 # Test passthrough 698 self.getPage("/internalredirect/petshop?user_id=Sir-not-appearing-in-this-film") 699 self.assertBody('0 images for Sir-not-appearing-in-this-film') 700 self.assertStatus(200) 701 702 # Test args 703 self.getPage("/internalredirect/petshop?user_id=parrot") 704 self.assertBody('0 images for slug') 705 self.assertStatus(200) 706 707 # Test POST 708 self.getPage("/internalredirect/petshop", method="POST", 709 body="user_id=terrier") 710 self.assertBody('0 images for fish') 711 self.assertStatus(200) 712 713 # Test ir before body read 714 self.getPage("/internalredirect/early_ir", method="POST", 715 body="arg=aha!") 716 self.assertBody("Something went horribly wrong.") 717 self.assertStatus(200) 718 719 self.getPage("/internalredirect/secure") 720 self.assertBody('Please log in') 721 self.assertStatus(200) 722 723 # Relative path in InternalRedirect. 724 # Also tests request.prev. 725 self.getPage("/internalredirect/relative?a=3&b=5") 726 self.assertBody("a=3&b=5") 727 self.assertStatus(200) 728 729 # InternalRedirect on error 730 self.getPage("/internalredirect/choke") 731 self.assertStatus(200) 732 self.assertBody("Something went horribly wrong.")
733
734 - def testFlatten(self):
735 for url in ["/flatten/as_string", "/flatten/as_list", 736 "/flatten/as_yield", "/flatten/as_dblyield", 737 "/flatten/as_refyield"]: 738 self.getPage(url) 739 self.assertBody('content')
740
741 - def testErrorHandling(self):
742 self.getPage("/error/missing") 743 self.assertStatus(404) 744 self.assertErrorPage(404, "The path '/error/missing' was not found.") 745 746 ignore = helper.webtest.ignored_exceptions 747 ignore.append(ValueError) 748 try: 749 valerr = '\n raise ValueError()\nValueError' 750 self.getPage("/error/page_method") 751 self.assertErrorPage(500, pattern=valerr) 752 753 self.getPage("/error/page_yield") 754 self.assertErrorPage(500, pattern=valerr) 755 756 if (cherrypy.server.protocol_version == "HTTP/1.0" or 757 getattr(cherrypy.server, "using_apache", False)): 758 self.getPage("/error/page_streamed") 759 # Because this error is raised after the response body has 760 # started, the status should not change to an error status. 761 self.assertStatus(200) 762 self.assertBody("word up") 763 else: 764 # Under HTTP/1.1, the chunked transfer-coding is used. 765 # The HTTP client will choke when the output is incomplete. 766 self.assertRaises((ValueError, IncompleteRead), self.getPage, 767 "/error/page_streamed") 768 769 # No traceback should be present 770 self.getPage("/error/cause_err_in_finalize") 771 msg = "Illegal response status from server ('ZOO' is non-numeric)." 772 self.assertErrorPage(500, msg, None) 773 finally: 774 ignore.pop() 775 776 # Test custom error page for a specific error. 777 self.getPage("/error/custom") 778 self.assertStatus(404) 779 self.assertBody("Hello, world\r\n" + (" " * 499)) 780 781 # Test custom error page for a specific error. 782 self.getPage("/error/custom?err=401") 783 self.assertStatus(401) 784 self.assertBody("Error 401 Unauthorized - Well, I'm very sorry but you haven't paid!") 785 786 # Test default custom error page. 787 self.getPage("/error/custom_default") 788 self.assertStatus(500) 789 self.assertBody("Error 500 Internal Server Error - Well, I'm very sorry but you haven't paid!".ljust(513)) 790 791 # Test error in custom error page (ticket #305). 792 # Note that the message is escaped for HTML (ticket #310). 793 self.getPage("/error/noexist") 794 self.assertStatus(404) 795 msg = ("No, &lt;b&gt;really&lt;/b&gt;, not found!<br />" 796 "In addition, the custom error page failed:\n<br />" 797 "IOError: [Errno 2] No such file or directory: 'nonexistent.html'") 798 self.assertInBody(msg) 799 800 if getattr(cherrypy.server, "using_apache", False): 801 pass 802 else: 803 # Test throw_errors (ticket #186). 804 self.getPage("/error/rethrow") 805 self.assertInBody("raise ValueError()")
806
807 - def testRanges(self):
808 self.getPage("/ranges/get_ranges?bytes=3-6") 809 self.assertBody("[(3, 7)]") 810 811 # Test multiple ranges and a suffix-byte-range-spec, for good measure. 812 self.getPage("/ranges/get_ranges?bytes=2-4,-1") 813 self.assertBody("[(2, 5), (7, 8)]") 814 815 # Get a partial file. 816 if cherrypy.server.protocol_version == "HTTP/1.1": 817 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')]) 818 self.assertStatus(206) 819 self.assertHeader("Content-Type", "text/html") 820 self.assertHeader("Content-Range", "bytes 2-5/14") 821 self.assertBody("llo,") 822 823 # What happens with overlapping ranges (and out of order, too)? 824 self.getPage("/ranges/slice_file", [('Range', 'bytes=4-6,2-5')]) 825 self.assertStatus(206) 826 ct = self.assertHeader("Content-Type") 827 expected_type = "multipart/byteranges; boundary=" 828 self.assert_(ct.startswith(expected_type)) 829 boundary = ct[len(expected_type):] 830 expected_body = ("\r\n--%s\r\n" 831 "Content-type: text/html\r\n" 832 "Content-range: bytes 4-6/14\r\n" 833 "\r\n" 834 "o, \r\n" 835 "--%s\r\n" 836 "Content-type: text/html\r\n" 837 "Content-range: bytes 2-5/14\r\n" 838 "\r\n" 839 "llo,\r\n" 840 "--%s--\r\n" % (boundary, boundary, boundary)) 841 self.assertBody(expected_body) 842 self.assertHeader("Content-Length") 843 844 # Test "416 Requested Range Not Satisfiable" 845 self.getPage("/ranges/slice_file", [('Range', 'bytes=2300-2900')]) 846 self.assertStatus(416) 847 # "When this status code is returned for a byte-range request, 848 # the response SHOULD include a Content-Range entity-header 849 # field specifying the current length of the selected resource" 850 self.assertHeader("Content-Range", "bytes */14") 851 elif cherrypy.server.protocol_version == "HTTP/1.0": 852 # Test Range behavior with HTTP/1.0 request 853 self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')]) 854 self.assertStatus(200) 855 self.assertBody("Hello, world\r\n")
856
857 - def testExpect(self):
858 e = ('Expect', '100-continue') 859 self.getPage("/headerelements/get_elements?headername=Expect", [e]) 860 self.assertBody('100-continue') 861 862 self.getPage("/expect/expectation_failed", [e]) 863 self.assertStatus(417)
864
865 - def testHeaderElements(self):
866 # Accept-* header elements should be sorted, with most preferred first. 867 h = [('Accept', 'audio/*; q=0.2, audio/basic')] 868 self.getPage("/headerelements/get_elements?headername=Accept", h) 869 self.assertStatus(200) 870 self.assertBody("audio/basic\n" 871 "audio/*;q=0.2") 872 873 h = [('Accept', 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')] 874 self.getPage("/headerelements/get_elements?headername=Accept", h) 875 self.assertStatus(200) 876 self.assertBody("text/x-c\n" 877 "text/html\n" 878 "text/x-dvi;q=0.8\n" 879 "text/plain;q=0.5") 880 881 # Test that more specific media ranges get priority. 882 h = [('Accept', 'text/*, text/html, text/html;level=1, */*')] 883 self.getPage("/headerelements/get_elements?headername=Accept", h) 884 self.assertStatus(200) 885 self.assertBody("text/html;level=1\n" 886 "text/html\n" 887 "text/*\n" 888 "*/*") 889 890 # Test Accept-Charset 891 h = [('Accept-Charset', 'iso-8859-5, unicode-1-1;q=0.8')] 892 self.getPage("/headerelements/get_elements?headername=Accept-Charset", h) 893 self.assertStatus("200 OK") 894 self.assertBody("iso-8859-5\n" 895 "unicode-1-1;q=0.8") 896 897 # Test Accept-Encoding 898 h = [('Accept-Encoding', 'gzip;q=1.0, identity; q=0.5, *;q=0')] 899 self.getPage("/headerelements/get_elements?headername=Accept-Encoding", h) 900 self.assertStatus("200 OK") 901 self.assertBody("gzip;q=1.0\n" 902 "identity;q=0.5\n" 903 "*;q=0") 904 905 # Test Accept-Language 906 h = [('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')] 907 self.getPage("/headerelements/get_elements?headername=Accept-Language", h) 908 self.assertStatus("200 OK") 909 self.assertBody("da\n" 910 "en-gb;q=0.8\n" 911 "en;q=0.7") 912 913 # Test malformed header parsing. See http://www.cherrypy.org/ticket/763. 914 self.getPage("/headerelements/get_elements?headername=Content-Type", 915 # Note the illegal trailing ";" 916 headers=[('Content-Type', 'text/html; charset=utf-8;')]) 917 self.assertStatus(200) 918 self.assertBody("text/html;charset=utf-8")
919
920 - def testHeaders(self):
921 # Tests that each header only appears once, regardless of case. 922 self.getPage("/headers/doubledheaders") 923 self.assertBody("double header test") 924 hnames = [name.title() for name, val in self.headers] 925 for key in ['Content-Length', 'Content-Type', 'Date', 926 'Expires', 'Location', 'Server']: 927 self.assertEqual(hnames.count(key), 1, self.headers) 928 929 if cherrypy.server.protocol_version == "HTTP/1.1": 930 # Test RFC-2047-encoded request and response header values 931 c = "=E2=84=ABngstr=C3=B6m" 932 self.getPage("/headers/ifmatch", [('If-Match', '=?utf-8?q?%s?=' % c)]) 933 self.assertBody("u'\\u212bngstr\\xf6m'") 934 self.assertHeader("ETag", '=?utf-8?b?4oSrbmdzdHLDtm0=?=') 935 936 # Test a *LONG* RFC-2047-encoded request and response header value 937 self.getPage("/headers/ifmatch", 938 [('If-Match', '=?utf-8?q?%s?=' % (c * 10))]) 939 self.assertBody("u'%s'" % ('\\u212bngstr\\xf6m' * 10)) 940 self.assertHeader("ETag", 941 '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt4oSrbmdzdHLDtm0=?=' 942 '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt4oSrbmdzdHLDtm0=?=' 943 '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2bQ==?=') 944 945 # Test that two request headers are collapsed into one. 946 # See http://www.cherrypy.org/ticket/542. 947 self.getPage("/headers/Accept-Charset", 948 headers=[("Accept-Charset", "iso-8859-5"), 949 ("Accept-Charset", "unicode-1-1;q=0.8")]) 950 self.assertBody("iso-8859-5, unicode-1-1;q=0.8") 951 952 # If we don't pass a Content-Type header, it should not be present 953 # in cherrypy.request.headers 954 self.getPage("/headers/Content-Type", 955 headers=[]) 956 self.assertStatus(500) 957 958 # If Content-Type is present in the request, it should be present in 959 # cherrypy.request.headers 960 self.getPage("/headers/Content-Type", 961 headers=[("Content-type", "application/json")]) 962 self.assertBody("application/json")
963
964 - def test_basic_HTTPMethods(self):
965 helper.webtest.methods_with_bodies = ("POST", "PUT", "PROPFIND") 966 967 # Test that all defined HTTP methods work. 968 for m in defined_http_methods: 969 self.getPage("/method/", method=m) 970 971 # HEAD requests should not return any body. 972 if m == "HEAD": 973 self.assertBody("") 974 elif m == "TRACE": 975 # Some HTTP servers (like modpy) have their own TRACE support 976 self.assertEqual(self.body[:5], "TRACE") 977 else: 978 self.assertBody(m) 979 980 # Request a PUT method with a form-urlencoded body 981 self.getPage("/method/parameterized", method="PUT", 982 body="data=on+top+of+other+things") 983 self.assertBody("on top of other things") 984 985 # Request a PUT method with a file body 986 b = "one thing on top of another" 987 h = [("Content-Type", "text/plain"), 988 ("Content-Length", str(len(b)))] 989 self.getPage("/method/request_body", headers=h, method="PUT", body=b) 990 self.assertStatus(200) 991 self.assertBody(b) 992 993 # Request a PUT method with a file body but no Content-Type. 994 # See http://www.cherrypy.org/ticket/790. 995 b = "one thing on top of another" 996 self.persistent = True 997 try: 998 conn = self.HTTP_CONN 999 ## conn.set_debuglevel(10) 1000 conn.putrequest("PUT", "/method/request_body", skip_host=True) 1001 conn.putheader("Host", self.HOST) 1002 conn.putheader('Content-Length', str(len(b))) 1003 conn.endheaders() 1004 conn.send(b) 1005 response = conn.response_class(conn.sock, method="PUT") 1006 response.begin() 1007 self.assertEqual(response.status, 200) 1008 self.body = response.read() 1009 self.assertBody(b) 1010 finally: 1011 self.persistent = False 1012 1013 # Request a PUT method with no body whatsoever (not an empty one). 1014 # See http://www.cherrypy.org/ticket/650. 1015 # Provide a C-T or webtest will provide one (and a C-L) for us. 1016 h = [("Content-Type", "text/plain")] 1017 self.getPage("/method/reachable", headers=h, method="PUT") 1018 self.assertStatus(411) 1019 1020 # Request a custom method with a request body 1021 b = ('<?xml version="1.0" encoding="utf-8" ?>\n\n' 1022 '<propfind xmlns="DAV:"><prop><getlastmodified/>' 1023 '</prop></propfind>') 1024 h = [('Content-Type', 'text/xml'), 1025 ('Content-Length', str(len(b)))] 1026 self.getPage("/method/request_body", headers=h, method="PROPFIND", body=b) 1027 self.assertStatus(200) 1028 self.assertBody(b) 1029 1030 # Request a disallowed method 1031 self.getPage("/method/", method="LINK") 1032 self.assertStatus(405) 1033 1034 # Request an unknown method 1035 self.getPage("/method/", method="SEARCH") 1036 self.assertStatus(501) 1037 1038 # For method dispatchers: make sure that an HTTP method doesn't 1039 # collide with a virtual path atom. If you build HTTP-method 1040 # dispatching into the core, rewrite these handlers to use 1041 # your dispatch idioms. 1042 self.getPage("/divorce/get?ID=13") 1043 self.assertBody('Divorce document 13: empty') 1044 self.assertStatus(200) 1045 self.getPage("/divorce/", method="GET") 1046 self.assertBody('<h1>Choose your document</h1>\n<ul>\n</ul>') 1047 self.assertStatus(200)
1048
1049 - def test_CONNECT_method(self):
1050 if getattr(cherrypy.server, "using_apache", False): 1051 print "skipped due to known Apache differences...", 1052 return 1053 1054 self.getPage("/method/", method="CONNECT") 1055 self.assertBody("CONNECT")
1056
1057 - def testFavicon(self):
1058 # favicon.ico is served by staticfile. 1059 icofilename = os.path.join(localDir, "../favicon.ico") 1060 icofile = open(icofilename, "rb") 1061 data = icofile.read() 1062 icofile.close() 1063 1064 self.getPage("/favicon.ico") 1065 self.assertBody(data)
1066
1067 - def testCookies(self):
1068 if sys.version_info >= (2, 5): 1069 header_value = lambda x: x 1070 else: 1071 header_value = lambda x: x+';' 1072 1073 self.getPage("/cookies/single?name=First", 1074 [('Cookie', 'First=Dinsdale;')]) 1075 self.assertHeader('Set-Cookie', header_value('First=Dinsdale')) 1076 1077 self.getPage("/cookies/multiple?names=First&names=Last", 1078 [('Cookie', 'First=Dinsdale; Last=Piranha;'), 1079 ]) 1080 self.assertHeader('Set-Cookie', header_value('First=Dinsdale')) 1081 self.assertHeader('Set-Cookie', header_value('Last=Piranha')) 1082 1083 self.getPage("/cookies/single?name=Something-With:Colon", 1084 [('Cookie', 'Something-With:Colon=some-value')]) 1085 self.assertStatus(400)
1086
1087 - def testMaxRequestSize(self):
1088 if getattr(cherrypy.server, "using_apache", False): 1089 print "skipped due to known Apache differences...", 1090 return 1091 1092 for size in (500, 5000, 50000): 1093 self.getPage("/", headers=[('From', "x" * 500)]) 1094 self.assertStatus(413) 1095 1096 # Test for http://www.cherrypy.org/ticket/421 1097 # (Incorrect border condition in readline of SizeCheckWrapper). 1098 # This hangs in rev 891 and earlier. 1099 lines256 = "x" * 248 1100 self.getPage("/", 1101 headers=[('Host', '%s:%s' % (self.HOST, self.PORT)), 1102 ('From', lines256)]) 1103 1104 # Test upload 1105 body = """--x 1106 Content-Disposition: form-data; name="file"; filename="hello.txt" 1107 Content-Type: text/plain 1108 1109 %s 1110 --x-- 1111 """ 1112 b = body % ("x" * 96) 1113 h = [("Content-type", "multipart/form-data; boundary=x"), 1114 ("Content-Length", len(b))] 1115 self.getPage('/upload', h, "POST", b) 1116 self.assertBody('Size: 96') 1117 1118 b = body % ("x" * 200) 1119 h = [("Content-type", "multipart/form-data; boundary=x"), 1120 ("Content-Length", len(b))] 1121 self.getPage('/upload', h, "POST", b) 1122 self.assertStatus(413)
1123
1124 - def testEmptyThreadlocals(self):
1125 results = [] 1126 for x in xrange(20): 1127 self.getPage("/threadlocal/") 1128 results.append(self.body) 1129 self.assertEqual(results, ["None"] * 20)
1130
1131 - def testDefaultContentType(self):
1132 self.getPage('/') 1133 self.assertHeader('Content-Type', 'text/html') 1134 self.getPage('/defct/plain') 1135 self.getPage('/') 1136 self.assertHeader('Content-Type', 'text/plain') 1137 self.getPage('/defct/html')
1138
1139 - def test_cherrypy_url(self):
1140 # Input relative to current 1141 self.getPage('/url/leaf?path_info=page1') 1142 self.assertBody('%s/url/page1' % self.base()) 1143 self.getPage('/url/?path_info=page1') 1144 self.assertBody('%s/url/page1' % self.base()) 1145 1146 # Input is 'absolute'; that is, relative to script_name 1147 self.getPage('/url/leaf?path_info=/page1') 1148 self.assertBody('%s/page1' % self.base()) 1149 self.getPage('/url/?path_info=/page1') 1150 self.assertBody('%s/page1' % self.base()) 1151 1152 # Single dots 1153 self.getPage('/url/leaf?path_info=./page1') 1154 self.assertBody('%s/url/page1' % self.base()) 1155 self.getPage('/url/leaf?path_info=other/./page1') 1156 self.assertBody('%s/url/other/page1' % self.base()) 1157 self.getPage('/url/?path_info=/other/./page1') 1158 self.assertBody('%s/other/page1' % self.base()) 1159 1160 # Double dots 1161 self.getPage('/url/leaf?path_info=../page1') 1162 self.assertBody('%s/page1' % self.base()) 1163 self.getPage('/url/leaf?path_info=other/../page1') 1164 self.assertBody('%s/url/page1' % self.base()) 1165 self.getPage('/url/leaf?path_info=/other/../page1') 1166 self.assertBody('%s/page1' % self.base()) 1167 1168 # Output relative to current path or script_name 1169 self.getPage('/url/?path_info=page1&relative=True') 1170 self.assertBody('page1') 1171 self.getPage('/url/leaf?path_info=/page1&relative=True') 1172 self.assertBody('../page1') 1173 self.getPage('/url/leaf?path_info=page1&relative=True') 1174 self.assertBody('page1') 1175 self.getPage('/url/leaf?path_info=leaf/page1&relative=True') 1176 self.assertBody('leaf/page1') 1177 self.getPage('/url/leaf?path_info=../page1&relative=True') 1178 self.assertBody('../page1') 1179 self.getPage('/url/?path_info=other/../page1&relative=True') 1180 self.assertBody('page1') 1181 1182 # Output relative to / 1183 self.getPage('/baseurl?path_info=ab&relative=True') 1184 self.assertBody('ab') 1185 # Output relative to / 1186 self.getPage('/baseurl?path_info=/ab&relative=True') 1187 self.assertBody('ab') 1188 1189 # absolute-path references ("server-relative") 1190 # Input relative to current 1191 self.getPage('/url/leaf?path_info=page1&relative=server') 1192 self.assertBody('/url/page1') 1193 self.getPage('/url/?path_info=page1&relative=server') 1194 self.assertBody('/url/page1') 1195 # Input is 'absolute'; that is, relative to script_name 1196 self.getPage('/url/leaf?path_info=/page1&relative=server') 1197 self.assertBody('/page1') 1198 self.getPage('/url/?path_info=/page1&relative=server') 1199 self.assertBody('/page1')
1200
1201 - def test_expose_decorator(self):
1202 if not sys.version_info >= (2, 5): 1203 print "skipped (Python 2.5+ only)", 1204 return 1205 1206 # Test @expose 1207 self.getPage("/expose_dec/no_call") 1208 self.assertStatus(200) 1209 self.assertBody("Mr E. R. Bradshaw") 1210 1211 # Test @expose() 1212 self.getPage("/expose_dec/call_empty") 1213 self.assertStatus(200) 1214 self.assertBody("Mrs. B.J. Smegma") 1215 1216 # Test @expose("alias") 1217 self.getPage("/expose_dec/call_alias") 1218 self.assertStatus(200) 1219 self.assertBody("Mr Nesbitt") 1220 # Does the original name work? 1221 self.getPage("/expose_dec/nesbitt") 1222 self.assertStatus(200) 1223 self.assertBody("Mr Nesbitt") 1224 1225 # Test @expose(["alias1", "alias2"]) 1226 self.getPage("/expose_dec/alias1") 1227 self.assertStatus(200) 1228 self.assertBody("Mr Ken Andrews") 1229 self.getPage("/expose_dec/alias2") 1230 self.assertStatus(200) 1231 self.assertBody("Mr Ken Andrews") 1232 # Does the original name work? 1233 self.getPage("/expose_dec/andrews") 1234 self.assertStatus(200) 1235 self.assertBody("Mr Ken Andrews") 1236 1237 # Test @expose(alias="alias") 1238 self.getPage("/expose_dec/alias3") 1239 self.assertStatus(200) 1240 self.assertBody("Mr. and Mrs. Watson")
1241 1242 1243 if __name__ == '__main__': 1244 setup_server() 1245 helper.testmain() 1246