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