diff --git a/README.md b/README.md index 7c7a7c8..da85f7f 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,19 @@ are supposed to get mocked. # Usage - from httpretty import HTTPretty - HTTPretty.register_uri(HTTPretty.GET, "http://globo.com/", - body="The biggest portal in Brazil") +## expecting a simple response body - fd = urllib2.urlopen('http://globo.com') - got = fd.read() - fd.close() +```python +from httpretty import HTTPretty +HTTPretty.register_uri(HTTPretty.GET, "http://globo.com/", + body="The biggest portal in Brazil") - print got +fd = urllib2.urlopen('http://globo.com') +got = fd.read() +fd.close() + +print got +``` **:: output ::** @@ -44,39 +48,63 @@ are supposed to get mocked. ## rotating responses - HTTPretty.register_uri(HTTPretty.GET, "http://github.com/gabrielfalcao/httpretty", - responses=[ - HTTPretty.Response(body="first response", status=201), - HTTPretty.Response(body='second and last response', status=202), - ]) +same URL, same request method, the first request return the first +HTTPretty.Response, all the subsequent ones return the last (status +202) - request1 = urllib2.urlopen('http://github.com/gabrielfalcao/httpretty') - body1 = request1.read() - request1.close() +```python +HTTPretty.register_uri(HTTPretty.GET, "http://github.com/gabrielfalcao/httpretty", + responses=[ + HTTPretty.Response(body="first response", status=201), + HTTPretty.Response(body='second and last response', status=202), + ]) - assert that(request1.code).equals(201) - assert that(body1).equals('first response') +request1 = urllib2.urlopen('http://github.com/gabrielfalcao/httpretty') +body1 = request1.read() +request1.close() - request2 = urllib2.urlopen('http://github.com/gabrielfalcao/httpretty') - body2 = request2.read() - request2.close() - assert that(request2.code).equals(202) - assert that(body2).equals('second and last response') +assert that(request1.code).equals(201) +assert that(body1).equals('first response') - request3 = urllib2.urlopen('http://github.com/gabrielfalcao/httpretty') - body3 = request3.read() - request3.close() - assert that(request3.code).equals(202) - assert that(body3).equals('second and last response') +request2 = urllib2.urlopen('http://github.com/gabrielfalcao/httpretty') +body2 = request2.read() +request2.close() +assert that(request2.code).equals(202) +assert that(body2).equals('second and last response') +request3 = urllib2.urlopen('http://github.com/gabrielfalcao/httpretty') +body3 = request3.read() +request3.close() +assert that(request3.code).equals(202) +assert that(body3).equals('second and last response') +``` # Documentation -Unfortunately HTTPretty is lacking a documentation, but as for it is 100% based on [FakeWeb](http://fakeweb.rubyforge.org/), a good way to learn it is by looking at **HTTPretty** tests right [here](http://github.com/gabrielfalcao/HTTPretty/blob/master/tests/functional/test_urllib2.py) +## expect for a response, and check the request got by the "server" to make sure it was fine. + +```python +from httpretty import HTTPretty +from httplib2 import Http + +HTTPretty.register_uri(HTTPretty.PATCH, "http://api.github.com/", + body='{"repositories": ["HTTPretty", "lettuce"]}') + +client = Http() +headers, body = client.request('http://api.github.com', 'PATCH', + body='{"username": "gabrielfalcao"}', + headers={ + 'content-type': 'text/json', + }) +assert body == '{"repositories": ["HTTPretty", "lettuce"]}' +assert HTTPretty.last_request.method == 'PATCH' +assert HTTPretty.last_request.headers['content-type'] == 'text/json' +``` # Dependencies -you will need **ONLY** if you decide to contribute to HTTPretty which means you're gonna need run our test suite +you will need **ONLY** if you decide to contribute to HTTPretty which +means you're gonna need run our test suite * [nose](http://code.google.com/p/python-nose/) * [sure](http://github.com/gabrielfalcao/sure/) diff --git a/httpretty/__init__.py b/httpretty/__init__.py index 5f19913..37a2d12 100644 --- a/httpretty/__init__.py +++ b/httpretty/__init__.py @@ -23,16 +23,20 @@ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. -version = '0.1' +version = '0.2' import re import socket import warnings +import logging +import traceback from datetime import datetime from StringIO import StringIO from urlparse import urlsplit +from BaseHTTPServer import BaseHTTPRequestHandler + old_socket = socket.socket old_create_connection = socket.create_connection old_gethostbyname = socket.gethostbyname @@ -51,6 +55,24 @@ class HTTPrettyError(Exception): pass +class HTTPrettyRequest(BaseHTTPRequestHandler): + def __init__(self, headers, body=''): + self.body = body + self.raw_headers = headers + + self.rfile = StringIO(headers + body) + self.raw_requestline = self.rfile.readline() + self.error_code = self.error_message = None + self.parse_request() + self.method = self.command + + def __repr__(self): + return 'HTTPrettyRequest(headers={0}, body="{1}")'.format( + self.headers, + self.body, + ) + + class FakeSockFile(StringIO): def read(self, amount=None): amount = amount or self.len @@ -71,7 +93,7 @@ class fakesock(object): class socket(object): _entry = None debuglevel = 0 - + _sent_data = [] def __init__(self, family, type, protocol): self.family = family self.type = type @@ -113,15 +135,34 @@ class fakesock(object): self.truesock.close() def sendall(self, data): + self._sent_data.append(data) + hostnames = [i.hostname for i in HTTPretty._entries.keys()] self.fd.seek(0) try: verb, headers_string = data.split('\n', 1) + is_parsing_headers = True except ValueError: - return self._true_sendall(data) + is_parsing_headers = False + + if self._host not in hostnames: + return self._true_sendall(data) + + if not is_parsing_headers: + if len(self._sent_data) > 1: + headers, body = self._sent_data[-2:] + try: + return HTTPretty.historify_request(headers, body) + + except Exception, e: + logging.error(traceback.format_exc(e)) + return self._true_sendall(data) method, path, version = re.split('\s+', verb.strip(), 3) - info = URIInfo(hostname=self._host, port=self._port, path=path) + request = HTTPretty.historify_request(data) + + info = URIInfo(hostname=self._host, port=self._port, path=path, + last_request=request) entries = [] for key, value in HTTPretty._entries.items(): @@ -272,8 +313,14 @@ class Entry(object): string_list.append('Date: %s' % headers.pop('Date')) if not self.forcing_headers: - string_list.append('Content-Type: %s' % headers.pop('Content-Type', 'text/plain; charset=utf-8')) - string_list.append('Content-Length: %s' % headers.pop('Content-Length', len(self.body))) + content_type = headers.pop('Content-Type', + 'text/plain; charset=utf-8') + content_length = headers.pop('Content-Length', len(self.body)) + string_list.append( + 'Content-Type: %s' % content_type) + string_list.append( + 'Content-Length: %s' % content_length) + string_list.append('Server: %s' % headers.pop('Server')), for k, v in headers.items(): @@ -286,8 +333,18 @@ class Entry(object): fk.write(self.body) fk.seek(0) + class URIInfo(object): - def __init__(self, username='', password='', hostname='', port=80, path='/', query='', fragment='', entries=None): + def __init__(self, + username='', + password='', + hostname='', + port=80, + path='/', + query='', + fragment='', + entries=None, + last_request=None): self.username = username or '' self.password = password or '' self.hostname = hostname or '' @@ -297,6 +354,7 @@ class URIInfo(object): self.fragment = fragment or '' self.entries = entries self.current_entry = 0 + self.last_request = last_request def get_next_entry(self): if self.current_entry >= len(self.entries): @@ -340,12 +398,20 @@ class URIInfo(object): class HTTPretty(object): u"""The URI registration class""" _entries = {} - + latest_requests = [] GET = 'GET' PUT = 'PUT' POST = 'POST' DELETE = 'DELETE' HEAD = 'HEAD' + PATCH = 'PATCH' + + @classmethod + def historify_request(cls, headers, body=''): + request = HTTPrettyRequest(headers, body) + cls.last_request = request + cls.latest_requests.append(request) + return request @classmethod def register_uri(cls, method, uri, body='HTTPretty :)', adding_headers=None, forcing_headers=None, status=200, responses=None, **headers): diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a2624b6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +nose +sure +bolacha +httplib2 +tornado +multiprocessing \ No newline at end of file diff --git a/tests/functional/test_httplib2.py b/tests/functional/test_httplib2.py index 8ed2dad..af915d1 100644 --- a/tests/functional/test_httplib2.py +++ b/tests/functional/test_httplib2.py @@ -47,6 +47,8 @@ def test_httpretty_should_mock_a_simple_get_with_httplib2_read(context, now): _, got = context.http.request('http://globo.com', 'GET') assert that(got).equals('The biggest portal in Brazil') + assert that(HTTPretty.last_request.method).equals('GET') + assert that(HTTPretty.last_request.path).equals('/') @within(two=microseconds) @that_with_context(prepare, and_clear) @@ -166,3 +168,28 @@ def test_httpretty_should_support_a_list_of_successive_responses_httplib2(contex assert that(headers3['status']).equals('202') assert that(body3).equals('second and last response') + +@within(two=microseconds) +@that_with_context(prepare, and_clear) +def test_can_inspect_last_request(context, now): + u"HTTPretty.last_request is a mimetools.Message request from last match" + + HTTPretty.register_uri(HTTPretty.POST, "http://api.github.com/", + body='{"repositories": ["HTTPretty", "lettuce"]}') + + headers, body = context.http.request( + 'http://api.github.com', 'POST', + body='{"username": "gabrielfalcao"}', + headers={ + 'content-type': 'text/json', + }, + ) + + assert that(HTTPretty.last_request.method).equals('POST') + assert that(HTTPretty.last_request.body).equals( + '{"username": "gabrielfalcao"}', + ) + assert that(HTTPretty.last_request.headers['content-type']).equals( + 'text/json', + ) + assert that(body).equals('{"repositories": ["HTTPretty", "lettuce"]}') diff --git a/tests/functional/test_urllib2.py b/tests/functional/test_urllib2.py index 91d5752..fa194e2 100644 --- a/tests/functional/test_urllib2.py +++ b/tests/functional/test_urllib2.py @@ -28,6 +28,7 @@ import urllib2 from sure import * from httpretty import HTTPretty + @within(two=microseconds) def test_httpretty_should_mock_a_simple_get_with_urllib2_read(): u"HTTPretty should mock a simple GET with urllib2.read()" @@ -41,6 +42,7 @@ def test_httpretty_should_mock_a_simple_get_with_urllib2_read(): assert that(got).equals('The biggest portal in Brazil') + @within(two=microseconds) def test_httpretty_should_mock_headers_urllib2(now): u"HTTPretty should mock basic headers with urllib2" @@ -87,9 +89,10 @@ def test_httpretty_should_allow_adding_and_overwritting_urllib2(now): 'content-length': '27', 'status': '200 OK', 'server': 'Apache', - 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT') + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), }) + @within(two=microseconds) def test_httpretty_should_allow_forcing_headers_urllib2(): u"HTTPretty should allow forcing headers with urllib2" @@ -131,7 +134,7 @@ def test_httpretty_should_allow_adding_and_overwritting_by_kwargs_u2(now): 'content-length': '23456789', 'status': '200 OK', 'server': 'Apache', - 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT') + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), }) @@ -163,3 +166,31 @@ def test_httpretty_should_support_a_list_of_successive_responses_urllib2(now): request3.close() assert that(request3.code).equals(202) assert that(body3).equals('second and last response') + + +@within(two=microseconds) +def test_can_inspect_last_request(now): + u"HTTPretty.last_request is a mimetools.Message request from last match" + + HTTPretty.register_uri(HTTPretty.POST, "http://api.github.com/", + body='{"repositories": ["HTTPretty", "lettuce"]}') + + request = urllib2.Request( + 'http://api.github.com', + '{"username": "gabrielfalcao"}', + { + 'content-type': 'text/json', + }, + ) + fd = urllib2.urlopen(request) + got = fd.read() + fd.close() + + assert that(HTTPretty.last_request.method).equals('POST') + assert that(HTTPretty.last_request.body).equals( + '{"username": "gabrielfalcao"}', + ) + assert that(HTTPretty.last_request.headers['content-type']).equals( + 'text/json', + ) + assert that(got).equals('{"repositories": ["HTTPretty", "lettuce"]}')