summaryrefslogtreecommitdiff
path: root/tests/functional
diff options
context:
space:
mode:
Diffstat (limited to 'tests/functional')
-rw-r--r--tests/functional/__init__.py28
-rw-r--r--tests/functional/base.py98
-rw-r--r--tests/functional/fixtures/.placeholder0
-rw-r--r--tests/functional/fixtures/playback-1.json58
-rw-r--r--tests/functional/test_bypass.py136
-rw-r--r--tests/functional/test_debug.py105
-rw-r--r--tests/functional/test_decorator.py48
-rw-r--r--tests/functional/test_httplib2.py306
-rw-r--r--tests/functional/test_requests.py751
-rw-r--r--tests/functional/test_urllib2.py337
-rw-r--r--tests/functional/testserver.py171
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()