diff options
| author | Gabriel Falcao <gabriel@nacaolivre.org> | 2013-10-14 01:10:53 -0400 |
|---|---|---|
| committer | Gabriel Falcao <gabriel@nacaolivre.org> | 2013-10-14 01:10:53 -0400 |
| commit | c88bbe9dea6170788a5f4db4bdb9d2fc119fcab0 (patch) | |
| tree | 2d1be892ada39a99c34d2b325f4c9e2e3a0867b2 /tests/functional | |
| download | httpretty-gh-pages.tar.gz | |
documentationgh-pages
Diffstat (limited to 'tests/functional')
| -rw-r--r-- | tests/functional/__init__.py | 28 | ||||
| -rw-r--r-- | tests/functional/base.py | 98 | ||||
| -rw-r--r-- | tests/functional/fixtures/.placeholder | 0 | ||||
| -rw-r--r-- | tests/functional/fixtures/playback-1.json | 58 | ||||
| -rw-r--r-- | tests/functional/test_bypass.py | 136 | ||||
| -rw-r--r-- | tests/functional/test_debug.py | 105 | ||||
| -rw-r--r-- | tests/functional/test_decorator.py | 48 | ||||
| -rw-r--r-- | tests/functional/test_httplib2.py | 306 | ||||
| -rw-r--r-- | tests/functional/test_requests.py | 751 | ||||
| -rw-r--r-- | tests/functional/test_urllib2.py | 337 | ||||
| -rw-r--r-- | tests/functional/testserver.py | 171 |
11 files changed, 2038 insertions, 0 deletions
diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py new file mode 100644 index 0000000..8589d02 --- /dev/null +++ b/tests/functional/__init__.py @@ -0,0 +1,28 @@ +# #!/usr/bin/env python +# -*- coding: utf-8 -*- +# <HTTPretty - HTTP client mock for Python> +# Copyright (C) <2011-2013> Gabriel Falcão <gabriel@nacaolivre.org> +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# 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. + +import warnings +warnings.simplefilter('ignore') diff --git a/tests/functional/base.py b/tests/functional/base.py new file mode 100644 index 0000000..4808752 --- /dev/null +++ b/tests/functional/base.py @@ -0,0 +1,98 @@ +# #!/usr/bin/env python +# -*- coding: utf-8 -*- + +# <HTTPretty - HTTP client mock for Python> +# Copyright (C) <2011-2013> Gabriel Falcão <gabriel@nacaolivre.org> +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# 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. + +from __future__ import unicode_literals +import os +import threading +import traceback +import tornado.ioloop +import tornado.web +from functools import wraps +from sure import scenario +import json +from os.path import abspath, dirname, join +from httpretty.core import POTENTIAL_HTTP_PORTS + + +LOCAL_FILE = lambda *path: join(abspath(dirname(__file__)), *path) +FIXTURE_FILE = lambda name: LOCAL_FILE('fixtures', name) + + +class JSONEchoHandler(tornado.web.RequestHandler): + def get(self, matched): + payload = dict([(x, self.get_argument(x)) for x in self.request.arguments]) + self.write(json.dumps({matched or 'index': payload}, indent=4)) + + def post(self, matched): + payload = dict(self.request.arguments) + self.write(json.dumps({matched or 'index': payload}, indent=4)) + + +class JSONEchoServer(threading.Thread): + def __init__(self, lock, port=8888, *args, **kw): + self.lock = lock + self.port = int(port) + self._stop = threading.Event() + super(JSONEchoServer, self).__init__(*args, **kw) + self.daemon = True + + def stop(self): + self._stop.set() + + def stopped(self): + return self._stop.isSet() + + def setup_application(self): + return tornado.web.Application([ + (r"/(.*)", JSONEchoHandler), + ]) + + def run(self): + application = self.setup_application() + application.listen(self.port) + self.lock.release() + tornado.ioloop.IOLoop.instance().start() + + + +def use_tornado_server(callback): + lock = threading.Lock() + lock.acquire() + + @wraps(callback) + def func(*args, **kw): + server = JSONEchoServer(lock, os.getenv('TEST_PORT', 8888)) + server.start() + try: + lock.acquire() + callback(*args, **kw) + finally: + lock.release() + server.stop() + if 8888 in POTENTIAL_HTTP_PORTS: + POTENTIAL_HTTP_PORTS.remove(8888) + return func diff --git a/tests/functional/fixtures/.placeholder b/tests/functional/fixtures/.placeholder new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/functional/fixtures/.placeholder diff --git a/tests/functional/fixtures/playback-1.json b/tests/functional/fixtures/playback-1.json new file mode 100644 index 0000000..1eef307 --- /dev/null +++ b/tests/functional/fixtures/playback-1.json @@ -0,0 +1,58 @@ +[ + { + "request": { + "body": "", + "headers": { + "host": "localhost:8888", + "accept-encoding": "gzip, deflate, compress", + "content-length": "0", + "accept": "*/*", + "user-agent": "python-requests/1.1.0 CPython/2.7.5 Darwin/12.5.0" + }, + "querystring": { + "age": [ + "25" + ], + "name": [ + "Gabriel" + ] + }, + "uri": "http://localhost:8888/foobar?name=Gabriel&age=25", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\n \"foobar\": {\n \"age\": \"25\", \n \"name\": \"Gabriel\"\n }\n}", + "headers": { + "content-length": "73", + "etag": "\"6fdccaba6542114e7d1098d22a01623dc2aa5761\"", + "content-type": "text/html; charset=UTF-8", + "server": "TornadoServer/2.4" + } + } + }, + { + "request": { + "body": "{\"test\": \"123\"}", + "headers": { + "host": "localhost:8888", + "accept-encoding": "gzip, deflate, compress", + "content-length": "15", + "accept": "*/*", + "user-agent": "python-requests/1.1.0 CPython/2.7.5 Darwin/12.5.0" + }, + "querystring": {}, + "uri": "http://localhost:8888/foobar", + "method": "POST" + }, + "response": { + "status": 200, + "body": "{\n \"foobar\": {}\n}", + "headers": { + "content-length": "20", + "content-type": "text/html; charset=UTF-8", + "server": "TornadoServer/2.4" + } + } + } +]
\ No newline at end of file diff --git a/tests/functional/test_bypass.py b/tests/functional/test_bypass.py new file mode 100644 index 0000000..6409eb5 --- /dev/null +++ b/tests/functional/test_bypass.py @@ -0,0 +1,136 @@ +# #!/usr/bin/env python +# -*- coding: utf-8 -*- + +# <httpretty - HTTP client mock for Python> +# Copyright (C) <2011-2013> Gabriel Falcão <gabriel@nacaolivre.org> +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# 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. +from __future__ import unicode_literals + +try: + import urllib.request as urllib2 +except ImportError: + import urllib2 + +from .testserver import TornadoServer, TCPServer, TCPClient +from sure import expect, that_with_context + +import httpretty +from httpretty import core + + +def start_http_server(context): + context.server = TornadoServer(9999) + context.server.start() + httpretty.enable() + + +def stop_http_server(context): + context.server.stop() + httpretty.enable() + + +def start_tcp_server(context): + context.server = TCPServer(8888) + context.server.start() + context.client = TCPClient(8888) + httpretty.enable() + + +def stop_tcp_server(context): + context.server.stop() + context.client.close() + httpretty.enable() + + +@httpretty.activate +@that_with_context(start_http_server, stop_http_server) +def test_httpretty_bypasses_when_disabled(context): + "httpretty should bypass all requests by disabling it" + + httpretty.register_uri( + httpretty.GET, "http://localhost:9999/go-for-bubbles/", + body="glub glub") + + httpretty.disable() + + fd = urllib2.urlopen('http://localhost:9999/go-for-bubbles/') + got1 = fd.read() + fd.close() + + expect(got1).to.equal( + b'. o O 0 O o . o O 0 O o . o O 0 O o . o O 0 O o . o O 0 O o .') + + fd = urllib2.urlopen('http://localhost:9999/come-again/') + got2 = fd.read() + fd.close() + + expect(got2).to.equal(b'<- HELLO WORLD ->') + + httpretty.enable() + + fd = urllib2.urlopen('http://localhost:9999/go-for-bubbles/') + got3 = fd.read() + fd.close() + + expect(got3).to.equal(b'glub glub') + core.POTENTIAL_HTTP_PORTS.remove(9999) + +@httpretty.activate +@that_with_context(start_http_server, stop_http_server) +def test_httpretty_bypasses_a_unregistered_request(context): + "httpretty should bypass a unregistered request by disabling it" + + httpretty.register_uri( + httpretty.GET, "http://localhost:9999/go-for-bubbles/", + body="glub glub") + + fd = urllib2.urlopen('http://localhost:9999/go-for-bubbles/') + got1 = fd.read() + fd.close() + + expect(got1).to.equal(b'glub glub') + + fd = urllib2.urlopen('http://localhost:9999/come-again/') + got2 = fd.read() + fd.close() + + expect(got2).to.equal(b'<- HELLO WORLD ->') + core.POTENTIAL_HTTP_PORTS.remove(9999) + + +@httpretty.activate +@that_with_context(start_tcp_server, stop_tcp_server) +def test_using_httpretty_with_other_tcp_protocols(context): + "httpretty should work even when testing code that also use other TCP-based protocols" + + httpretty.register_uri( + httpretty.GET, "http://falcao.it/foo/", + body="BAR") + + fd = urllib2.urlopen('http://falcao.it/foo/') + got1 = fd.read() + fd.close() + + expect(got1).to.equal(b'BAR') + + expect(context.client.send("foobar")).to.equal(b"RECEIVED: foobar") diff --git a/tests/functional/test_debug.py b/tests/functional/test_debug.py new file mode 100644 index 0000000..1562be6 --- /dev/null +++ b/tests/functional/test_debug.py @@ -0,0 +1,105 @@ +# #!/usr/bin/env python +# -*- coding: utf-8 -*- + +# <HTTPretty - HTTP client mock for Python> +# Copyright (C) <2011-2013> Gabriel Falcão <gabriel@nacaolivre.org> +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# 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. +from __future__ import unicode_literals +import socket +from sure import scenario, expect +from httpretty import httprettified + + +def create_socket(context): + context.sock = socket.socket( + socket.AF_INET, + socket.SOCK_STREAM, + socket.IPPROTO_TCP, + ) + context.sock.is_http = True + + +@httprettified +@scenario(create_socket) +def test_httpretty_debugs_socket_send(context): + "HTTPretty should debug socket.send" + + expect(context.sock.send).when.called.to.throw( + RuntimeError, + "HTTPretty intercepted and unexpected socket method call." + ) + + +@httprettified +@scenario(create_socket) +def test_httpretty_debugs_socket_sendto(context): + "HTTPretty should debug socket.sendto" + + expect(context.sock.sendto).when.called.to.throw( + RuntimeError, + "HTTPretty intercepted and unexpected socket method call." + ) + + +@httprettified +@scenario(create_socket) +def test_httpretty_debugs_socket_recv(context): + "HTTPretty should debug socket.recv" + + expect(context.sock.recv).when.called.to.throw( + RuntimeError, + "HTTPretty intercepted and unexpected socket method call." + ) + + +@httprettified +@scenario(create_socket) +def test_httpretty_debugs_socket_recvfrom(context): + "HTTPretty should debug socket.recvfrom" + + expect(context.sock.recvfrom).when.called.to.throw( + RuntimeError, + "HTTPretty intercepted and unexpected socket method call." + ) + + +@httprettified +@scenario(create_socket) +def test_httpretty_debugs_socket_recv_into(context): + "HTTPretty should debug socket.recv_into" + + expect(context.sock.recv_into).when.called.to.throw( + RuntimeError, + "HTTPretty intercepted and unexpected socket method call." + ) + + +@httprettified +@scenario(create_socket) +def test_httpretty_debugs_socket_recvfrom_into(context): + "HTTPretty should debug socket.recvfrom_into" + + expect(context.sock.recvfrom_into).when.called.to.throw( + RuntimeError, + "HTTPretty intercepted and unexpected socket method call." + ) diff --git a/tests/functional/test_decorator.py b/tests/functional/test_decorator.py new file mode 100644 index 0000000..65f6f2a --- /dev/null +++ b/tests/functional/test_decorator.py @@ -0,0 +1,48 @@ +# coding: utf-8 +from unittest import TestCase +from sure import expect +from httpretty import httprettified, HTTPretty + +try: + import urllib.request as urllib2 +except ImportError: + import urllib2 + + +@httprettified +def test_decor(): + HTTPretty.register_uri( + HTTPretty.GET, "http://localhost/", + body="glub glub") + + fd = urllib2.urlopen('http://localhost/') + got1 = fd.read() + fd.close() + + expect(got1).to.equal(b'glub glub') + + +@httprettified +class ClassDecorator(TestCase): + + def test_decorated(self): + HTTPretty.register_uri( + HTTPretty.GET, "http://localhost/", + body="glub glub") + + fd = urllib2.urlopen('http://localhost/') + got1 = fd.read() + fd.close() + + expect(got1).to.equal(b'glub glub') + + def test_decorated2(self): + HTTPretty.register_uri( + HTTPretty.GET, "http://localhost/", + body="buble buble") + + fd = urllib2.urlopen('http://localhost/') + got1 = fd.read() + fd.close() + + expect(got1).to.equal(b'buble buble')
\ No newline at end of file diff --git a/tests/functional/test_httplib2.py b/tests/functional/test_httplib2.py new file mode 100644 index 0000000..11baedc --- /dev/null +++ b/tests/functional/test_httplib2.py @@ -0,0 +1,306 @@ +# #!/usr/bin/env python +# -*- coding: utf-8 -*- + +# <HTTPretty - HTTP client mock for Python> +# Copyright (C) <2011-2013> Gabriel Falcão <gabriel@nacaolivre.org> +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# 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. +from __future__ import unicode_literals + +import re +import httplib2 +from sure import expect, within, microseconds +from httpretty import HTTPretty, httprettified +from httpretty.core import decode_utf8 + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_mock_a_simple_get_with_httplib2_read(now): + "HTTPretty should mock a simple GET with httplib2.context.http" + + HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/", + body="Find the best daily deals") + + _, got = httplib2.Http().request('http://yipit.com', 'GET') + expect(got).to.equal(b'Find the best daily deals') + + expect(HTTPretty.last_request.method).to.equal('GET') + expect(HTTPretty.last_request.path).to.equal('/') + + +@httprettified +@within(two=microseconds) +def test_httpretty_provides_easy_access_to_querystrings(now): + "HTTPretty should provide an easy access to the querystring" + + HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/", + body="Find the best daily deals") + + httplib2.Http().request('http://yipit.com?foo=bar&foo=baz&chuck=norris', 'GET') + expect(HTTPretty.last_request.querystring).to.equal({ + 'foo': ['bar', 'baz'], + 'chuck': ['norris'], + }) + + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_mock_headers_httplib2(now): + "HTTPretty should mock basic headers with httplib2" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/", + body="this is supposed to be the response", + status=201) + + headers, _ = httplib2.Http().request('http://github.com', 'GET') + expect(headers['status']).to.equal('201') + expect(dict(headers)).to.equal({ + 'content-type': 'text/plain; charset=utf-8', + 'connection': 'close', + 'content-length': '35', + 'status': '201', + 'server': 'Python/HTTPretty', + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_allow_adding_and_overwritting_httplib2(now): + "HTTPretty should allow adding and overwritting headers with httplib2" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo", + body="this is supposed to be the response", + adding_headers={ + 'Server': 'Apache', + 'Content-Length': '27', + 'Content-Type': 'application/json', + }) + + headers, _ = httplib2.Http().request('http://github.com/foo', 'GET') + + expect(dict(headers)).to.equal({ + 'content-type': 'application/json', + 'content-location': 'http://github.com/foo', + 'connection': 'close', + 'content-length': '27', + 'status': '200', + 'server': 'Apache', + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_allow_forcing_headers_httplib2(now): + "HTTPretty should allow forcing headers with httplib2" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo", + body="this is supposed to be the response", + forcing_headers={ + 'Content-Type': 'application/xml', + }) + + headers, _ = httplib2.Http().request('http://github.com/foo', 'GET') + + expect(dict(headers)).to.equal({ + 'content-location': 'http://github.com/foo', # httplib2 FORCES + # content-location + # even if the + # server does not + # provide it + 'content-type': 'application/xml', + 'status': '200', # httplib2 also ALWAYS put status on headers + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_allow_adding_and_overwritting_by_kwargs_u2(now): + "HTTPretty should allow adding and overwritting headers by keyword args " \ + "with httplib2" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo", + body="this is supposed to be the response", + server='Apache', + content_length='27', + content_type='application/json') + + headers, _ = httplib2.Http().request('http://github.com/foo', 'GET') + + expect(dict(headers)).to.equal({ + 'content-type': 'application/json', + 'content-location': 'http://github.com/foo', # httplib2 FORCES + # content-location + # even if the + # server does not + # provide it + 'connection': 'close', + 'content-length': '27', + 'status': '200', + 'server': 'Apache', + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), + }) + + +@httprettified +@within(two=microseconds) +def test_rotating_responses_with_httplib2(now): + "HTTPretty should support rotating responses with httplib2" + + HTTPretty.register_uri( + HTTPretty.GET, "https://api.yahoo.com/test", + responses=[ + HTTPretty.Response(body="first response", status=201), + HTTPretty.Response(body='second and last response', status=202), + ]) + + headers1, body1 = httplib2.Http().request( + 'https://api.yahoo.com/test', 'GET') + + expect(headers1['status']).to.equal('201') + expect(body1).to.equal(b'first response') + + headers2, body2 = httplib2.Http().request( + 'https://api.yahoo.com/test', 'GET') + + expect(headers2['status']).to.equal('202') + expect(body2).to.equal(b'second and last response') + + headers3, body3 = httplib2.Http().request( + 'https://api.yahoo.com/test', 'GET') + + expect(headers3['status']).to.equal('202') + expect(body3).to.equal(b'second and last response') + + +@httprettified +@within(two=microseconds) +def test_can_inspect_last_request(now): + "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 = httplib2.Http().request( + 'http://api.github.com', 'POST', + body='{"username": "gabrielfalcao"}', + headers={ + 'content-type': 'text/json', + }, + ) + + expect(HTTPretty.last_request.method).to.equal('POST') + expect(HTTPretty.last_request.body).to.equal( + b'{"username": "gabrielfalcao"}', + ) + expect(HTTPretty.last_request.headers['content-type']).to.equal( + 'text/json', + ) + expect(body).to.equal(b'{"repositories": ["HTTPretty", "lettuce"]}') + + +@httprettified +@within(two=microseconds) +def test_can_inspect_last_request_with_ssl(now): + "HTTPretty.last_request is recorded even when mocking 'https' (SSL)" + + HTTPretty.register_uri(HTTPretty.POST, "https://secure.github.com/", + body='{"repositories": ["HTTPretty", "lettuce"]}') + + headers, body = httplib2.Http().request( + 'https://secure.github.com', 'POST', + body='{"username": "gabrielfalcao"}', + headers={ + 'content-type': 'text/json', + }, + ) + + expect(HTTPretty.last_request.method).to.equal('POST') + expect(HTTPretty.last_request.body).to.equal( + b'{"username": "gabrielfalcao"}', + ) + expect(HTTPretty.last_request.headers['content-type']).to.equal( + 'text/json', + ) + expect(body).to.equal(b'{"repositories": ["HTTPretty", "lettuce"]}') + + +@httprettified +@within(two=microseconds) +def test_httpretty_ignores_querystrings_from_registered_uri(now): + "Registering URIs with query string cause them to be ignored" + + HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/?id=123", + body="Find the best daily deals") + + _, got = httplib2.Http().request('http://yipit.com/?id=123', 'GET') + + expect(got).to.equal(b'Find the best daily deals') + expect(HTTPretty.last_request.method).to.equal('GET') + expect(HTTPretty.last_request.path).to.equal('/?id=123') + + +@httprettified +@within(two=microseconds) +def test_callback_response(now): + ("HTTPretty should all a callback function to be set as the body with" + " httplib2") + + def request_callback(request, uri, headers): + return [200,headers,"The {0} response from {1}".format(decode_utf8(request.method), uri)] + + HTTPretty.register_uri( + HTTPretty.GET, "https://api.yahoo.com/test", + body=request_callback) + + headers1, body1 = httplib2.Http().request( + 'https://api.yahoo.com/test', 'GET') + + expect(body1).to.equal(b"The GET response from https://api.yahoo.com/test") + + HTTPretty.register_uri( + HTTPretty.POST, "https://api.yahoo.com/test_post", + body=request_callback) + + headers2, body2 = httplib2.Http().request( + 'https://api.yahoo.com/test_post', 'POST') + + expect(body2).to.equal(b"The POST response from https://api.yahoo.com/test_post") + + +@httprettified +def test_httpretty_should_allow_registering_regexes(): + "HTTPretty should allow registering regexes with httplib2" + + HTTPretty.register_uri( + HTTPretty.GET, + re.compile("https://api.yipit.com/v1/deal;brand=(?P<brand_name>\w+)"), + body="Found brand", + ) + + response, body = httplib2.Http().request('https://api.yipit.com/v1/deal;brand=gap', 'GET') + expect(body).to.equal(b'Found brand') + expect(HTTPretty.last_request.method).to.equal('GET') + expect(HTTPretty.last_request.path).to.equal('/v1/deal;brand=gap') diff --git a/tests/functional/test_requests.py b/tests/functional/test_requests.py new file mode 100644 index 0000000..0a4bf55 --- /dev/null +++ b/tests/functional/test_requests.py @@ -0,0 +1,751 @@ +# #!/usr/bin/env python +# -*- coding: utf-8 -*- + +# <HTTPretty - HTTP client mock for Python> +# Copyright (C) <2011-2013> Gabriel Falcão <gabriel@nacaolivre.org> +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# 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. + +from __future__ import unicode_literals + +import os +import re +import json +import requests +from sure import within, microseconds, expect +from httpretty import HTTPretty, httprettified +from httpretty.core import decode_utf8 + +from base import FIXTURE_FILE, use_tornado_server + +try: + xrange = xrange +except NameError: + xrange = range + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + +PORT = int(os.getenv('TEST_PORT') or 8888) +server_url = lambda path: "http://localhost:{0}/{1}".format(PORT, path.lstrip('/')) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_mock_a_simple_get_with_requests_read(now): + "HTTPretty should mock a simple GET with requests.get" + + HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/", + body="Find the best daily deals") + + response = requests.get('http://yipit.com') + expect(response.text).to.equal('Find the best daily deals') + expect(HTTPretty.last_request.method).to.equal('GET') + expect(HTTPretty.last_request.path).to.equal('/') + + +@httprettified +@within(two=microseconds) +def test_httpretty_provides_easy_access_to_querystrings(now): + "HTTPretty should provide an easy access to the querystring" + + HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/", + body="Find the best daily deals") + + requests.get('http://yipit.com/?foo=bar&foo=baz&chuck=norris') + expect(HTTPretty.last_request.querystring).to.equal({ + 'foo': ['bar', 'baz'], + 'chuck': ['norris'], + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_mock_headers_requests(now): + "HTTPretty should mock basic headers with requests" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/", + body="this is supposed to be the response", + status=201) + + response = requests.get('http://github.com') + expect(response.status_code).to.equal(201) + + expect(dict(response.headers)).to.equal({ + 'content-type': 'text/plain; charset=utf-8', + 'connection': 'close', + 'content-length': '35', + 'status': '201', + 'server': 'Python/HTTPretty', + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_allow_adding_and_overwritting_requests(now): + "HTTPretty should allow adding and overwritting headers with requests" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo", + body="this is supposed to be the response", + adding_headers={ + 'Server': 'Apache', + 'Content-Length': '27', + 'Content-Type': 'application/json', + }) + + response = requests.get('http://github.com/foo') + + expect(dict(response.headers)).to.equal({ + 'content-type': 'application/json', + 'connection': 'close', + 'content-length': '27', + 'status': '200', + 'server': 'Apache', + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_allow_forcing_headers_requests(now): + "HTTPretty should allow forcing headers with requests" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo", + body="<root><baz /</root>", + forcing_headers={ + 'Content-Type': 'application/xml', + 'Content-Length': '19', + }) + + response = requests.get('http://github.com/foo') + + expect(dict(response.headers)).to.equal({ + 'content-type': 'application/xml', + 'content-length': '19', + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_allow_adding_and_overwritting_by_kwargs_u2(now): + "HTTPretty should allow adding and overwritting headers by keyword args " \ + "with requests" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/foo", + body="this is supposed to be the response", + server='Apache', + content_length='27', + content_type='application/json') + + response = requests.get('http://github.com/foo') + + expect(dict(response.headers)).to.equal({ + 'content-type': 'application/json', + 'connection': 'close', + 'content-length': '27', + 'status': '200', + 'server': 'Apache', + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), + }) + + +@httprettified +@within(two=microseconds) +def test_rotating_responses_with_requests(now): + "HTTPretty should support rotating responses with requests" + + HTTPretty.register_uri( + HTTPretty.GET, "https://api.yahoo.com/test", + responses=[ + HTTPretty.Response(body=b"first response", status=201), + HTTPretty.Response(body=b'second and last response', status=202), + ]) + + response1 = requests.get( + 'https://api.yahoo.com/test') + + expect(response1.status_code).to.equal(201) + expect(response1.text).to.equal('first response') + + response2 = requests.get( + 'https://api.yahoo.com/test') + + expect(response2.status_code).to.equal(202) + expect(response2.text).to.equal('second and last response') + + response3 = requests.get( + 'https://api.yahoo.com/test') + + expect(response3.status_code).to.equal(202) + expect(response3.text).to.equal('second and last response') + + +@httprettified +@within(two=microseconds) +def test_can_inspect_last_request(now): + "HTTPretty.last_request is a mimetools.Message request from last match" + + HTTPretty.register_uri(HTTPretty.POST, "http://api.github.com/", + body='{"repositories": ["HTTPretty", "lettuce"]}') + + response = requests.post( + 'http://api.github.com', + '{"username": "gabrielfalcao"}', + headers={ + 'content-type': 'text/json', + }, + ) + + expect(HTTPretty.last_request.method).to.equal('POST') + expect(HTTPretty.last_request.body).to.equal( + b'{"username": "gabrielfalcao"}', + ) + expect(HTTPretty.last_request.headers['content-type']).to.equal( + 'text/json', + ) + expect(response.json()).to.equal({"repositories": ["HTTPretty", "lettuce"]}) + + +@httprettified +@within(two=microseconds) +def test_can_inspect_last_request_with_ssl(now): + "HTTPretty.last_request is recorded even when mocking 'https' (SSL)" + + HTTPretty.register_uri(HTTPretty.POST, "https://secure.github.com/", + body='{"repositories": ["HTTPretty", "lettuce"]}') + + response = requests.post( + 'https://secure.github.com', + '{"username": "gabrielfalcao"}', + headers={ + 'content-type': 'text/json', + }, + ) + + expect(HTTPretty.last_request.method).to.equal('POST') + expect(HTTPretty.last_request.body).to.equal( + b'{"username": "gabrielfalcao"}', + ) + expect(HTTPretty.last_request.headers['content-type']).to.equal( + 'text/json', + ) + expect(response.json()).to.equal({"repositories": ["HTTPretty", "lettuce"]}) + + +@httprettified +@within(two=microseconds) +def test_httpretty_ignores_querystrings_from_registered_uri(now): + "HTTPretty should ignore querystrings from the registered uri (requests library)" + + HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/?id=123", + body=b"Find the best daily deals") + + response = requests.get('http://yipit.com/', params={'id': 123}) + expect(response.text).to.equal('Find the best daily deals') + expect(HTTPretty.last_request.method).to.equal('GET') + expect(HTTPretty.last_request.path).to.equal('/?id=123') + + +@httprettified +@within(five=microseconds) +def test_streaming_responses(now): + """ + Mock a streaming HTTP response, like those returned by the Twitter streaming + API. + """ + from contextlib import contextmanager + + @contextmanager + def in_time(time, message): + """ + A context manager that uses signals to force a time limit in tests + (unlike the `@within` decorator, which only complains afterward), or + raise an AssertionError. + """ + import signal + + def handler(signum, frame): + raise AssertionError(message) + signal.signal(signal.SIGALRM, handler) + signal.setitimer(signal.ITIMER_REAL, time) + yield + signal.setitimer(signal.ITIMER_REAL, 0) + + #XXX this obviously isn't a fully functional twitter streaming client! + twitter_response_lines = [ + b'{"text":"If \\"for the boobs\\" requests to follow me one more time I\'m calling the police. http://t.co/a0mDEAD8"}\r\n', + b'\r\n', + b'{"text":"RT @onedirection: Thanks for all your #FollowMe1D requests Directioners! We\u2019ll be following 10 people throughout the day starting NOW. G ..."}\r\n' + ] + + TWITTER_STREAMING_URL = "https://stream.twitter.com/1/statuses/filter.json" + + HTTPretty.register_uri(HTTPretty.POST, TWITTER_STREAMING_URL, + body=(l for l in twitter_response_lines), + streaming=True) + + # taken from the requests docs + # Http://docs.python-requests.org/en/latest/user/advanced/#streaming-requests + response = requests.post(TWITTER_STREAMING_URL, data={'track': 'requests'}, + auth=('username', 'password'), stream=True) + + #test iterating by line + line_iter = response.iter_lines() + with in_time(0.01, 'Iterating by line is taking forever!'): + for i in xrange(len(twitter_response_lines)): + expect(next(line_iter).strip()).to.equal( + twitter_response_lines[i].strip()) + + #test iterating by line after a second request + response = requests.post(TWITTER_STREAMING_URL, data={'track': 'requests'}, + auth=('username', 'password'), stream=True) + + line_iter = response.iter_lines() + with in_time(0.01, 'Iterating by line is taking forever the second time ' + 'around!'): + for i in xrange(len(twitter_response_lines)): + expect(next(line_iter).strip()).to.equal( + twitter_response_lines[i].strip()) + + #test iterating by char + response = requests.post(TWITTER_STREAMING_URL, data={'track': 'requests'}, + auth=('username', 'password'), stream=True) + + twitter_expected_response_body = b''.join(twitter_response_lines) + with in_time(0.02, 'Iterating by char is taking forever!'): + twitter_body = b''.join(c for c in response.iter_content(chunk_size=1)) + + expect(twitter_body).to.equal(twitter_expected_response_body) + + #test iterating by chunks larger than the stream + + response = requests.post(TWITTER_STREAMING_URL, data={'track': 'requests'}, + auth=('username', 'password'), stream=True) + + with in_time(0.02, 'Iterating by large chunks is taking forever!'): + twitter_body = b''.join(c for c in + response.iter_content(chunk_size=1024)) + + expect(twitter_body).to.equal(twitter_expected_response_body) + + +@httprettified +def test_multiline(): + url = 'http://httpbin.org/post' + data = b'content=Im\r\na multiline\r\n\r\nsentence\r\n' + headers = { + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', + 'Accept': 'text/plain', + } + HTTPretty.register_uri( + HTTPretty.POST, + url, + ) + response = requests.post(url, data=data, headers=headers) + + expect(response.status_code).to.equal(200) + expect(HTTPretty.last_request.method).to.equal('POST') + expect(HTTPretty.last_request.path).to.equal('/post') + expect(HTTPretty.last_request.body).to.equal(data) + expect(HTTPretty.last_request.headers['content-length']).to.equal('37') + expect(HTTPretty.last_request.headers['content-type']).to.equal('application/x-www-form-urlencoded; charset=utf-8') + expect(len(HTTPretty.latest_requests)).to.equal(1) + + +@httprettified +def test_multipart(): + url = 'http://httpbin.org/post' + data = b'--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="content"\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 68\r\n\r\nAction: comment\nText: Comment with attach\nAttachment: x1.txt, x2.txt\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_2"; filename="x.txt"\r\nContent-Type: text/plain\r\nContent-Length: 4\r\n\r\nbye\n\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_1"; filename="x.txt"\r\nContent-Type: text/plain\r\nContent-Length: 4\r\n\r\nbye\n\r\n--xXXxXXyYYzzz--\r\n' + headers = {'Content-Length': '495', 'Content-Type': 'multipart/form-data; boundary=xXXxXXyYYzzz', 'Accept': 'text/plain'} + HTTPretty.register_uri( + HTTPretty.POST, + url, + ) + response = requests.post(url, data=data, headers=headers) + expect(response.status_code).to.equal(200) + expect(HTTPretty.last_request.method).to.equal('POST') + expect(HTTPretty.last_request.path).to.equal('/post') + expect(HTTPretty.last_request.body).to.equal(data) + expect(HTTPretty.last_request.headers['content-length']).to.equal('495') + expect(HTTPretty.last_request.headers['content-type']).to.equal('multipart/form-data; boundary=xXXxXXyYYzzz') + expect(len(HTTPretty.latest_requests)).to.equal(1) + + +@httprettified +@within(two=microseconds) +def test_callback_response(now): + ("HTTPretty should call a callback function and set its return value as the body of the response" + " requests") + + def request_callback(request, uri, headers): + return [200, headers,"The {0} response from {1}".format(decode_utf8(request.method), uri)] + + HTTPretty.register_uri( + HTTPretty.GET, "https://api.yahoo.com/test", + body=request_callback) + + response = requests.get('https://api.yahoo.com/test') + + expect(response.text).to.equal("The GET response from https://api.yahoo.com/test") + + HTTPretty.register_uri( + HTTPretty.POST, "https://api.yahoo.com/test_post", + body=request_callback) + + response = requests.post( + "https://api.yahoo.com/test_post", + {"username": "gabrielfalcao"} + ) + + expect(response.text).to.equal("The POST response from https://api.yahoo.com/test_post") + +@httprettified +@within(two=microseconds) +def test_callback_body_remains_callable_for_any_subsequent_requests(now): + ("HTTPretty should call a callback function more than one" + " requests") + + def request_callback(request, uri, headers): + return [200, headers,"The {0} response from {1}".format(decode_utf8(request.method), uri)] + + HTTPretty.register_uri( + HTTPretty.GET, "https://api.yahoo.com/test", + body=request_callback) + + response = requests.get('https://api.yahoo.com/test') + expect(response.text).to.equal("The GET response from https://api.yahoo.com/test") + + response = requests.get('https://api.yahoo.com/test') + expect(response.text).to.equal("The GET response from https://api.yahoo.com/test") + +@httprettified +@within(two=microseconds) +def test_callback_setting_headers_and_status_response(now): + ("HTTPretty should call a callback function and uses it retur tuple as status code, headers and body" + " requests") + + def request_callback(request, uri, headers): + headers.update({'a':'b'}) + return [418,headers,"The {0} response from {1}".format(decode_utf8(request.method), uri)] + + HTTPretty.register_uri( + HTTPretty.GET, "https://api.yahoo.com/test", + body=request_callback) + + response = requests.get('https://api.yahoo.com/test') + expect(response.text).to.equal("The GET response from https://api.yahoo.com/test") + expect(response.headers).to.have.key('a').being.equal("b") + expect(response.status_code).to.be(418) + + HTTPretty.register_uri( + HTTPretty.POST, "https://api.yahoo.com/test_post", + body=request_callback) + + response = requests.post( + "https://api.yahoo.com/test_post", + {"username": "gabrielfalcao"} + ) + + expect(response.text).to.equal("The POST response from https://api.yahoo.com/test_post") + expect(response.headers).to.have.key('a').being.equal("b") + expect(response.status_code).to.be(418) + +@httprettified +def test_httpretty_should_allow_registering_regexes_and_give_a_proper_match_to_the_callback(): + "HTTPretty should allow registering regexes with requests and giva a proper match to the callback" + + HTTPretty.register_uri( + HTTPretty.GET, + re.compile("https://api.yipit.com/v1/deal;brand=(?P<brand_name>\w+)"), + body=lambda method,uri,headers: [200,headers,uri] + ) + + response = requests.get('https://api.yipit.com/v1/deal;brand=gap?first_name=chuck&last_name=norris') + + expect(response.text).to.equal('https://api.yipit.com/v1/deal;brand=gap?first_name=chuck&last_name=norris') + expect(HTTPretty.last_request.method).to.equal('GET') + expect(HTTPretty.last_request.path).to.equal('/v1/deal;brand=gap?first_name=chuck&last_name=norris') + +@httprettified +def test_httpretty_should_allow_registering_regexes(): + "HTTPretty should allow registering regexes with requests" + + HTTPretty.register_uri( + HTTPretty.GET, + re.compile("https://api.yipit.com/v1/deal;brand=(?P<brand_name>\w+)"), + body="Found brand", + ) + + response = requests.get('https://api.yipit.com/v1/deal;brand=gap?first_name=chuck&last_name=norris' + ) + expect(response.text).to.equal('Found brand') + expect(HTTPretty.last_request.method).to.equal('GET') + expect(HTTPretty.last_request.path).to.equal('/v1/deal;brand=gap?first_name=chuck&last_name=norris') + + +@httprettified +def test_httpretty_provides_easy_access_to_querystrings_with_regexes(): + "HTTPretty should match regexes even if they have a different querystring" + + HTTPretty.register_uri( + HTTPretty.GET, + re.compile("https://api.yipit.com/v1/(?P<endpoint>\w+)/$"), + body="Find the best daily deals" + ) + + response = requests.get('https://api.yipit.com/v1/deals/?foo=bar&foo=baz&chuck=norris') + expect(response.text).to.equal("Find the best daily deals") + expect(HTTPretty.last_request.querystring).to.equal({ + 'foo': ['bar', 'baz'], + 'chuck': ['norris'], + }) + + +@httprettified +def test_httpretty_allows_to_chose_if_querystring_should_be_matched(): + "HTTPretty should provide a way to not match regexes that have a different querystring" + + HTTPretty.register_uri( + HTTPretty.GET, + re.compile("https://example.org/(?P<endpoint>\w+)/$"), + body="Nudge, nudge, wink, wink. Know what I mean?", + match_querystring=True + ) + + response = requests.get('https://example.org/what/') + expect(response.text).to.equal('Nudge, nudge, wink, wink. Know what I mean?') + try: + requests.get('https://example.org/what/?flying=coconuts') + raised = False + except requests.ConnectionError: + raised = True + + assert raised is True + + +@httprettified +def test_httpretty_should_allow_multiple_methods_for_the_same_uri(): + "HTTPretty should allow registering multiple methods for the same uri" + + url = 'http://test.com/test' + methods = ['GET', 'POST', 'PUT', 'OPTIONS'] + for method in methods: + HTTPretty.register_uri( + getattr(HTTPretty, method), + url, + method + ) + + for method in methods: + request_action = getattr(requests, method.lower()) + expect(request_action(url).text).to.equal(method) + + +@httprettified +def test_httpretty_should_allow_registering_regexes_with_streaming_responses(): + "HTTPretty should allow registering regexes with streaming responses" + import os + os.environ['DEBUG'] = 'true' + + def my_callback(request, url, headers): + request.body.should.equal('hithere') + return 200, headers, "Received" + + HTTPretty.register_uri( + HTTPretty.POST, + re.compile("https://api.yipit.com/v1/deal;brand=(?P<brand_name>\w+)"), + body=my_callback, + ) + + def gen(): + yield b'hi' + yield b'there' + + response = requests.post( + 'https://api.yipit.com/v1/deal;brand=gap?first_name=chuck&last_name=norris', + data=gen(), + ) + expect(response.content).to.equal("Received") + expect(HTTPretty.last_request.method).to.equal('POST') + expect(HTTPretty.last_request.path).to.equal('/v1/deal;brand=gap?first_name=chuck&last_name=norris') + + +@httprettified +def test_httpretty_should_allow_multiple_responses_with_multiple_methods(): + "HTTPretty should allow multiple responses when binding multiple methods to the same uri" + + url = 'http://test.com/list' + + #add get responses + HTTPretty.register_uri(HTTPretty.GET, url, + responses=[HTTPretty.Response(body='a'), + HTTPretty.Response(body='b') + ] + ) + + #add post responses + HTTPretty.register_uri(HTTPretty.POST, url, + responses=[HTTPretty.Response(body='c'), + HTTPretty.Response(body='d') + ] + ) + + expect(requests.get(url).text).to.equal('a') + expect(requests.post(url).text).to.equal('c') + + expect(requests.get(url).text).to.equal('b') + expect(requests.get(url).text).to.equal('b') + expect(requests.get(url).text).to.equal('b') + + expect(requests.post(url).text).to.equal('d') + expect(requests.post(url).text).to.equal('d') + expect(requests.post(url).text).to.equal('d') + + +@httprettified +def test_httpretty_should_normalize_url_patching(): + "HTTPretty should normalize all url patching" + + HTTPretty.register_uri( + HTTPretty.GET, + "http://yipit.com/foo(bar)", + body="Find the best daily deals") + + response = requests.get('http://yipit.com/foo%28bar%29') + expect(response.text).to.equal('Find the best daily deals') + + +@httprettified +def test_lack_of_trailing_slash(): + ("HTTPretty should automatically append a slash to given urls") + url = 'http://www.youtube.com' + HTTPretty.register_uri(HTTPretty.GET, url, body='') + response = requests.get(url) + response.status_code.should.equal(200) + + +@httprettified +def test_unicode_querystrings(): + ("Querystrings should accept unicode characters") + HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/login", + body="Find the best daily deals") + requests.get('http://yipit.com/login?user=Gabriel+Falcão') + expect(HTTPretty.last_request.querystring['user'][0]).should.be.equal('Gabriel Falcão') + + +@use_tornado_server +def test_recording_calls(): + ("HTTPretty should be able to record calls") + # Given a destination path: + destination = FIXTURE_FILE("recording-.json") + + # When I record some calls + with HTTPretty.record(destination): + requests.get(server_url("/foobar?name=Gabriel&age=25")) + requests.post(server_url("/foobar"), data=json.dumps({'test': '123'})) + + # Then the destination path should exist + os.path.exists(destination).should.be.true + + # And the contents should be json + raw = open(destination).read() + json.loads.when.called_with(raw).should_not.throw(ValueError) + + # And the contents should be expected + data = json.loads(raw) + data.should.be.a(list) + data.should.have.length_of(2) + + # And the responses should have the expected keys + response = data[0] + response.should.have.key("request").being.length_of(5) + response.should.have.key("response").being.length_of(3) + + response['request'].should.have.key("method").being.equal("GET") + response['request'].should.have.key("headers").being.a(dict) + response['request'].should.have.key("querystring").being.equal({ + "age": [ + "25" + ], + "name": [ + "Gabriel" + ] + }) + response['response'].should.have.key("status").being.equal(200) + response['response'].should.have.key("body").being.an(unicode) + response['response'].should.have.key("headers").being.a(dict) + response['response']["headers"].should.have.key("server").being.equal("TornadoServer/2.4") + + +def test_playing_calls(): + ("HTTPretty should be able to record calls") + # Given a destination path: + destination = FIXTURE_FILE("playback-1.json") + + # When I playback some previously recorded calls + with HTTPretty.playback(destination): + # And make the expected requests + response1 = requests.get(server_url("/foobar?name=Gabriel&age=25")) + response2 = requests.post(server_url("/foobar"), data=json.dumps({'test': '123'})) + + # Then the responses should be the expected + response1.json().should.equal({"foobar": {"age": "25", "name": "Gabriel"}}) + response2.json().should.equal({"foobar": {}}) + + +@httprettified +def test_py26_callback_response(): + ("HTTPretty should call a callback function *once* and set its return value" + " as the body of the response requests") + + from mock import Mock + + def _request_callback(request, uri, headers): + return [200, headers,"The {0} response from {1}".format(decode_utf8(request.method), uri)] + + request_callback = Mock() + request_callback.side_effect = _request_callback + + HTTPretty.register_uri( + HTTPretty.POST, "https://api.yahoo.com/test_post", + body=request_callback) + + response = requests.post( + "https://api.yahoo.com/test_post", + {"username": "gabrielfalcao"} + ) + os.environ['STOP'] = 'true' + expect(request_callback.call_count).equal(1) + + +import json + + +def hello(): + return json.dumps({ + 'href': 'http://foobar.com' + }) diff --git a/tests/functional/test_urllib2.py b/tests/functional/test_urllib2.py new file mode 100644 index 0000000..cb84f00 --- /dev/null +++ b/tests/functional/test_urllib2.py @@ -0,0 +1,337 @@ +# #!/usr/bin/env python +# -*- coding: utf-8 -*- + +# <HTTPretty - HTTP client mock for Python> +# Copyright (C) <2011-2013> Gabriel Falcão <gabriel@nacaolivre.org> +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# 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. +from __future__ import unicode_literals + +try: + from urllib.request import urlopen + import urllib.request as urllib2 +except ImportError: + import urllib2 + urlopen = urllib2.urlopen + +from sure import * +from httpretty import HTTPretty, httprettified +from httpretty.core import decode_utf8 + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_mock_a_simple_get_with_urllib2_read(): + "HTTPretty should mock a simple GET with urllib2.read()" + + HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/", + body="Find the best daily deals") + + fd = urlopen('http://yipit.com') + got = fd.read() + fd.close() + + expect(got).to.equal(b'Find the best daily deals') + + +@httprettified +@within(two=microseconds) +def test_httpretty_provides_easy_access_to_querystrings(now): + "HTTPretty should provide an easy access to the querystring" + + HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/", + body="Find the best daily deals") + + fd = urllib2.urlopen('http://yipit.com/?foo=bar&foo=baz&chuck=norris') + fd.read() + fd.close() + + expect(HTTPretty.last_request.querystring).to.equal({ + 'foo': ['bar', 'baz'], + 'chuck': ['norris'], + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_mock_headers_urllib2(now): + "HTTPretty should mock basic headers with urllib2" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/", + body="this is supposed to be the response", + status=201) + + request = urlopen('http://github.com') + + headers = dict(request.headers) + request.close() + + expect(request.code).to.equal(201) + expect(headers).to.equal({ + 'content-type': 'text/plain; charset=utf-8', + 'connection': 'close', + 'content-length': '35', + 'status': '201', + 'server': 'Python/HTTPretty', + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_allow_adding_and_overwritting_urllib2(now): + "HTTPretty should allow adding and overwritting headers with urllib2" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/", + body="this is supposed to be the response", + adding_headers={ + 'Server': 'Apache', + 'Content-Length': '27', + 'Content-Type': 'application/json', + }) + + request = urlopen('http://github.com') + headers = dict(request.headers) + request.close() + + expect(request.code).to.equal(200) + expect(headers).to.equal({ + 'content-type': 'application/json', + 'connection': 'close', + 'content-length': '27', + 'status': '200', + 'server': 'Apache', + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_allow_forcing_headers_urllib2(): + "HTTPretty should allow forcing headers with urllib2" + + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/", + body="this is supposed to be the response", + forcing_headers={ + 'Content-Type': 'application/xml', + }) + + request = urlopen('http://github.com') + headers = dict(request.headers) + request.close() + + expect(headers).to.equal({ + 'content-type': 'application/xml', + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_allow_adding_and_overwritting_by_kwargs_u2(now): + "HTTPretty should allow adding and overwritting headers by " \ + "keyword args with urllib2" + + body = "this is supposed to be the response, indeed" + HTTPretty.register_uri(HTTPretty.GET, "http://github.com/", + body=body, + server='Apache', + content_length=len(body), + content_type='application/json') + + request = urlopen('http://github.com') + headers = dict(request.headers) + request.close() + + expect(request.code).to.equal(200) + expect(headers).to.equal({ + 'content-type': 'application/json', + 'connection': 'close', + 'content-length': str(len(body)), + 'status': '200', + 'server': 'Apache', + 'date': now.strftime('%a, %d %b %Y %H:%M:%S GMT'), + }) + + +@httprettified +@within(two=microseconds) +def test_httpretty_should_support_a_list_of_successive_responses_urllib2(now): + "HTTPretty should support adding a list of successive " \ + "responses with urllib2" + + HTTPretty.register_uri( + HTTPretty.GET, "https://api.yahoo.com/test", + responses=[ + HTTPretty.Response(body="first response", status=201), + HTTPretty.Response(body='second and last response', status=202), + ]) + + request1 = urlopen('https://api.yahoo.com/test') + body1 = request1.read() + request1.close() + + expect(request1.code).to.equal(201) + expect(body1).to.equal(b'first response') + + request2 = urlopen('https://api.yahoo.com/test') + body2 = request2.read() + request2.close() + expect(request2.code).to.equal(202) + expect(body2).to.equal(b'second and last response') + + request3 = urlopen('https://api.yahoo.com/test') + body3 = request3.read() + request3.close() + expect(request3.code).to.equal(202) + expect(body3).to.equal(b'second and last response') + + +@httprettified +@within(two=microseconds) +def test_can_inspect_last_request(now): + "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', + b'{"username": "gabrielfalcao"}', + { + 'content-type': 'text/json', + }, + ) + fd = urlopen(request) + got = fd.read() + fd.close() + + expect(HTTPretty.last_request.method).to.equal('POST') + expect(HTTPretty.last_request.body).to.equal( + b'{"username": "gabrielfalcao"}', + ) + expect(HTTPretty.last_request.headers['content-type']).to.equal( + 'text/json', + ) + expect(got).to.equal(b'{"repositories": ["HTTPretty", "lettuce"]}') + + +@httprettified +@within(two=microseconds) +def test_can_inspect_last_request_with_ssl(now): + "HTTPretty.last_request is recorded even when mocking 'https' (SSL)" + + HTTPretty.register_uri(HTTPretty.POST, "https://secure.github.com/", + body='{"repositories": ["HTTPretty", "lettuce"]}') + + request = urllib2.Request( + 'https://secure.github.com', + b'{"username": "gabrielfalcao"}', + { + 'content-type': 'text/json', + }, + ) + fd = urlopen(request) + got = fd.read() + fd.close() + + expect(HTTPretty.last_request.method).to.equal('POST') + expect(HTTPretty.last_request.body).to.equal( + b'{"username": "gabrielfalcao"}', + ) + expect(HTTPretty.last_request.headers['content-type']).to.equal( + 'text/json', + ) + expect(got).to.equal(b'{"repositories": ["HTTPretty", "lettuce"]}') + + +@httprettified +@within(two=microseconds) +def test_httpretty_ignores_querystrings_from_registered_uri(): + "HTTPretty should mock a simple GET with urllib2.read()" + + HTTPretty.register_uri(HTTPretty.GET, "http://yipit.com/?id=123", + body="Find the best daily deals") + + fd = urlopen('http://yipit.com/?id=123') + got = fd.read() + fd.close() + + expect(got).to.equal(b'Find the best daily deals') + expect(HTTPretty.last_request.method).to.equal('GET') + expect(HTTPretty.last_request.path).to.equal('/?id=123') + + +@httprettified +@within(two=microseconds) +def test_callback_response(now): + ("HTTPretty should all a callback function to be set as the body with" + " urllib2") + + def request_callback(request, uri, headers): + return [200, headers, "The {0} response from {1}".format(decode_utf8(request.method), uri)] + + HTTPretty.register_uri( + HTTPretty.GET, "https://api.yahoo.com/test", + body=request_callback) + + fd = urllib2.urlopen('https://api.yahoo.com/test') + got = fd.read() + fd.close() + + expect(got).to.equal(b"The GET response from https://api.yahoo.com/test") + + HTTPretty.register_uri( + HTTPretty.POST, "https://api.yahoo.com/test_post", + body=request_callback) + + request = urllib2.Request( + "https://api.yahoo.com/test_post", + b'{"username": "gabrielfalcao"}', + { + 'content-type': 'text/json', + }, + ) + fd = urllib2.urlopen(request) + got = fd.read() + fd.close() + + expect(got).to.equal(b"The POST response from https://api.yahoo.com/test_post") + + +@httprettified +def test_httpretty_should_allow_registering_regexes(): + "HTTPretty should allow registering regexes with urllib2" + + HTTPretty.register_uri( + HTTPretty.GET, + re.compile("https://api.yipit.com/v1/deal;brand=(?P<brand_name>\w+)"), + body="Found brand", + ) + + request = urllib2.Request( + "https://api.yipit.com/v1/deal;brand=GAP", + ) + fd = urllib2.urlopen(request) + got = fd.read() + fd.close() + + expect(got).to.equal(b"Found brand") diff --git a/tests/functional/testserver.py b/tests/functional/testserver.py new file mode 100644 index 0000000..db070ba --- /dev/null +++ b/tests/functional/testserver.py @@ -0,0 +1,171 @@ +# #!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# <HTTPretty - HTTP client mock for Python> +# Copyright (C) <2011-2013> Gabriel Falcão <gabriel@nacaolivre.org> +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# 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. +from __future__ import unicode_literals + +import os +import sys + +try: + import io + StringIO = io.StringIO +except ImportError: + import StringIO + StringIO = StringIO.StringIO + +import time +import socket +from tornado.web import Application +from tornado.web import RequestHandler +from tornado.httpserver import HTTPServer +from tornado.ioloop import IOLoop +from multiprocessing import Process + +PY3 = sys.version_info[0] == 3 +if PY3: + text_type = str + byte_type = bytes +else: + text_type = unicode + byte_type = str + + +def utf8(s): + if isinstance(s, text_type): + s = s.encode('utf-8') + + return byte_type(s) + +true_socket = socket.socket + +PY3 = sys.version_info[0] == 3 + +if not PY3: + bytes = lambda s, *args: str(s) + + +class BubblesHandler(RequestHandler): + def get(self): + self.write(". o O 0 O o . o O 0 O o . o O 0 O o . o O 0 O o . o O 0 O o .") + + +class ComeHandler(RequestHandler): + def get(self): + self.write("<- HELLO WORLD ->") + + +class TornadoServer(object): + is_running = False + + def __init__(self, port): + self.port = int(port) + self.process = None + + @classmethod + def get_handlers(cls): + return Application([ + (r"/go-for-bubbles/?", BubblesHandler), + (r"/come-again/?", ComeHandler), + ]) + + def start(self): + def go(app, port, data={}): + from httpretty import HTTPretty + HTTPretty.disable() + http = HTTPServer(app) + http.listen(int(port)) + IOLoop.instance().start() + + app = self.get_handlers() + + data = {} + args = (app, self.port, data) + self.process = Process(target=go, args=args) + self.process.start() + time.sleep(0.4) + + def stop(self): + try: + os.kill(self.process.pid, 9) + except OSError: + self.process.terminate() + finally: + self.is_running = False + + +class TCPServer(object): + def __init__(self, port): + self.port = int(port) + + def start(self): + def go(port): + from httpretty import HTTPretty + HTTPretty.disable() + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(('localhost', port)) + s.listen(True) + conn, addr = s.accept() + + while True: + data = conn.recv(1024) + conn.send(b"RECEIVED: " + bytes(data)) + + conn.close() + + args = [self.port] + self.process = Process(target=go, args=args) + self.process.start() + time.sleep(0.4) + + def stop(self): + try: + os.kill(self.process.pid, 9) + except OSError: + self.process.terminate() + finally: + self.is_running = False + + +class TCPClient(object): + def __init__(self, port): + self.port = int(port) + self.sock = true_socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect(('localhost', self.port)) + + def send(self, what): + data = bytes(what, 'utf-8') + self.sock.sendall(data) + return self.sock.recv(len(data) + 11) + + def close(self): + try: + self.sock.close() + except socket.error: + pass # already closed + + def __del__(self): + self.close() |
