forked from tornadoweb/tornado
-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #135 from dpnova/feature/unittesting
Feature/unittesting
- Loading branch information
Showing
11 changed files
with
458 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[report] | ||
include = */cyclone/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ dist | |
cyclone.egg-info | ||
dropin.cache | ||
*DS_Store* | ||
_trial_temp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
language: python | ||
python: 2.7 | ||
|
||
install: | ||
- pip install -r cyclone/tests/test_requirements.txt | ||
- pip install coveralls | ||
|
||
script: coverage run `which trial` cyclone | ||
after_success: coveralls |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# | ||
# Copyright 2014 David Novakovic | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
# not use this file except in compliance with the License. You may obtain | ||
# a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations | ||
# under the License. | ||
|
||
from .testcase import CycloneTestCase | ||
from .client import Client |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# | ||
# Copyright 2014 David Novakovic | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
# not use this file except in compliance with the License. You may obtain | ||
# a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations | ||
# under the License. | ||
|
||
from cyclone.httpserver import HTTPRequest, HTTPConnection | ||
import urllib | ||
from twisted.test import proto_helpers | ||
from twisted.internet.defer import inlineCallbacks, returnValue | ||
|
||
|
||
class Client(object): | ||
def __init__(self, app): | ||
self.app = app | ||
|
||
def get(self, uri, params=None, version="HTTP/1.0", headers=None, | ||
body=None, remote_ip=None, protocol=None, host=None, | ||
files=None, connection=None): | ||
return self.request( | ||
"GET", uri, params=params, version=version, headers=headers, | ||
body=body, remote_ip=remote_ip, protocol=protocol, host=host, | ||
files=files, connection=connection | ||
) | ||
|
||
def put(self, uri, params=None, version="HTTP/1.0", headers=None, | ||
body=None, remote_ip=None, protocol=None, host=None, | ||
files=None, connection=None): | ||
return self.request( | ||
"PUT", uri, params=params, version=version, headers=headers, | ||
body=body, remote_ip=remote_ip, protocol=protocol, host=host, | ||
files=files, connection=connection | ||
) | ||
|
||
def post(self, uri, params=None, version="HTTP/1.0", headers=None, | ||
body=None, remote_ip=None, protocol=None, host=None, | ||
files=None, connection=None): | ||
return self.request( | ||
"POST", uri, params=params, version=version, headers=headers, | ||
body=body, remote_ip=remote_ip, protocol=protocol, host=host, | ||
files=files, connection=connection | ||
) | ||
|
||
def delete(self, uri, params=None, version="HTTP/1.0", headers=None, | ||
body=None, remote_ip=None, protocol=None, host=None, | ||
files=None, connection=None): | ||
return self.request( | ||
"DELETE", uri, params=params, version=version, headers=headers, | ||
body=body, remote_ip=remote_ip, protocol=protocol, host=host, | ||
files=files, connection=connection | ||
) | ||
|
||
def head(self, uri, params=None, version="HTTP/1.0", headers=None, | ||
body=None, remote_ip=None, protocol=None, host=None, | ||
files=None, connection=None): | ||
return self.request( | ||
"HEAD", uri, params=params, version=version, headers=headers, | ||
body=body, remote_ip=remote_ip, protocol=protocol, host=host, | ||
files=files, connection=connection | ||
) | ||
|
||
@inlineCallbacks | ||
def request(self, method, uri, *args, **kwargs): | ||
params = kwargs.pop("params") | ||
if params: | ||
uri = uri + "?" + urllib.urlencode(params) | ||
connection = kwargs.pop('connection') | ||
if not connection: | ||
connection = HTTPConnection() | ||
connection.xheaders = False | ||
kwargs['connection'] = connection | ||
connection.factory = self.app | ||
|
||
request = HTTPRequest(method, uri, *args, **kwargs) | ||
connection.connectionMade() | ||
connection._request = request | ||
connection.transport = proto_helpers.StringTransport() | ||
request.remote_ip = connection.transport.getHost().host | ||
handler = self.app(request) | ||
|
||
def setup_response(): | ||
response_body = connection.transport.io.getvalue() | ||
handler.content = response_body.split("\r\n\r\n", 1)[1] | ||
handler.headers = handler._headers | ||
|
||
if handler._finished: | ||
setup_response() | ||
returnValue(handler) | ||
yield connection.notifyFinish() | ||
setup_response() | ||
returnValue(handler) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# | ||
# Copyright 2014 David Novakovic | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
# not use this file except in compliance with the License. You may obtain | ||
# a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations | ||
# under the License. | ||
|
||
from twisted.trial import unittest | ||
from .client import Client | ||
|
||
|
||
class CycloneTestCase(unittest.TestCase, object): | ||
client_impl = Client | ||
|
||
def __init__(self, app_builder, *args, **kwargs): | ||
""" | ||
Create a test case for a cyclone app. | ||
The ``app_builder`` param should be a function that returns a | ||
cyclone.web.Application instance will all the appropriate handlers | ||
loaded etc. | ||
For most use cases this should be as simple as creating a function | ||
that returns you application instead of just declaring it in a file | ||
somewhere. | ||
""" | ||
super(CycloneTestCase, self).__init__(*args, **kwargs) | ||
self._app = app_builder() | ||
self.client = self.client_impl(self._app) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# | ||
# Copyright 2014 David Novakovic | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
# not use this file except in compliance with the License. You may obtain | ||
# a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations | ||
# under the License. | ||
|
||
from twisted.trial import unittest | ||
|
||
class AppTests(unittest.TestCase): | ||
def test_something(self): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
# | ||
# Copyright 2014 David Novakovic | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
# not use this file except in compliance with the License. You may obtain | ||
# a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations | ||
# under the License. | ||
|
||
from twisted.trial import unittest | ||
from cyclone.httpclient import StringProducer, Receiver, HTTPClient | ||
import cyclone.httpclient | ||
from cStringIO import StringIO | ||
from twisted.internet.defer import inlineCallbacks, Deferred, succeed | ||
from mock import Mock | ||
import functools | ||
from cyclone import escape | ||
from cyclone.web import HTTPError | ||
|
||
|
||
class TestStringProducer(unittest.TestCase): | ||
@inlineCallbacks | ||
def test_stringproducer(self): | ||
text = "some text" | ||
producer = StringProducer(text) | ||
self.assertEqual(producer.length, len(text)) | ||
consumer = StringIO() | ||
yield producer.startProducing(consumer) | ||
self.assertEqual(consumer.getvalue(), text) | ||
|
||
|
||
class TestReceiver(unittest.TestCase): | ||
def test_receiver(self): | ||
text = "Some text" | ||
mock = Mock() | ||
finished = Deferred().addCallback(mock) | ||
receiver = Receiver(finished) | ||
receiver.dataReceived(text) | ||
receiver.dataReceived(text) | ||
receiver.connectionLost(None) | ||
mock.assert_called_with("Some textSome text") | ||
|
||
|
||
class TestHTTPClient(unittest.TestCase): | ||
URL = "http://example.com" | ||
|
||
def test_create_client(self): | ||
client = HTTPClient(self.URL) | ||
self.assertEqual(client._args, ()) | ||
|
||
def test_create_client_with_proxy(self): | ||
client = HTTPClient(self.URL, proxy=("example.com", 8080)) | ||
self.assertEqual(client.proxyConfig, ("example.com", 8080)) | ||
self.assertEqual(client.agent._proxyEndpoint._port, 8080) | ||
self.assertEqual(client.agent._proxyEndpoint._host, "example.com") | ||
|
||
def test_ensure_method_set_properly(self): | ||
client = HTTPClient(self.URL, postdata="something") | ||
self.assertEqual(client.method, "POST") | ||
client = HTTPClient(self.URL) | ||
self.assertEqual(client.method, "GET") | ||
|
||
def test_ensure_contenttype_set_properly(self): | ||
client = HTTPClient(self.URL, postdata="something") | ||
self.assertEqual( | ||
client.headers, | ||
{'Content-Type': ['application/x-www-form-urlencoded']} | ||
) | ||
client = HTTPClient(self.URL, postdata="something", headers={ | ||
"Content-Type": "nothing" | ||
}) | ||
self.assertEqual(client.headers, {"Content-Type": "nothing"}) | ||
|
||
def test_slightly_ambiguous_things(self): | ||
""" | ||
Test some broken things. | ||
This is to make sure we dont break backwards compat | ||
if they are ever fixed. | ||
""" | ||
client = HTTPClient(self.URL, postdata="") | ||
self.assertEqual(client.method, "GET") | ||
|
||
@inlineCallbacks | ||
def test_fetch_basic(self): | ||
client = HTTPClient("http://example.com") | ||
client.agent = Mock() | ||
_response = Mock() | ||
_response.headers.getAllRawHeaders.return_value = {} | ||
_response.deliverBody = lambda x: x.dataReceived("done") \ | ||
or x.connectionLost(None) | ||
client.agent.request.return_value = succeed(_response) | ||
response = yield client.fetch() | ||
self.assertEqual(response.body, "done") | ||
|
||
@inlineCallbacks | ||
def test_fetch_head(self): | ||
client = HTTPClient("http://example.com", method="HEAD") | ||
client.agent = Mock() | ||
_response = Mock() | ||
_response.headers.getAllRawHeaders.return_value = {} | ||
_response.deliverBody = lambda x: x.connectionLost(None) | ||
client.agent.request.return_value = succeed(_response) | ||
response = yield client.fetch() | ||
self.assertEqual(response.body, "") | ||
|
||
@inlineCallbacks | ||
def test_fetch_redirect(self): | ||
client = HTTPClient("http://example.com") | ||
client.agent = Mock() | ||
_response = Mock() | ||
_response.code = 302 | ||
_response.headers.getAllRawHeaders.return_value = { | ||
"Location": "http://example.com" | ||
} | ||
_response.deliverBody = lambda x: x.connectionLost(None) | ||
client.agent.request.return_value = succeed(_response) | ||
response = yield client.fetch() | ||
self.assertEqual(response.body, "") | ||
self.assertEqual(_response.headers, {"Location": "http://example.com"}) | ||
|
||
|
||
class JsonRPCTest(unittest.TestCase): | ||
URL = "http://example.com/jsonrpc" | ||
|
||
def setUp(self): | ||
self._old_fetch = cyclone.httpclient.fetch | ||
cyclone.httpclient.fetch = Mock() | ||
self.client = cyclone.httpclient.JsonRPC(self.URL) | ||
|
||
def tearDown(self): | ||
cyclone.httpclient.fetch = self._old_fetch | ||
|
||
def test_create_client(self): | ||
client = cyclone.httpclient.JsonRPC(self.URL) | ||
self.assertEqual(client.__dict__['_JsonRPC__rpcId'], 0) | ||
self.assertEqual(client.__dict__['_JsonRPC__rpcUrl'], self.URL) | ||
|
||
def test_client_method_access(self): | ||
method = self.client.foo | ||
self.assertTrue(isinstance(method, functools.partial)) | ||
self.assertTrue(method.args[0], 'foo') | ||
|
||
@inlineCallbacks | ||
def test_rpc_request(self): | ||
response = Mock() | ||
response.code = 200 | ||
response.body = escape.json_encode({"result": True}) | ||
cyclone.httpclient.fetch.return_value = succeed(response) | ||
result = yield self.client.foo() | ||
self.assertTrue(result) | ||
|
||
@inlineCallbacks | ||
def test_rpc_request_error(self): | ||
response = Mock() | ||
response.code = 200 | ||
response.body = escape.json_encode({"error": {"message": "failed"}}) | ||
cyclone.httpclient.fetch.return_value = succeed(response) | ||
try: | ||
yield self.client.foo() | ||
except Exception, e: | ||
self.assertEqual(e.message, "failed") | ||
else: | ||
raise Exception("Should raise an error.") | ||
|
||
@inlineCallbacks | ||
def test_rpc_request_error_old(self): | ||
response = Mock() | ||
response.code = 200 | ||
response.body = escape.json_encode({"error": "some error"}) | ||
cyclone.httpclient.fetch.return_value = succeed(response) | ||
try: | ||
yield self.client.foo() | ||
except Exception, e: | ||
self.assertEqual(e.message, "some error") | ||
else: | ||
raise Exception("Should raise an error.") | ||
|
||
@inlineCallbacks | ||
def test_rpc_request_404(self): | ||
response = Mock() | ||
response.code = 404 | ||
response.phrase = "Not found." | ||
response.body = escape.json_encode({"result": True}) | ||
cyclone.httpclient.fetch.return_value = succeed(response) | ||
try: | ||
yield self.client.foo() | ||
except HTTPError, e: | ||
self.assertEqual(e.log_message, "Not found.") | ||
else: | ||
raise Exception("Should raise an error.") |
Oops, something went wrong.