1import pytest 2 3import json 4 5from webob.request import Request 6from webob.dec import wsgify 7from webob import exc as webob_exc 8 9@wsgify 10def method_not_allowed_app(req): 11 if req.method != 'GET': 12 raise webob_exc.HTTPMethodNotAllowed() 13 return 'hello!' 14 15def test_noescape_null(): 16 assert webob_exc.no_escape(None) == '' 17 18def test_noescape_not_basestring(): 19 assert webob_exc.no_escape(42) == '42' 20 21def test_noescape_unicode(): 22 class DummyUnicodeObject(object): 23 def __unicode__(self): 24 return '42' 25 duo = DummyUnicodeObject() 26 assert webob_exc.no_escape(duo) == '42' 27 28def test_strip_tags_empty(): 29 assert webob_exc.strip_tags('') == '' 30 31def test_strip_tags_newline_to_space(): 32 assert webob_exc.strip_tags('a\nb') == 'a b' 33 34def test_strip_tags_zaps_carriage_return(): 35 assert webob_exc.strip_tags('a\rb') == 'ab' 36 37def test_strip_tags_br_to_newline(): 38 assert webob_exc.strip_tags('a<br/>b') == 'a\nb' 39 40def test_strip_tags_zaps_comments(): 41 assert webob_exc.strip_tags('a<!--b-->') == 'ab' 42 43def test_strip_tags_zaps_tags(): 44 assert webob_exc.strip_tags('foo<bar>baz</bar>') == 'foobaz' 45 46def test_HTTPException(): 47 _called = [] 48 _result = object() 49 def _response(environ, start_response): 50 _called.append((environ, start_response)) 51 return _result 52 environ = {} 53 start_response = object() 54 exc = webob_exc.HTTPException('testing', _response) 55 assert exc.wsgi_response is _response 56 result = exc(environ, start_response) 57 assert result is result 58 assert _called == [(environ, start_response)] 59 60def test_exception_with_unicode_data(): 61 req = Request.blank('/', method='POST') 62 res = req.get_response(method_not_allowed_app) 63 assert res.status_code == 405 64 65def test_WSGIHTTPException_headers(): 66 exc = webob_exc.WSGIHTTPException(headers=[('Set-Cookie', 'a=1'), 67 ('Set-Cookie', 'a=2')]) 68 mixed = exc.headers.mixed() 69 assert mixed['set-cookie'] == ['a=1', 'a=2'] 70 71def test_WSGIHTTPException_w_body_template(): 72 from string import Template 73 TEMPLATE = '$foo: $bar' 74 exc = webob_exc.WSGIHTTPException(body_template=TEMPLATE) 75 assert exc.body_template == TEMPLATE 76 assert isinstance(exc.body_template_obj, Template) 77 assert exc.body_template_obj.substitute({'foo': 'FOO', 'bar': 'BAR'}) == 'FOO: BAR' 78 79def test_WSGIHTTPException_w_empty_body(): 80 class EmptyOnly(webob_exc.WSGIHTTPException): 81 empty_body = True 82 exc = EmptyOnly(content_type='text/plain', content_length=234) 83 assert 'content_type' not in exc.__dict__ 84 assert 'content_length' not in exc.__dict__ 85 86def test_WSGIHTTPException___str__(): 87 exc1 = webob_exc.WSGIHTTPException(detail='Detail') 88 assert str(exc1) == 'Detail' 89 class Explain(webob_exc.WSGIHTTPException): 90 explanation = 'Explanation' 91 assert str(Explain()) == 'Explanation' 92 93def test_WSGIHTTPException_plain_body_no_comment(): 94 class Explain(webob_exc.WSGIHTTPException): 95 code = '999' 96 title = 'Testing' 97 explanation = 'Explanation' 98 exc = Explain(detail='Detail') 99 assert exc.plain_body({}) == '999 Testing\n\nExplanation\n\n Detail ' 100 101def test_WSGIHTTPException_html_body_w_comment(): 102 class Explain(webob_exc.WSGIHTTPException): 103 code = '999' 104 title = 'Testing' 105 explanation = 'Explanation' 106 exc = Explain(detail='Detail', comment='Comment') 107 assert exc.html_body({}) == ( 108 '<html>\n' 109 ' <head>\n' 110 ' <title>999 Testing</title>\n' 111 ' </head>\n' 112 ' <body>\n' 113 ' <h1>999 Testing</h1>\n' 114 ' Explanation<br /><br />\n' 115 'Detail\n' 116 '<!-- Comment -->\n\n' 117 ' </body>\n' 118 '</html>' 119 ) 120 121def test_WSGIHTTPException_json_body_no_comment(): 122 class ValidationError(webob_exc.WSGIHTTPException): 123 code = '422' 124 title = 'Validation Failed' 125 explanation = 'Validation of an attribute failed.' 126 127 exc = ValidationError(detail='Attribute "xyz" is invalid.') 128 body = exc.json_body({}) 129 assert json.loads(body) == { 130 "code": "422 Validation Failed", 131 "title": "Validation Failed", 132 "message": "Validation of an attribute failed.<br /><br />\nAttribute" 133 ' "xyz" is invalid.\n\n', 134 } 135 136def test_WSGIHTTPException_respects_application_json(): 137 class ValidationError(webob_exc.WSGIHTTPException): 138 code = '422' 139 title = 'Validation Failed' 140 explanation = 'Validation of an attribute failed.' 141 def start_response(status, headers, exc_info=None): 142 # check that json doesn't contain a charset 143 assert ('Content-Type', 'application/json') in headers 144 pass 145 146 exc = ValidationError(detail='Attribute "xyz" is invalid.') 147 resp = exc.generate_response(environ={ 148 'wsgi.url_scheme': 'HTTP', 149 'SERVER_NAME': 'localhost', 150 'SERVER_PORT': '80', 151 'REQUEST_METHOD': 'PUT', 152 'HTTP_ACCEPT': 'application/json', 153 }, start_response=start_response) 154 assert json.loads(resp[0].decode('utf-8')) == { 155 "code": "422 Validation Failed", 156 "title": "Validation Failed", 157 "message": "Validation of an attribute failed.<br /><br />\nAttribute" 158 ' "xyz" is invalid.\n\n', 159 } 160 161def test_WSGIHTTPException_respects_accept_text_html(): 162 def start_response(status, headers, exc_info=None): 163 for header in headers: 164 if header[0] == 'Content-Type': 165 assert header[1].startswith('text/html') 166 167 exc = webob_exc.WSGIHTTPException() 168 resp = exc.generate_response(environ={ 169 'wsgi.url_scheme': 'HTTP', 170 'SERVER_NAME': 'localhost', 171 'SERVER_PORT': '80', 172 'REQUEST_METHOD': 'GET', 173 'HTTP_ACCEPT': 'text/html', 174 }, start_response=start_response) 175 176def test_WSGIHTTPException_respects_accept_text_plain(): 177 def start_response(status, headers, exc_info=None): 178 for header in headers: 179 if header[0] == 'Content-Type': 180 assert header[1].startswith('text/plain') 181 182 exc = webob_exc.WSGIHTTPException() 183 resp = exc.generate_response(environ={ 184 'wsgi.url_scheme': 'HTTP', 185 'SERVER_NAME': 'localhost', 186 'SERVER_PORT': '80', 187 'REQUEST_METHOD': 'GET', 188 'HTTP_ACCEPT': 'text/plain', 189 }, start_response=start_response) 190 191def test_WSGIHTTPException_respects_accept_star_star(): 192 def start_response(status, headers, exc_info=None): 193 for header in headers: 194 if header[0] == 'Content-Type': 195 assert header[1].startswith('text/html') 196 197 exc = webob_exc.WSGIHTTPException() 198 resp = exc.generate_response(environ={ 199 'wsgi.url_scheme': 'HTTP', 200 'SERVER_NAME': 'localhost', 201 'SERVER_PORT': '80', 202 'REQUEST_METHOD': 'GET', 203 'HTTP_ACCEPT': '*/*', 204 }, start_response=start_response) 205 206def test_WSGIHTTPException_allows_custom_json_formatter(): 207 def json_formatter(body, status, title, environ): 208 return {"fake": True} 209 class ValidationError(webob_exc.WSGIHTTPException): 210 code = '422' 211 title = 'Validation Failed' 212 explanation = 'Validation of an attribute failed.' 213 214 exc = ValidationError(detail='Attribute "xyz" is invalid.', 215 json_formatter=json_formatter) 216 body = exc.json_body({}) 217 assert json.loads(body) == {"fake": True} 218 219def test_WSGIHTTPException_generate_response(): 220 def start_response(status, headers, exc_info=None): 221 assert ('Content-Type', 'text/html; charset=UTF-8') in headers 222 pass 223 environ = { 224 'wsgi.url_scheme': 'HTTP', 225 'SERVER_NAME': 'localhost', 226 'SERVER_PORT': '80', 227 'REQUEST_METHOD': 'PUT', 228 'HTTP_ACCEPT': 'text/html' 229 } 230 excep = webob_exc.WSGIHTTPException() 231 assert excep(environ, start_response) == [ 232 b'<html>\n' 233 b' <head>\n' 234 b' <title>500 Internal Server Error</title>\n' 235 b' </head>\n' 236 b' <body>\n' 237 b' <h1>500 Internal Server Error</h1>\n' 238 b' <br /><br />\n' 239 b'\n' 240 b'\n\n' 241 b' </body>\n' 242 b'</html>'] 243 244def test_WSGIHTTPException_call_w_body(): 245 def start_response(status, headers, exc_info=None): 246 pass 247 environ = { 248 'wsgi.url_scheme': 'HTTP', 249 'SERVER_NAME': 'localhost', 250 'SERVER_PORT': '80', 251 'REQUEST_METHOD': 'PUT' 252 } 253 excep = webob_exc.WSGIHTTPException() 254 excep.body = b'test' 255 assert excep(environ,start_response) == [b'test'] 256 257 258def test_WSGIHTTPException_wsgi_response(): 259 def start_response(status, headers, exc_info=None): 260 pass 261 environ = { 262 'wsgi.url_scheme': 'HTTP', 263 'SERVER_NAME': 'localhost', 264 'SERVER_PORT': '80', 265 'REQUEST_METHOD': 'HEAD' 266 } 267 excep = webob_exc.WSGIHTTPException() 268 assert excep.wsgi_response(environ,start_response) == [] 269 270def test_WSGIHTTPException_exception_newstyle(): 271 def start_response(status, headers, exc_info=None): 272 pass 273 environ = { 274 'wsgi.url_scheme': 'HTTP', 275 'SERVER_NAME': 'localhost', 276 'SERVER_PORT': '80', 277 'REQUEST_METHOD': 'HEAD' 278 } 279 excep = webob_exc.WSGIHTTPException() 280 webob_exc.newstyle_exceptions = True 281 assert excep(environ,start_response) == [] 282 283def test_WSGIHTTPException_exception_no_newstyle(): 284 def start_response(status, headers, exc_info=None): 285 pass 286 environ = { 287 'wsgi.url_scheme': 'HTTP', 288 'SERVER_NAME': 'localhost', 289 'SERVER_PORT': '80', 290 'REQUEST_METHOD': 'HEAD' 291 } 292 excep = webob_exc.WSGIHTTPException() 293 webob_exc.newstyle_exceptions = False 294 assert excep(environ,start_response) == [] 295 296def test_HTTPOk_head_of_proxied_head(): 297 # first set up a response to a HEAD request 298 HELLO_WORLD = "Hi!\n" 299 CONTENT_TYPE = "application/hello" 300 def head_app(environ, start_response): 301 """An application object that understands HEAD""" 302 status = '200 OK' 303 response_headers = [('Content-Type', CONTENT_TYPE), 304 ('Content-Length', len(HELLO_WORLD))] 305 start_response(status, response_headers) 306 307 if environ['REQUEST_METHOD'] == 'HEAD': 308 return [] 309 else: 310 return [HELLO_WORLD] 311 312 def verify_response(resp, description): 313 assert resp.content_type == CONTENT_TYPE, description 314 assert resp.content_length == len(HELLO_WORLD), description 315 assert resp.body == b'', description 316 317 req = Request.blank('/', method='HEAD') 318 resp1 = req.get_response(head_app) 319 verify_response(resp1, "first response") 320 321 # Copy the response like a proxy server would. 322 # Copying an empty body has set content_length 323 # so copy the headers only afterwards. 324 resp2 = webob_exc.status_map[resp1.status_int](request=req) 325 resp2.body = resp1.body 326 resp2.headerlist = resp1.headerlist 327 verify_response(resp2, "copied response") 328 329 # evaluate it again 330 resp3 = req.get_response(resp2) 331 verify_response(resp3, "evaluated copy") 332 333def test_HTTPMove(): 334 def start_response(status, headers, exc_info=None): 335 pass 336 environ = { 337 'wsgi.url_scheme': 'HTTP', 338 'SERVER_NAME': 'localhost', 339 'SERVER_PORT': '80', 340 'REQUEST_METHOD': 'HEAD', 341 'PATH_INFO': '/', 342 } 343 m = webob_exc._HTTPMove() 344 assert m( environ, start_response ) == [] 345 346def test_HTTPMove_location_not_none(): 347 def start_response(status, headers, exc_info=None): 348 pass 349 environ = { 350 'wsgi.url_scheme': 'HTTP', 351 'SERVER_NAME': 'localhost', 352 'SERVER_PORT': '80', 353 'REQUEST_METHOD': 'HEAD', 354 'PATH_INFO': '/', 355 } 356 m = webob_exc._HTTPMove(location='http://example.com') 357 assert m( environ, start_response ) == [] 358 359def test_HTTPMove_location_newlines(): 360 environ = { 361 'wsgi.url_scheme': 'HTTP', 362 'SERVER_NAME': 'localhost', 363 'SERVER_PORT': '80', 364 'REQUEST_METHOD': 'HEAD', 365 'PATH_INFO': '/', 366 } 367 with pytest.raises(ValueError): 368 webob_exc._HTTPMove(location='http://example.com\r\nX-Test: false') 369 370def test_HTTPMove_add_slash_and_location(): 371 def start_response(status, headers, exc_info=None): 372 pass 373 with pytest.raises(TypeError): 374 webob_exc._HTTPMove(location='http://example.com', add_slash=True) 375 376def test_HTTPMove_call_add_slash(): 377 def start_response(status, headers, exc_info=None): 378 pass 379 environ = { 380 'wsgi.url_scheme': 'HTTP', 381 'SERVER_NAME': 'localhost', 382 'SERVER_PORT': '80', 383 'REQUEST_METHOD': 'HEAD', 384 'PATH_INFO': '/', 385 } 386 m = webob_exc._HTTPMove() 387 m.add_slash = True 388 assert m( environ, start_response ) == [] 389 390def test_HTTPMove_call_query_string(): 391 def start_response(status, headers, exc_info=None): 392 pass 393 environ = { 394 'wsgi.url_scheme': 'HTTP', 395 'SERVER_NAME': 'localhost', 396 'SERVER_PORT': '80', 397 'REQUEST_METHOD': 'HEAD' 398 } 399 m = webob_exc._HTTPMove() 400 m.add_slash = True 401 environ[ 'QUERY_STRING' ] = 'querystring' 402 environ['PATH_INFO'] = '/' 403 assert m( environ, start_response ) == [] 404 405def test_HTTPFound_unused_environ_variable(): 406 class Crashy(object): 407 def __str__(self): 408 raise Exception('I crashed!') 409 410 def start_response(status, headers, exc_info=None): 411 pass 412 environ = { 413 'wsgi.url_scheme': 'HTTP', 414 'SERVER_NAME': 'localhost', 415 'SERVER_PORT': '80', 416 'REQUEST_METHOD': 'GET', 417 'PATH_INFO': '/', 418 'HTTP_ACCEPT': 'text/html', 419 'crashy': Crashy() 420 } 421 422 m = webob_exc._HTTPMove(location='http://www.example.com') 423 assert m(environ, start_response) == [ 424 b'<html>\n' 425 b' <head>\n' 426 b' <title>500 Internal Server Error</title>\n' 427 b' </head>\n' 428 b' <body>\n' 429 b' <h1>500 Internal Server Error</h1>\n' 430 b' The resource has been moved to ' 431 b'<a href="http://www.example.com">' 432 b'http://www.example.com</a>;\n' 433 b'you should be redirected automatically.\n' 434 b'\n\n' 435 b' </body>\n' 436 b'</html>'] 437 438def test_HTTPExceptionMiddleware_ok(): 439 def app(environ, start_response): 440 return '123' 441 application = app 442 m = webob_exc.HTTPExceptionMiddleware(application) 443 environ = {} 444 start_response = None 445 res = m(environ, start_response) 446 assert res == '123' 447 448def test_HTTPExceptionMiddleware_exception(): 449 def wsgi_response(environ, start_response): 450 return '123' 451 def app(environ, start_response): 452 raise webob_exc.HTTPException(None, wsgi_response) 453 application = app 454 m = webob_exc.HTTPExceptionMiddleware(application) 455 environ = {} 456 start_response = None 457 res = m(environ, start_response) 458 assert res == '123' 459 460def test_HTTPExceptionMiddleware_exception_exc_info_none(): 461 class DummySys: 462 def exc_info(self): 463 return None 464 def wsgi_response(environ, start_response): 465 return start_response('200 OK', [], exc_info=None) 466 def app(environ, start_response): 467 raise webob_exc.HTTPException(None, wsgi_response) 468 application = app 469 m = webob_exc.HTTPExceptionMiddleware(application) 470 environ = {} 471 def start_response(status, headers, exc_info): 472 pass 473 try: 474 old_sys = webob_exc.sys 475 sys = DummySys() 476 res = m(environ, start_response) 477 assert res is None 478 finally: 479 webob_exc.sys = old_sys 480 481def test_status_map_is_deterministic(): 482 for code, cls in ( 483 (200, webob_exc.HTTPOk), 484 (201, webob_exc.HTTPCreated), 485 (202, webob_exc.HTTPAccepted), 486 (203, webob_exc.HTTPNonAuthoritativeInformation), 487 (204, webob_exc.HTTPNoContent), 488 (205, webob_exc.HTTPResetContent), 489 (206, webob_exc.HTTPPartialContent), 490 (300, webob_exc.HTTPMultipleChoices), 491 (301, webob_exc.HTTPMovedPermanently), 492 (302, webob_exc.HTTPFound), 493 (303, webob_exc.HTTPSeeOther), 494 (304, webob_exc.HTTPNotModified), 495 (305, webob_exc.HTTPUseProxy), 496 (307, webob_exc.HTTPTemporaryRedirect), 497 (308, webob_exc.HTTPPermanentRedirect), 498 (400, webob_exc.HTTPBadRequest), 499 (401, webob_exc.HTTPUnauthorized), 500 (402, webob_exc.HTTPPaymentRequired), 501 (403, webob_exc.HTTPForbidden), 502 (404, webob_exc.HTTPNotFound), 503 (405, webob_exc.HTTPMethodNotAllowed), 504 (406, webob_exc.HTTPNotAcceptable), 505 (407, webob_exc.HTTPProxyAuthenticationRequired), 506 (408, webob_exc.HTTPRequestTimeout), 507 (409, webob_exc.HTTPConflict), 508 (410, webob_exc.HTTPGone), 509 (411, webob_exc.HTTPLengthRequired), 510 (412, webob_exc.HTTPPreconditionFailed), 511 (413, webob_exc.HTTPRequestEntityTooLarge), 512 (414, webob_exc.HTTPRequestURITooLong), 513 (415, webob_exc.HTTPUnsupportedMediaType), 514 (416, webob_exc.HTTPRequestRangeNotSatisfiable), 515 (417, webob_exc.HTTPExpectationFailed), 516 (422, webob_exc.HTTPUnprocessableEntity), 517 (423, webob_exc.HTTPLocked), 518 (424, webob_exc.HTTPFailedDependency), 519 (428, webob_exc.HTTPPreconditionRequired), 520 (429, webob_exc.HTTPTooManyRequests), 521 (431, webob_exc.HTTPRequestHeaderFieldsTooLarge), 522 (451, webob_exc.HTTPUnavailableForLegalReasons), 523 (500, webob_exc.HTTPInternalServerError), 524 (501, webob_exc.HTTPNotImplemented), 525 (502, webob_exc.HTTPBadGateway), 526 (503, webob_exc.HTTPServiceUnavailable), 527 (504, webob_exc.HTTPGatewayTimeout), 528 (505, webob_exc.HTTPVersionNotSupported), 529 (511, webob_exc.HTTPNetworkAuthenticationRequired), 530 ): 531 assert webob_exc.status_map[code] == cls 532