/tests/unit/test_core.py
Python | 666 lines | 547 code | 63 blank | 56 comment | 1 complexity | 3f6fd5a15fffc7d58dd1be264f82c90d MD5 | raw file
1import io 2import json 3import errno 4 5from freezegun import freeze_time 6from sure import expect 7 8from httpretty.core import HTTPrettyRequest, FakeSSLSocket, fakesock, httpretty 9from httpretty.core import URIMatcher, URIInfo 10 11from tests.compat import Mock, patch, call 12 13 14class SocketErrorStub(Exception): 15 def __init__(self, errno): 16 self.errno = errno 17 18 19def test_request_stubs_internals(): 20 ("HTTPrettyRequest is a BaseHTTPRequestHandler that replaces " 21 "real socket file descriptors with in-memory ones") 22 23 # Given a valid HTTP request header string 24 headers = "\r\n".join([ 25 'POST /somewhere/?name=foo&age=bar HTTP/1.1', 26 'accept-encoding: identity', 27 'host: github.com', 28 'content-type: application/json', 29 'connection: close', 30 'user-agent: Python-urllib/2.7', 31 ]) 32 33 # When I create a HTTPrettyRequest with an empty body 34 request = HTTPrettyRequest(headers, body='') 35 36 # Then it should have parsed the headers 37 dict(request.headers).should.equal({ 38 'accept-encoding': 'identity', 39 'connection': 'close', 40 'content-type': 'application/json', 41 'host': 'github.com', 42 'user-agent': 'Python-urllib/2.7' 43 }) 44 45 # And the `rfile` should be a io.BytesIO 46 type_as_str = io.BytesIO.__module__ + '.' + io.BytesIO.__name__ 47 48 request.should.have.property('rfile').being.a(type_as_str) 49 50 # And the `wfile` should be a io.BytesIO 51 request.should.have.property('wfile').being.a(type_as_str) 52 53 # And the `method` should be available 54 request.should.have.property('method').being.equal('POST') 55 56 57def test_request_parse_querystring(): 58 ("HTTPrettyRequest#parse_querystring should parse unicode data") 59 60 # Given a request string containing a unicode encoded querystring 61 62 headers = "\r\n".join([ 63 'POST /create?name=Gabriel+Falcão HTTP/1.1', 64 'Content-Type: multipart/form-data', 65 ]) 66 67 # When I create a HTTPrettyRequest with an empty body 68 request = HTTPrettyRequest(headers, body='') 69 70 # Then it should have a parsed querystring 71 request.querystring.should.equal({'name': ['Gabriel Falcão']}) 72 73 74def test_request_parse_body_when_it_is_application_json(): 75 ("HTTPrettyRequest#parse_request_body recognizes the " 76 "content-type `application/json` and parses it") 77 78 # Given a request string containing a unicode encoded querystring 79 headers = "\r\n".join([ 80 'POST /create HTTP/1.1', 81 'Content-Type: application/json', 82 ]) 83 # And a valid json body 84 body = json.dumps({'name': 'Gabriel Falcão'}) 85 86 # When I create a HTTPrettyRequest with that data 87 request = HTTPrettyRequest(headers, body) 88 89 # Then it should have a parsed body 90 request.parsed_body.should.equal({'name': 'Gabriel Falcão'}) 91 92 93def test_request_parse_body_when_it_is_text_json(): 94 ("HTTPrettyRequest#parse_request_body recognizes the " 95 "content-type `text/json` and parses it") 96 97 # Given a request string containing a unicode encoded querystring 98 headers = "\r\n".join([ 99 'POST /create HTTP/1.1', 100 'Content-Type: text/json', 101 ]) 102 # And a valid json body 103 body = json.dumps({'name': 'Gabriel Falcão'}) 104 105 # When I create a HTTPrettyRequest with that data 106 request = HTTPrettyRequest(headers, body) 107 108 # Then it should have a parsed body 109 request.parsed_body.should.equal({'name': 'Gabriel Falcão'}) 110 111 112def test_request_parse_body_when_it_is_urlencoded(): 113 ("HTTPrettyRequest#parse_request_body recognizes the " 114 "content-type `application/x-www-form-urlencoded` and parses it") 115 116 # Given a request string containing a unicode encoded querystring 117 headers = "\r\n".join([ 118 'POST /create HTTP/1.1', 119 'Content-Type: application/x-www-form-urlencoded', 120 ]) 121 # And a valid urlencoded body 122 body = "name=Gabriel+Falcão&age=25&projects=httpretty&projects=sure&projects=lettuce" 123 124 # When I create a HTTPrettyRequest with that data 125 request = HTTPrettyRequest(headers, body) 126 127 # Then it should have a parsed body 128 request.parsed_body.should.equal({ 129 'name': ['Gabriel Falcão'], 130 'age': ["25"], 131 'projects': ["httpretty", "sure", "lettuce"] 132 }) 133 134 135def test_request_parse_body_when_unrecognized(): 136 ("HTTPrettyRequest#parse_request_body returns the value as " 137 "is if the Content-Type is not recognized") 138 139 # Given a request string containing a unicode encoded querystring 140 headers = "\r\n".join([ 141 'POST /create HTTP/1.1', 142 'Content-Type: whatever', 143 ]) 144 # And a valid urlencoded body 145 body = "foobar:\nlalala" 146 147 # When I create a HTTPrettyRequest with that data 148 request = HTTPrettyRequest(headers, body) 149 150 # Then it should have a parsed body 151 request.parsed_body.should.equal("foobar:\nlalala") 152 153 154def test_request_string_representation(): 155 ("HTTPrettyRequest should have a forward_and_trace-friendly " 156 "string representation") 157 158 # Given a request string containing a unicode encoded querystring 159 headers = "\r\n".join([ 160 'POST /create HTTP/1.1', 161 'Content-Type: JPEG-baby', 162 ]) 163 # And a valid urlencoded body 164 body = "foobar:\nlalala" 165 166 # When I create a HTTPrettyRequest with that data 167 request = HTTPrettyRequest(headers, body) 168 169 # Then its string representation should show the headers and the body 170 str(request).should.equal('<HTTPrettyRequest("JPEG-baby", total_headers=1, body_length=14)>') 171 172 173def test_fake_ssl_socket_proxies_its_ow_socket(): 174 ("FakeSSLSocket is a simpel wrapper around its own socket, " 175 "which was designed to be a HTTPretty fake socket") 176 177 # Given a sentinel mock object 178 socket = Mock() 179 180 # And a FakeSSLSocket wrapping it 181 ssl = FakeSSLSocket(socket) 182 183 # When I make a method call 184 ssl.send("FOO") 185 186 # Then it should bypass any method calls to its own socket 187 socket.send.assert_called_once_with("FOO") 188 189 190@freeze_time("2013-10-04 04:20:00") 191def test_fakesock_socket_getpeercert(): 192 ("fakesock.socket#getpeercert should return a hardcoded fake certificate") 193 # Given a fake socket instance 194 socket = fakesock.socket() 195 196 # And that it's bound to some host 197 socket._host = 'somewhere.com' 198 199 # When I retrieve the peer certificate 200 certificate = socket.getpeercert() 201 202 # Then it should return a hardcoded value 203 certificate.should.equal({ 204 u'notAfter': 'Sep 29 04:20:00 GMT', 205 u'subject': ( 206 ((u'organizationName', u'*.somewhere.com'),), 207 ((u'organizationalUnitName', u'Domain Control Validated'),), 208 ((u'commonName', u'*.somewhere.com'),)), 209 u'subjectAltName': ( 210 (u'DNS', u'*.somewhere.com'), 211 (u'DNS', u'somewhere.com'), 212 (u'DNS', u'*') 213 ) 214 }) 215 216 217def test_fakesock_socket_ssl(): 218 ("fakesock.socket#ssl should take a socket instance and return itself") 219 # Given a fake socket instance 220 socket = fakesock.socket() 221 222 # And a stubbed socket sentinel 223 sentinel = Mock() 224 225 # When I call `ssl` on that mock 226 result = socket.ssl(sentinel) 227 228 # Then it should have returned its first argument 229 result.should.equal(sentinel) 230 231 232@patch('httpretty.core.old_socket') 233@patch('httpretty.core.POTENTIAL_HTTP_PORTS') 234def test_fakesock_socket_connect_fallback(POTENTIAL_HTTP_PORTS, old_socket): 235 ("fakesock.socket#connect should open a real connection if the " 236 "given port is not a potential http port") 237 # Background: the potential http ports are 80 and 443 238 POTENTIAL_HTTP_PORTS.__contains__.side_effect = lambda other: int(other) in (80, 443) 239 240 # Given a fake socket instance 241 socket = fakesock.socket() 242 243 # When it is connected to a remote server in a port that isn't 80 nor 443 244 socket.connect(('somewhere.com', 42)) 245 246 # Then it should have open a real connection in the background 247 old_socket.return_value.connect.assert_called_once_with(('somewhere.com', 42)) 248 249 250@patch('httpretty.core.old_socket') 251def test_fakesock_socket_close(old_socket): 252 ("fakesock.socket#close should close the actual socket in case " 253 "it's not http and __truesock_is_connected__ is True") 254 # Given a fake socket instance that is synthetically open 255 socket = fakesock.socket() 256 socket.__truesock_is_connected__ = True 257 258 # When I close it 259 socket.close() 260 261 # Then its real socket should have been closed 262 old_socket.return_value.close.assert_called_once_with() 263 socket.__truesock_is_connected__.should.be.false 264 265 266@patch('httpretty.core.old_socket') 267def test_fakesock_socket_makefile(old_socket): 268 ("fakesock.socket#makefile should set the mode, " 269 "bufsize and return its mocked file descriptor") 270 271 # Given a fake socket that has a mocked Entry associated with it 272 socket = fakesock.socket() 273 socket._entry = Mock() 274 275 # When I call makefile() 276 fd = socket.makefile(mode='rw', bufsize=512) 277 278 # Then it should have returned the socket's own filedescriptor 279 expect(fd).to.equal(socket.fd) 280 # And the mode should have been set in the socket instance 281 socket._mode.should.equal('rw') 282 # And the bufsize should have been set in the socket instance 283 socket._bufsize.should.equal(512) 284 285 # And the entry should have been filled with that filedescriptor 286 socket._entry.fill_filekind.assert_called_once_with(fd) 287 288 289@patch('httpretty.core.old_socket') 290def test_fakesock_socket_real_sendall(old_socket): 291 ("fakesock.socket#real_sendall calls truesock#connect and bails " 292 "out when not http") 293 # Background: the real socket will stop returning bytes after the 294 # first call 295 real_socket = old_socket.return_value 296 real_socket.recv.side_effect = [b'response from server', b""] 297 298 # Given a fake socket 299 socket = fakesock.socket() 300 301 # When I call real_sendall with data, some args and kwargs 302 socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar') 303 304 # Then it should have called sendall in the real socket 305 real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar') 306 307 # # And setblocking was never called 308 # real_socket.setblocking.called.should.be.false 309 310 # And recv was never called 311 real_socket.recv.called.should.be.false 312 313 # And the buffer is empty 314 socket.fd.read().should.equal(b'') 315 316 # And connect was never called 317 real_socket.connect.called.should.be.false 318 319 320@patch('httpretty.core.old_socket') 321def test_fakesock_socket_real_sendall_when_http(old_socket): 322 ("fakesock.socket#real_sendall sends data and buffers " 323 "the response in the file descriptor") 324 # Background: the real socket will stop returning bytes after the 325 # first call 326 real_socket = old_socket.return_value 327 real_socket.recv.side_effect = [b'response from server', b""] 328 329 # Given a fake socket 330 socket = fakesock.socket() 331 socket._address = ('1.2.3.4', 42) 332 socket.is_http = True 333 334 # When I call real_sendall with data, some args and kwargs 335 socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar') 336 337 # Then it should have called sendall in the real socket 338 real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar') 339 340 # And the socket was set to blocking 341 real_socket.setblocking.assert_called_once_with(1) 342 343 # And recv was called with the bufsize 344 real_socket.recv.assert_has_calls([ 345 call(socket._bufsize) 346 ]) 347 348 # And the buffer should contain the data from the server 349 socket.fd.read().should.equal(b"response from server") 350 351 # And connect was called 352 real_socket.connect.called.should.be.true 353 354 355@patch('httpretty.core.old_socket') 356@patch('httpretty.core.socket') 357def test_fakesock_socket_real_sendall_continue_eagain_when_http(socket, old_socket): 358 ("fakesock.socket#real_sendall should continue if the socket error was EAGAIN") 359 socket.error = SocketErrorStub 360 # Background: the real socket will stop returning bytes after the 361 # first call 362 real_socket = old_socket.return_value 363 real_socket.recv.side_effect = [SocketErrorStub(errno.EAGAIN), b'after error', b""] 364 365 # Given a fake socket 366 socket = fakesock.socket() 367 socket._address = ('1.2.3.4', 42) 368 socket.is_http = True 369 370 # When I call real_sendall with data, some args and kwargs 371 socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar') 372 373 # Then it should have called sendall in the real socket 374 real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar') 375 376 # And the socket was set to blocking 377 real_socket.setblocking.assert_called_once_with(1) 378 379 # And recv was called with the bufsize 380 real_socket.recv.assert_has_calls([ 381 call(socket._bufsize) 382 ]) 383 384 # And the buffer should contain the data from the server 385 socket.fd.read().should.equal(b"after error") 386 387 # And connect was called 388 real_socket.connect.called.should.be.true 389 390 391@patch('httpretty.core.old_socket') 392@patch('httpretty.core.socket') 393def test_fakesock_socket_real_sendall_socket_error_when_http(socket, old_socket): 394 ("fakesock.socket#real_sendall should continue if the socket error was EAGAIN") 395 socket.error = SocketErrorStub 396 # Background: the real socket will stop returning bytes after the 397 # first call 398 real_socket = old_socket.return_value 399 real_socket.recv.side_effect = [SocketErrorStub(42), b'after error', ""] 400 401 # Given a fake socket 402 socket = fakesock.socket() 403 socket._address = ('1.2.3.4', 42) 404 socket.is_http = True 405 406 # When I call real_sendall with data, some args and kwargs 407 socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar') 408 409 # Then it should have called sendall in the real socket 410 real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar') 411 412 # And the socket was set to blocking 413 real_socket.setblocking.assert_called_once_with(1) 414 415 # And recv was called with the bufsize 416 real_socket.recv.assert_called_once_with(socket._bufsize) 417 418 # And the buffer should contain the data from the server 419 socket.fd.read().should.equal(b"") 420 421 # And connect was called 422 real_socket.connect.called.should.be.true 423 424 425@patch('httpretty.core.old_socket') 426@patch('httpretty.core.POTENTIAL_HTTP_PORTS') 427def test_fakesock_socket_real_sendall_when_sending_data(POTENTIAL_HTTP_PORTS, old_socket): 428 ("fakesock.socket#real_sendall should connect before sending data") 429 # Background: the real socket will stop returning bytes after the 430 # first call 431 real_socket = old_socket.return_value 432 real_socket.recv.side_effect = [b'response from foobar :)', b""] 433 434 # And the potential http port is 4000 435 POTENTIAL_HTTP_PORTS.__contains__.side_effect = lambda other: int(other) == 4000 436 POTENTIAL_HTTP_PORTS.union.side_effect = lambda other: POTENTIAL_HTTP_PORTS 437 438 # Given a fake socket 439 socket = fakesock.socket() 440 441 # When I call connect to a server in a port that is considered HTTP 442 socket.connect(('foobar.com', 4000)) 443 444 # And send some data 445 socket.real_sendall(b"SOMEDATA") 446 447 # Then connect should have been called 448 real_socket.connect.assert_called_once_with(('foobar.com', 4000)) 449 450 # And the socket was set to blocking 451 real_socket.setblocking.assert_called_once_with(1) 452 453 # And recv was called with the bufsize 454 real_socket.recv.assert_has_calls([ 455 call(socket._bufsize) 456 ]) 457 458 # And the buffer should contain the data from the server 459 socket.fd.read().should.equal(b"response from foobar :)") 460 461 462@patch('httpretty.core.old_socket') 463@patch('httpretty.core.httpretty') 464@patch('httpretty.core.POTENTIAL_HTTP_PORTS') 465def test_fakesock_socket_sendall_with_valid_requestline(POTENTIAL_HTTP_PORTS, httpretty, old_socket): 466 ("fakesock.socket#sendall should create an entry if it's given a valid request line") 467 matcher = Mock(name='matcher') 468 info = Mock(name='info') 469 httpretty.match_uriinfo.return_value = (matcher, info) 470 httpretty.register_uri(httpretty.GET, 'http://foo.com/foobar') 471 472 # Background: 473 # using a subclass of socket that mocks out real_sendall 474 class MySocket(fakesock.socket): 475 def real_sendall(self, data, *args, **kw): 476 raise AssertionError('should never call this...') 477 478 # Given an instance of that socket 479 socket = MySocket() 480 481 # And that is is considered http 482 socket.connect(('foo.com', 80)) 483 484 # When I try to send data 485 socket.sendall(b"GET /foobar HTTP/1.1\r\nContent-Type: application/json\r\n\r\n") 486 487 488@patch('httpretty.core.old_socket') 489@patch('httpretty.core.httpretty') 490@patch('httpretty.core.POTENTIAL_HTTP_PORTS') 491def test_fakesock_socket_sendall_with_valid_requestline_2(POTENTIAL_HTTP_PORTS, httpretty, old_socket): 492 ("fakesock.socket#sendall should create an entry if it's given a valid request line") 493 matcher = Mock(name='matcher') 494 info = Mock(name='info') 495 httpretty.match_uriinfo.return_value = (matcher, info) 496 httpretty.register_uri(httpretty.GET, 'http://foo.com/foobar') 497 498 # Background: 499 # using a subclass of socket that mocks out real_sendall 500 class MySocket(fakesock.socket): 501 def real_sendall(self, data, *args, **kw): 502 raise AssertionError('should never call this...') 503 504 # Given an instance of that socket 505 socket = MySocket() 506 507 # And that is is considered http 508 socket.connect(('foo.com', 80)) 509 510 # When I try to send data 511 socket.sendall(b"GET /foobar HTTP/1.1\r\nContent-Type: application/json\r\n\r\n") 512 513 514@patch('httpretty.core.old_socket') 515def test_fakesock_socket_sendall_with_body_data_no_entry(old_socket): 516 ("fakesock.socket#sendall should call real_sendall when not parsing headers and there is no entry") 517 # Background: 518 # Using a subclass of socket that mocks out real_sendall 519 520 class MySocket(fakesock.socket): 521 def real_sendall(self, data): 522 data.should.equal(b'BLABLABLABLA') 523 return 'cool' 524 525 # Given an instance of that socket 526 socket = MySocket() 527 socket._entry = None 528 529 # And that is is considered http 530 socket.connect(('foo.com', 80)) 531 532 # When I try to send data 533 result = socket.sendall(b"BLABLABLABLA") 534 535 # Then the result should be the return value from real_sendall 536 result.should.equal('cool') 537 538 539@patch('httpretty.core.old_socket') 540@patch('httpretty.core.POTENTIAL_HTTP_PORTS') 541def test_fakesock_socket_sendall_with_body_data_with_entry(POTENTIAL_HTTP_PORTS, old_socket): 542 ("fakesock.socket#sendall should call real_sendall when there is no entry") 543 # Background: 544 # Using a subclass of socket that mocks out real_sendall 545 data_sent = [] 546 547 class MySocket(fakesock.socket): 548 def real_sendall(self, data): 549 data_sent.append(data) 550 551 # Given an instance of that socket 552 socket = MySocket() 553 554 # And that is is considered http 555 socket.connect(('foo.com', 80)) 556 557 # When I try to send data 558 socket.sendall(b"BLABLABLABLA") 559 560 # Then it should have called real_sendall 561 data_sent.should.equal([b'BLABLABLABLA']) 562 563 564@patch('httpretty.core.httpretty.match_uriinfo') 565@patch('httpretty.core.old_socket') 566@patch('httpretty.core.POTENTIAL_HTTP_PORTS') 567def test_fakesock_socket_sendall_with_body_data_with_chunked_entry(POTENTIAL_HTTP_PORTS, old_socket, match_uriinfo): 568 ("fakesock.socket#sendall should call real_sendall when not ") 569 # Background: 570 # Using a subclass of socket that mocks out real_sendall 571 572 class MySocket(fakesock.socket): 573 def real_sendall(self, data): 574 raise AssertionError('should have never been called') 575 576 matcher = Mock(name='matcher') 577 info = Mock(name='info') 578 httpretty.match_uriinfo.return_value = (matcher, info) 579 580 # Using a mocked entry 581 entry = Mock() 582 entry.method = 'GET' 583 entry.info.path = '/foo' 584 585 entry.request.headers = { 586 'transfer-encoding': 'chunked', 587 } 588 entry.request.body = b'' 589 590 # Given an instance of that socket 591 socket = MySocket() 592 socket._entry = entry 593 594 # And that is is considered http 595 socket.connect(('foo.com', 80)) 596 597 # When I try to send data 598 socket.sendall(b"BLABLABLABLA") 599 600 # Then the entry should have that body 601 httpretty.last_request.body.should.equal(b'BLABLABLABLA') 602 603 604def test_fakesock_socket_sendall_with_path_starting_with_two_slashes(): 605 ("fakesock.socket#sendall handles paths starting with // well") 606 607 httpretty.register_uri(httpretty.GET, 'http://example.com//foo') 608 609 class MySocket(fakesock.socket): 610 def real_sendall(self, data, *args, **kw): 611 raise AssertionError('should never call this...') 612 613 # Given an instance of that socket 614 socket = MySocket() 615 616 # And that is is considered http 617 socket.connect(('example.com', 80)) 618 619 # When I try to send data 620 socket.sendall(b"GET //foo HTTP/1.1\r\nContent-Type: application/json\r\n\r\n") 621 622 623def test_URIMatcher_respects_querystring(): 624 ("URIMatcher response querystring") 625 matcher = URIMatcher('http://www.foo.com/?query=true', None) 626 info = URIInfo.from_uri('http://www.foo.com/', None) 627 assert matcher.matches(info) 628 629 matcher = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True) 630 info = URIInfo.from_uri('http://www.foo.com/', None) 631 assert not matcher.matches(info) 632 633 matcher = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True) 634 info = URIInfo.from_uri('http://www.foo.com/?query=true', None) 635 assert matcher.matches(info) 636 637 matcher = URIMatcher('http://www.foo.com/?query=true&unquery=false', None, match_querystring=True) 638 info = URIInfo.from_uri('http://www.foo.com/?unquery=false&query=true', None) 639 assert matcher.matches(info) 640 641 matcher = URIMatcher('http://www.foo.com/?unquery=false&query=true', None, match_querystring=True) 642 info = URIInfo.from_uri('http://www.foo.com/?query=true&unquery=false', None) 643 assert matcher.matches(info) 644 645 646def test_URIMatcher_equality_respects_querystring(): 647 ("URIMatcher equality check should check querystring") 648 matcher_a = URIMatcher('http://www.foo.com/?query=true', None) 649 matcher_b = URIMatcher('http://www.foo.com/?query=false', None) 650 assert matcher_a == matcher_b 651 652 matcher_a = URIMatcher('http://www.foo.com/?query=true', None) 653 matcher_b = URIMatcher('http://www.foo.com/', None) 654 assert matcher_a == matcher_b 655 656 matcher_a = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True) 657 matcher_b = URIMatcher('http://www.foo.com/?query=false', None, match_querystring=True) 658 assert not matcher_a == matcher_b 659 660 matcher_a = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True) 661 matcher_b = URIMatcher('http://www.foo.com/', None, match_querystring=True) 662 assert not matcher_a == matcher_b 663 664 matcher_a = URIMatcher('http://www.foo.com/?query=true&unquery=false', None, match_querystring=True) 665 matcher_b = URIMatcher('http://www.foo.com/?unquery=false&query=true', None, match_querystring=True) 666 assert matcher_a == matcher_b