1 """Basic tests for the cherrypy.Request object."""
2
3 import os
4 localDir = os.path.dirname(__file__)
5 import sys
6 import types
7 from cherrypy._cpcompat import IncompleteRead, ntob, ntou, unicodestr
8
9 import cherrypy
10 from cherrypy import _cptools, tools
11 from cherrypy.lib import httputil
12
13 defined_http_methods = ("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE",
14 "TRACE", "PROPFIND")
15
16
17
18
19 from cherrypy.test import helper
20
22
24 class Root:
25
26 def index(self):
27 return "hello"
28 index.exposed = True
29
30 def scheme(self):
31 return cherrypy.request.scheme
32 scheme.exposed = True
33
34 root = Root()
35
36
37 class TestType(type):
38 """Metaclass which automatically exposes all functions in each subclass,
39 and adds an instance of the subclass as an attribute of root.
40 """
41 def __init__(cls, name, bases, dct):
42 type.__init__(cls, name, bases, dct)
43 for value in dct.values():
44 if isinstance(value, types.FunctionType):
45 value.exposed = True
46 setattr(root, name.lower(), cls())
47 Test = TestType('Test', (object,), {})
48
49 class PathInfo(Test):
50
51 def default(self, *args):
52 return cherrypy.request.path_info
53
54 class Params(Test):
55
56 def index(self, thing):
57 return repr(thing)
58
59 def ismap(self, x, y):
60 return "Coordinates: %s, %s" % (x, y)
61
62 def default(self, *args, **kwargs):
63 return "args: %s kwargs: %s" % (args, kwargs)
64 default._cp_config = {'request.query_string_encoding': 'latin1'}
65
66
67 class ParamErrorsCallable(object):
68 exposed = True
69 def __call__(self):
70 return "data"
71
72 class ParamErrors(Test):
73
74 def one_positional(self, param1):
75 return "data"
76 one_positional.exposed = True
77
78 def one_positional_args(self, param1, *args):
79 return "data"
80 one_positional_args.exposed = True
81
82 def one_positional_args_kwargs(self, param1, *args, **kwargs):
83 return "data"
84 one_positional_args_kwargs.exposed = True
85
86 def one_positional_kwargs(self, param1, **kwargs):
87 return "data"
88 one_positional_kwargs.exposed = True
89
90 def no_positional(self):
91 return "data"
92 no_positional.exposed = True
93
94 def no_positional_args(self, *args):
95 return "data"
96 no_positional_args.exposed = True
97
98 def no_positional_args_kwargs(self, *args, **kwargs):
99 return "data"
100 no_positional_args_kwargs.exposed = True
101
102 def no_positional_kwargs(self, **kwargs):
103 return "data"
104 no_positional_kwargs.exposed = True
105
106 callable_object = ParamErrorsCallable()
107
108 def raise_type_error(self, **kwargs):
109 raise TypeError("Client Error")
110 raise_type_error.exposed = True
111
112 def raise_type_error_with_default_param(self, x, y=None):
113 return '%d' % 'a'
114 raise_type_error_with_default_param.exposed = True
115
116 def callable_error_page(status, **kwargs):
117 return "Error %s - Well, I'm very sorry but you haven't paid!" % status
118
119
120 class Error(Test):
121
122 _cp_config = {'tools.log_tracebacks.on': True,
123 }
124
125 def reason_phrase(self):
126 raise cherrypy.HTTPError("410 Gone fishin'")
127
128 def custom(self, err='404'):
129 raise cherrypy.HTTPError(int(err), "No, <b>really</b>, not found!")
130 custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html"),
131 'error_page.401': callable_error_page,
132 }
133
134 def custom_default(self):
135 return 1 + 'a'
136 custom_default._cp_config = {'error_page.default': callable_error_page}
137
138 def noexist(self):
139 raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!")
140 noexist._cp_config = {'error_page.404': "nonexistent.html"}
141
142 def page_method(self):
143 raise ValueError()
144
145 def page_yield(self):
146 yield "howdy"
147 raise ValueError()
148
149 def page_streamed(self):
150 yield "word up"
151 raise ValueError()
152 yield "very oops"
153 page_streamed._cp_config = {"response.stream": True}
154
155 def cause_err_in_finalize(self):
156
157 cherrypy.response.status = "ZOO OK"
158 cause_err_in_finalize._cp_config = {'request.show_tracebacks': False}
159
160 def rethrow(self):
161 """Test that an error raised here will be thrown out to the server."""
162 raise ValueError()
163 rethrow._cp_config = {'request.throw_errors': True}
164
165
166 class Expect(Test):
167
168 def expectation_failed(self):
169 expect = cherrypy.request.headers.elements("Expect")
170 if expect and expect[0].value != '100-continue':
171 raise cherrypy.HTTPError(400)
172 raise cherrypy.HTTPError(417, 'Expectation Failed')
173
174 class Headers(Test):
175
176 def default(self, headername):
177 """Spit back out the value for the requested header."""
178 return cherrypy.request.headers[headername]
179
180 def doubledheaders(self):
181
182
183
184
185
186
187 hMap = cherrypy.response.headers
188 hMap['content-type'] = "text/html"
189 hMap['content-length'] = 18
190 hMap['server'] = 'CherryPy headertest'
191 hMap['location'] = ('%s://%s:%s/headers/'
192 % (cherrypy.request.local.ip,
193 cherrypy.request.local.port,
194 cherrypy.request.scheme))
195
196
197 hMap['Expires'] = 'Thu, 01 Dec 2194 16:00:00 GMT'
198
199 return "double header test"
200
201 def ifmatch(self):
202 val = cherrypy.request.headers['If-Match']
203 assert isinstance(val, unicodestr)
204 cherrypy.response.headers['ETag'] = val
205 return val
206
207
208 class HeaderElements(Test):
209
210 def get_elements(self, headername):
211 e = cherrypy.request.headers.elements(headername)
212 return "\n".join([unicodestr(x) for x in e])
213
214
215 class Method(Test):
216
217 def index(self):
218 m = cherrypy.request.method
219 if m in defined_http_methods or m == "CONNECT":
220 return m
221
222 if m == "LINK":
223 raise cherrypy.HTTPError(405)
224 else:
225 raise cherrypy.HTTPError(501)
226
227 def parameterized(self, data):
228 return data
229
230 def request_body(self):
231
232
233 return cherrypy.request.body
234
235 def reachable(self):
236 return "success"
237
238 class Divorce:
239 """HTTP Method handlers shouldn't collide with normal method names.
240 For example, a GET-handler shouldn't collide with a method named 'get'.
241
242 If you build HTTP method dispatching into CherryPy, rewrite this class
243 to use your new dispatch mechanism and make sure that:
244 "GET /divorce HTTP/1.1" maps to divorce.index() and
245 "GET /divorce/get?ID=13 HTTP/1.1" maps to divorce.get()
246 """
247
248 documents = {}
249
250 def index(self):
251 yield "<h1>Choose your document</h1>\n"
252 yield "<ul>\n"
253 for id, contents in self.documents.items():
254 yield (" <li><a href='/divorce/get?ID=%s'>%s</a>: %s</li>\n"
255 % (id, id, contents))
256 yield "</ul>"
257 index.exposed = True
258
259 def get(self, ID):
260 return ("Divorce document %s: %s" %
261 (ID, self.documents.get(ID, "empty")))
262 get.exposed = True
263
264 root.divorce = Divorce()
265
266
267 class ThreadLocal(Test):
268
269 def index(self):
270 existing = repr(getattr(cherrypy.request, "asdf", None))
271 cherrypy.request.asdf = "rassfrassin"
272 return existing
273
274 appconf = {
275 '/method': {'request.methods_with_bodies': ("POST", "PUT", "PROPFIND")},
276 }
277 cherrypy.tree.mount(root, config=appconf)
278 setup_server = staticmethod(setup_server)
279
283
287
289
290 self.getPage("http://localhost/pathinfo/foo/bar")
291 self.assertBody("/pathinfo/foo/bar")
292
294 self.getPage("/params/?thing=a")
295 self.assertBody(repr(ntou("a")))
296
297 self.getPage("/params/?thing=a&thing=b&thing=c")
298 self.assertBody(repr([ntou('a'), ntou('b'), ntou('c')]))
299
300
301 cherrypy.config.update({"request.show_mismatched_params": True})
302 self.getPage("/params/?notathing=meeting")
303 self.assertInBody("Missing parameters: thing")
304 self.getPage("/params/?thing=meeting¬athing=meeting")
305 self.assertInBody("Unexpected query string parameters: notathing")
306
307
308 cherrypy.config.update({"request.show_mismatched_params": False})
309 self.getPage("/params/?notathing=meeting")
310 self.assertInBody("Not Found")
311 self.getPage("/params/?thing=meeting¬athing=meeting")
312 self.assertInBody("Not Found")
313
314
315 self.getPage("/params/%d4%20%e3/cheese?Gruy%E8re=Bulgn%e9ville")
316 self.assertBody("args: %s kwargs: %s" %
317 (('\xd4 \xe3', 'cheese'),
318 {'Gruy\xe8re': ntou('Bulgn\xe9ville')}))
319
320
321 self.getPage("/params/code?url=http%3A//cherrypy.org/index%3Fa%3D1%26b%3D2")
322 self.assertBody("args: %s kwargs: %s" %
323 (('code',),
324 {'url': ntou('http://cherrypy.org/index?a=1&b=2')}))
325
326
327 self.getPage("/params/ismap?223,114")
328 self.assertBody("Coordinates: 223, 114")
329
330
331 self.getPage("/params/dictlike?a[1]=1&a[2]=2&b=foo&b[bar]=baz")
332 self.assertBody("args: %s kwargs: %s" %
333 (('dictlike',),
334 {'a[1]': ntou('1'), 'b[bar]': ntou('baz'),
335 'b': ntou('foo'), 'a[2]': ntou('2')}))
336
338
339
340
341
342 for uri in (
343 '/paramerrors/one_positional?param1=foo',
344 '/paramerrors/one_positional_args?param1=foo',
345 '/paramerrors/one_positional_args/foo',
346 '/paramerrors/one_positional_args/foo/bar/baz',
347 '/paramerrors/one_positional_args_kwargs?param1=foo¶m2=bar',
348 '/paramerrors/one_positional_args_kwargs/foo?param2=bar¶m3=baz',
349 '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param2=bar¶m3=baz',
350 '/paramerrors/one_positional_kwargs?param1=foo¶m2=bar¶m3=baz',
351 '/paramerrors/one_positional_kwargs/foo?param4=foo¶m2=bar¶m3=baz',
352 '/paramerrors/no_positional',
353 '/paramerrors/no_positional_args/foo',
354 '/paramerrors/no_positional_args/foo/bar/baz',
355 '/paramerrors/no_positional_args_kwargs?param1=foo¶m2=bar',
356 '/paramerrors/no_positional_args_kwargs/foo?param2=bar',
357 '/paramerrors/no_positional_args_kwargs/foo/bar/baz?param2=bar¶m3=baz',
358 '/paramerrors/no_positional_kwargs?param1=foo¶m2=bar',
359 '/paramerrors/callable_object',
360 ):
361 self.getPage(uri)
362 self.assertStatus(200)
363
364
365
366 error_msgs = [
367 'Missing parameters',
368 'Nothing matches the given URI',
369 'Multiple values for parameters',
370 'Unexpected query string parameters',
371 'Unexpected body parameters',
372 ]
373 for uri, msg in (
374 ('/paramerrors/one_positional', error_msgs[0]),
375 ('/paramerrors/one_positional?foo=foo', error_msgs[0]),
376 ('/paramerrors/one_positional/foo/bar/baz', error_msgs[1]),
377 ('/paramerrors/one_positional/foo?param1=foo', error_msgs[2]),
378 ('/paramerrors/one_positional/foo?param1=foo¶m2=foo', error_msgs[2]),
379 ('/paramerrors/one_positional_args/foo?param1=foo¶m2=foo', error_msgs[2]),
380 ('/paramerrors/one_positional_args/foo/bar/baz?param2=foo', error_msgs[3]),
381 ('/paramerrors/one_positional_args_kwargs/foo/bar/baz?param1=bar¶m3=baz', error_msgs[2]),
382 ('/paramerrors/one_positional_kwargs/foo?param1=foo¶m2=bar¶m3=baz', error_msgs[2]),
383 ('/paramerrors/no_positional/boo', error_msgs[1]),
384 ('/paramerrors/no_positional?param1=foo', error_msgs[3]),
385 ('/paramerrors/no_positional_args/boo?param1=foo', error_msgs[3]),
386 ('/paramerrors/no_positional_kwargs/boo?param1=foo', error_msgs[1]),
387 ('/paramerrors/callable_object?param1=foo', error_msgs[3]),
388 ('/paramerrors/callable_object/boo', error_msgs[1]),
389 ):
390 for show_mismatched_params in (True, False):
391 cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params})
392 self.getPage(uri)
393 self.assertStatus(404)
394 if show_mismatched_params:
395 self.assertInBody(msg)
396 else:
397 self.assertInBody("Not Found")
398
399
400 for uri, body, msg in (
401 ('/paramerrors/one_positional/foo', 'param1=foo', error_msgs[2]),
402 ('/paramerrors/one_positional/foo', 'param1=foo¶m2=foo', error_msgs[2]),
403 ('/paramerrors/one_positional_args/foo', 'param1=foo¶m2=foo', error_msgs[2]),
404 ('/paramerrors/one_positional_args/foo/bar/baz', 'param2=foo', error_msgs[4]),
405 ('/paramerrors/one_positional_args_kwargs/foo/bar/baz', 'param1=bar¶m3=baz', error_msgs[2]),
406 ('/paramerrors/one_positional_kwargs/foo', 'param1=foo¶m2=bar¶m3=baz', error_msgs[2]),
407 ('/paramerrors/no_positional', 'param1=foo', error_msgs[4]),
408 ('/paramerrors/no_positional_args/boo', 'param1=foo', error_msgs[4]),
409 ('/paramerrors/callable_object', 'param1=foo', error_msgs[4]),
410 ):
411 for show_mismatched_params in (True, False):
412 cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params})
413 self.getPage(uri, method='POST', body=body)
414 self.assertStatus(400)
415 if show_mismatched_params:
416 self.assertInBody(msg)
417 else:
418 self.assertInBody("400 Bad")
419
420
421
422
423 for uri, body, msg in (
424 ('/paramerrors/one_positional?param2=foo', 'param1=foo', error_msgs[3]),
425 ('/paramerrors/one_positional/foo/bar', 'param2=foo', error_msgs[1]),
426 ('/paramerrors/one_positional_args/foo/bar?param2=foo', 'param3=foo', error_msgs[3]),
427 ('/paramerrors/one_positional_kwargs/foo/bar', 'param2=bar¶m3=baz', error_msgs[1]),
428 ('/paramerrors/no_positional?param1=foo', 'param2=foo', error_msgs[3]),
429 ('/paramerrors/no_positional_args/boo?param2=foo', 'param1=foo', error_msgs[3]),
430 ('/paramerrors/callable_object?param2=bar', 'param1=foo', error_msgs[3]),
431 ):
432 for show_mismatched_params in (True, False):
433 cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params})
434 self.getPage(uri, method='POST', body=body)
435 self.assertStatus(404)
436 if show_mismatched_params:
437 self.assertInBody(msg)
438 else:
439 self.assertInBody("Not Found")
440
441
442
443 for uri in (
444 '/paramerrors/raise_type_error',
445 '/paramerrors/raise_type_error_with_default_param?x=0',
446 '/paramerrors/raise_type_error_with_default_param?x=0&y=0',
447 ):
448 self.getPage(uri, method='GET')
449 self.assertStatus(500)
450 self.assertTrue('Client Error', self.body)
451
453 self.getPage("/error/missing")
454 self.assertStatus(404)
455 self.assertErrorPage(404, "The path '/error/missing' was not found.")
456
457 ignore = helper.webtest.ignored_exceptions
458 ignore.append(ValueError)
459 try:
460 valerr = '\n raise ValueError()\nValueError'
461 self.getPage("/error/page_method")
462 self.assertErrorPage(500, pattern=valerr)
463
464 self.getPage("/error/page_yield")
465 self.assertErrorPage(500, pattern=valerr)
466
467 if (cherrypy.server.protocol_version == "HTTP/1.0" or
468 getattr(cherrypy.server, "using_apache", False)):
469 self.getPage("/error/page_streamed")
470
471
472 self.assertStatus(200)
473 self.assertBody("word up")
474 else:
475
476
477 self.assertRaises((ValueError, IncompleteRead), self.getPage,
478 "/error/page_streamed")
479
480
481 self.getPage("/error/cause_err_in_finalize")
482 msg = "Illegal response status from server ('ZOO' is non-numeric)."
483 self.assertErrorPage(500, msg, None)
484 finally:
485 ignore.pop()
486
487
488 self.getPage('/error/reason_phrase')
489 self.assertStatus("410 Gone fishin'")
490
491
492 self.getPage("/error/custom")
493 self.assertStatus(404)
494 self.assertBody("Hello, world\r\n" + (" " * 499))
495
496
497 self.getPage("/error/custom?err=401")
498 self.assertStatus(401)
499 self.assertBody("Error 401 Unauthorized - Well, I'm very sorry but you haven't paid!")
500
501
502 self.getPage("/error/custom_default")
503 self.assertStatus(500)
504 self.assertBody("Error 500 Internal Server Error - Well, I'm very sorry but you haven't paid!".ljust(513))
505
506
507
508 self.getPage("/error/noexist")
509 self.assertStatus(404)
510 msg = ("No, <b>really</b>, not found!<br />"
511 "In addition, the custom error page failed:\n<br />"
512 "IOError: [Errno 2] No such file or directory: 'nonexistent.html'")
513 self.assertInBody(msg)
514
515 if getattr(cherrypy.server, "using_apache", False):
516 pass
517 else:
518
519 self.getPage("/error/rethrow")
520 self.assertInBody("raise ValueError()")
521
523 e = ('Expect', '100-continue')
524 self.getPage("/headerelements/get_elements?headername=Expect", [e])
525 self.assertBody('100-continue')
526
527 self.getPage("/expect/expectation_failed", [e])
528 self.assertStatus(417)
529
531
532 h = [('Accept', 'audio/*; q=0.2, audio/basic')]
533 self.getPage("/headerelements/get_elements?headername=Accept", h)
534 self.assertStatus(200)
535 self.assertBody("audio/basic\n"
536 "audio/*;q=0.2")
537
538 h = [('Accept', 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')]
539 self.getPage("/headerelements/get_elements?headername=Accept", h)
540 self.assertStatus(200)
541 self.assertBody("text/x-c\n"
542 "text/html\n"
543 "text/x-dvi;q=0.8\n"
544 "text/plain;q=0.5")
545
546
547 h = [('Accept', 'text/*, text/html, text/html;level=1, */*')]
548 self.getPage("/headerelements/get_elements?headername=Accept", h)
549 self.assertStatus(200)
550 self.assertBody("text/html;level=1\n"
551 "text/html\n"
552 "text/*\n"
553 "*/*")
554
555
556 h = [('Accept-Charset', 'iso-8859-5, unicode-1-1;q=0.8')]
557 self.getPage("/headerelements/get_elements?headername=Accept-Charset", h)
558 self.assertStatus("200 OK")
559 self.assertBody("iso-8859-5\n"
560 "unicode-1-1;q=0.8")
561
562
563 h = [('Accept-Encoding', 'gzip;q=1.0, identity; q=0.5, *;q=0')]
564 self.getPage("/headerelements/get_elements?headername=Accept-Encoding", h)
565 self.assertStatus("200 OK")
566 self.assertBody("gzip;q=1.0\n"
567 "identity;q=0.5\n"
568 "*;q=0")
569
570
571 h = [('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')]
572 self.getPage("/headerelements/get_elements?headername=Accept-Language", h)
573 self.assertStatus("200 OK")
574 self.assertBody("da\n"
575 "en-gb;q=0.8\n"
576 "en;q=0.7")
577
578
579 self.getPage("/headerelements/get_elements?headername=Content-Type",
580
581 headers=[('Content-Type', 'text/html; charset=utf-8;')])
582 self.assertStatus(200)
583 self.assertBody("text/html;charset=utf-8")
584
586
587
588 self.getPage("/headers/Accept-Charset",
589 headers=[("Accept-Charset", "iso-8859-5"),
590 ("Accept-Charset", "unicode-1-1;q=0.8")])
591 self.assertBody("iso-8859-5, unicode-1-1;q=0.8")
592
593
594 self.getPage("/headers/doubledheaders")
595 self.assertBody("double header test")
596 hnames = [name.title() for name, val in self.headers]
597 for key in ['Content-Length', 'Content-Type', 'Date',
598 'Expires', 'Location', 'Server']:
599 self.assertEqual(hnames.count(key), 1, self.headers)
600
602
603 self.assertEqual(httputil.decode_TEXT(ntou("=?utf-8?q?f=C3=BCr?=")), ntou("f\xfcr"))
604
605 if cherrypy.server.protocol_version == "HTTP/1.1":
606
607 u = ntou('\u212bngstr\xf6m', 'escape')
608 c = ntou("=E2=84=ABngstr=C3=B6m")
609 self.getPage("/headers/ifmatch", [('If-Match', ntou('=?utf-8?q?%s?=') % c)])
610
611 self.assertBody(ntob("\xe2\x84\xabngstr\xc3\xb6m"))
612
613 self.assertHeader("ETag", ntou('=?utf-8?b?4oSrbmdzdHLDtm0=?='))
614
615
616 self.getPage("/headers/ifmatch",
617 [('If-Match', ntou('=?utf-8?q?%s?=') % (c * 10))])
618 self.assertBody(ntob("\xe2\x84\xabngstr\xc3\xb6m") * 10)
619
620 etag = self.assertHeader("ETag",
621 '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt'
622 '4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt'
623 '4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt'
624 '4oSrbmdzdHLDtm0=?=')
625 self.assertEqual(httputil.decode_TEXT(etag), u * 10)
626
639
641 helper.webtest.methods_with_bodies = ("POST", "PUT", "PROPFIND")
642
643
644 for m in defined_http_methods:
645 self.getPage("/method/", method=m)
646
647
648 if m == "HEAD":
649 self.assertBody("")
650 elif m == "TRACE":
651
652 self.assertEqual(self.body[:5], ntob("TRACE"))
653 else:
654 self.assertBody(m)
655
656
657 self.getPage("/method/parameterized", method="PUT",
658 body="data=on+top+of+other+things")
659 self.assertBody("on top of other things")
660
661
662 b = "one thing on top of another"
663 h = [("Content-Type", "text/plain"),
664 ("Content-Length", str(len(b)))]
665 self.getPage("/method/request_body", headers=h, method="PUT", body=b)
666 self.assertStatus(200)
667 self.assertBody(b)
668
669
670
671 b = ntob("one thing on top of another")
672 self.persistent = True
673 try:
674 conn = self.HTTP_CONN
675 conn.putrequest("PUT", "/method/request_body", skip_host=True)
676 conn.putheader("Host", self.HOST)
677 conn.putheader('Content-Length', str(len(b)))
678 conn.endheaders()
679 conn.send(b)
680 response = conn.response_class(conn.sock, method="PUT")
681 response.begin()
682 self.assertEqual(response.status, 200)
683 self.body = response.read()
684 self.assertBody(b)
685 finally:
686 self.persistent = False
687
688
689
690
691 h = [("Content-Type", "text/plain")]
692 self.getPage("/method/reachable", headers=h, method="PUT")
693 self.assertStatus(411)
694
695
696 b = ('<?xml version="1.0" encoding="utf-8" ?>\n\n'
697 '<propfind xmlns="DAV:"><prop><getlastmodified/>'
698 '</prop></propfind>')
699 h = [('Content-Type', 'text/xml'),
700 ('Content-Length', str(len(b)))]
701 self.getPage("/method/request_body", headers=h, method="PROPFIND", body=b)
702 self.assertStatus(200)
703 self.assertBody(b)
704
705
706 self.getPage("/method/", method="LINK")
707 self.assertStatus(405)
708
709
710 self.getPage("/method/", method="SEARCH")
711 self.assertStatus(501)
712
713
714
715
716
717 self.getPage("/divorce/get?ID=13")
718 self.assertBody('Divorce document 13: empty')
719 self.assertStatus(200)
720 self.getPage("/divorce/", method="GET")
721 self.assertBody('<h1>Choose your document</h1>\n<ul>\n</ul>')
722 self.assertStatus(200)
723
725 if getattr(cherrypy.server, "using_apache", False):
726 return self.skip("skipped due to known Apache differences... ")
727
728 self.getPage("/method/", method="CONNECT")
729 self.assertBody("CONNECT")
730
732 results = []
733 for x in range(20):
734 self.getPage("/threadlocal/")
735 results.append(self.body)
736 self.assertEqual(results, [ntob("None")] * 20)
737