Skip to content

Commit

Permalink
Merge pull request #135 from dpnova/feature/unittesting
Browse files Browse the repository at this point in the history
Feature/unittesting
  • Loading branch information
fiorix committed Aug 2, 2014
2 parents eb2f112 + fe74476 commit d35b722
Show file tree
Hide file tree
Showing 11 changed files with 458 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[report]
include = */cyclone/*
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dist
cyclone.egg-info
dropin.cache
*DS_Store*
_trial_temp
9 changes: 9 additions & 0 deletions .travis.yml
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
17 changes: 17 additions & 0 deletions cyclone/testing/__init__.py
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
100 changes: 100 additions & 0 deletions cyclone/testing/client.py
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)
37 changes: 37 additions & 0 deletions cyclone/testing/testcase.py
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 added cyclone/tests/__init__.py
Empty file.
20 changes: 20 additions & 0 deletions cyclone/tests/test_app.py
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
197 changes: 197 additions & 0 deletions cyclone/tests/test_httpclient.py
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.")

0 comments on commit d35b722

Please sign in to comment.