From c6cf5c740769c6b689aa1bac4f2962e01a5d320b Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Wed, 30 Jul 2014 21:36:18 +1000 Subject: [PATCH 01/14] Some testing setup and tests for httpclient. --- cyclone/testing/__init__.py | 0 cyclone/tests/__init__.py | 0 cyclone/tests/test_app.py | 5 ++ cyclone/tests/test_httpclient.py | 123 +++++++++++++++++++++++++++++++ requirements.txt | 2 + 5 files changed, 130 insertions(+) create mode 100644 cyclone/testing/__init__.py create mode 100644 cyclone/tests/__init__.py create mode 100644 cyclone/tests/test_app.py create mode 100644 cyclone/tests/test_httpclient.py create mode 100644 requirements.txt diff --git a/cyclone/testing/__init__.py b/cyclone/testing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cyclone/tests/__init__.py b/cyclone/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cyclone/tests/test_app.py b/cyclone/tests/test_app.py new file mode 100644 index 0000000000..ce2a297043 --- /dev/null +++ b/cyclone/tests/test_app.py @@ -0,0 +1,5 @@ +from twisted.trial import unittest + +class AppTests(unittest.TestCase): + def test_something(self): + pass \ No newline at end of file diff --git a/cyclone/tests/test_httpclient.py b/cyclone/tests/test_httpclient.py new file mode 100644 index 0000000000..358f271c90 --- /dev/null +++ b/cyclone/tests/test_httpclient.py @@ -0,0 +1,123 @@ +# +# Copyright 2010 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 +from cStringIO import StringIO +from twisted.internet.defer import inlineCallbacks, Deferred, succeed +from mock import Mock + + +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"}) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..513cbd2c68 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +twisted>=12.0 +mock \ No newline at end of file From d602270fd609f28957464380c17fc00e10dc751e Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Thu, 31 Jul 2014 10:13:27 +1000 Subject: [PATCH 02/14] First cut of some tests and a testing tool. --- .gitignore | 1 + cyclone/testing/__init__.py | 2 + cyclone/testing/client.py | 101 ++++++++++++++++++++++++++++++++++ cyclone/testing/testcase.py | 33 +++++++++++ cyclone/tests/test_app.py | 15 +++++ cyclone/tests/test_testing.py | 58 +++++++++++++++++++ 6 files changed, 210 insertions(+) create mode 100644 cyclone/testing/client.py create mode 100644 cyclone/testing/testcase.py create mode 100644 cyclone/tests/test_testing.py diff --git a/.gitignore b/.gitignore index dfb4104698..ec489a0d20 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist cyclone.egg-info dropin.cache *DS_Store* +_trial_temp diff --git a/cyclone/testing/__init__.py b/cyclone/testing/__init__.py index e69de29bb2..6bb21994dc 100644 --- a/cyclone/testing/__init__.py +++ b/cyclone/testing/__init__.py @@ -0,0 +1,2 @@ +from .testcase import CycloneTestCase +from .client import Client \ No newline at end of file diff --git a/cyclone/testing/client.py b/cyclone/testing/client.py new file mode 100644 index 0000000000..a3b80bac2e --- /dev/null +++ b/cyclone/testing/client.py @@ -0,0 +1,101 @@ +# +# Copyright 2010 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 +from twisted.internet.task import deferLater +from twisted.internet import reactor + +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) diff --git a/cyclone/testing/testcase.py b/cyclone/testing/testcase.py new file mode 100644 index 0000000000..4d7f42faa2 --- /dev/null +++ b/cyclone/testing/testcase.py @@ -0,0 +1,33 @@ +# +# Copyright 2010 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. + """ + super(CycloneTestCase, self).__init__(*args, **kwargs) + self._app = app_builder() + self.client = self.client_impl(self._app) diff --git a/cyclone/tests/test_app.py b/cyclone/tests/test_app.py index ce2a297043..51be53c68d 100644 --- a/cyclone/tests/test_app.py +++ b/cyclone/tests/test_app.py @@ -1,3 +1,18 @@ +# +# Copyright 2010 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): diff --git a/cyclone/tests/test_testing.py b/cyclone/tests/test_testing.py new file mode 100644 index 0000000000..be15b30ab2 --- /dev/null +++ b/cyclone/tests/test_testing.py @@ -0,0 +1,58 @@ +from twisted.trial import unittest +from cyclone.testing import CycloneTestCase, Client +from cyclone.web import Application, RequestHandler, asynchronous +from twisted.internet import reactor +from twisted.internet.defer import inlineCallbacks + + +class TestHandler(RequestHandler): + def get(self): + self.write("Something") + + +class DeferredTestHandler(RequestHandler): + @asynchronous + def get(self): + self.write("Something...") + reactor.callLater(0.1, self.do_something) + + def do_something(self): + self.write("done!") + self.finish() + + +def mock_app_builder(): + return Application([ + (r'/testing/', TestHandler), + (r'/deferred_testing/', DeferredTestHandler) + ]) + + +class TestTestCase(unittest.TestCase): + def test_create(self): + case = CycloneTestCase(mock_app_builder) + self.assertTrue(case._app) + self.assertTrue(case.client) + + +class TestClient(unittest.TestCase): + def setUp(self): + self.app = mock_app_builder() + self.client = Client(self.app) + + def test_create_client(self): + app = mock_app_builder() + client = Client(app) + self.assertTrue(client.app) + + @inlineCallbacks + def test_get_request(self): + response = yield self.client.get("/testing/") + self.assertEqual(response.content, "Something") + self.assertTrue(len(response.headers) > 3) + + @inlineCallbacks + def test_get_deferred_request(self): + response = yield self.client.get("/deferred_testing/") + self.assertEqual(response.content, "Something...done!") + self.assertTrue(len(response.headers) > 3) From d6834a44eb8e25f9e90f36b9e0a0528380e7d445 Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Thu, 31 Jul 2014 10:15:21 +1000 Subject: [PATCH 03/14] Add copyright --- cyclone/tests/test_testing.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cyclone/tests/test_testing.py b/cyclone/tests/test_testing.py index be15b30ab2..20fb68436f 100644 --- a/cyclone/tests/test_testing.py +++ b/cyclone/tests/test_testing.py @@ -1,3 +1,18 @@ +# +# Copyright 2010 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.testing import CycloneTestCase, Client from cyclone.web import Application, RequestHandler, asynchronous From 63014037e61e4da2434c2fc6660c144b6eb637ff Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Thu, 31 Jul 2014 10:15:53 +1000 Subject: [PATCH 04/14] Remove old imports --- cyclone/testing/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cyclone/testing/client.py b/cyclone/testing/client.py index a3b80bac2e..0fee1b0baa 100644 --- a/cyclone/testing/client.py +++ b/cyclone/testing/client.py @@ -17,8 +17,7 @@ import urllib from twisted.test import proto_helpers from twisted.internet.defer import inlineCallbacks, returnValue -from twisted.internet.task import deferLater -from twisted.internet import reactor + class Client(object): def __init__(self, app): From 498e26e2ec522d3e45dd437a98451b72452c8ab1 Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Thu, 31 Jul 2014 12:33:08 +1000 Subject: [PATCH 05/14] Travis config --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..e20cf9c626 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: python +python: 2.7 + +install: "pip install -r requirements.txt" + +script: trial cyclone \ No newline at end of file From 25f401b0b8f7d2021620cae35ef721a744c252e1 Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Thu, 31 Jul 2014 12:44:04 +1000 Subject: [PATCH 06/14] Try coveralls support --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e20cf9c626..0e8f0a1b57 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: python python: 2.7 -install: "pip install -r requirements.txt" +install: + - pip install -r requirements.txt + - pip install coveralls -script: trial cyclone \ No newline at end of file +script: coverage run `which trial` cyclone \ No newline at end of file From f2b87b166ecb34219b8c59196fa4fdca939af043 Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Thu, 31 Jul 2014 12:45:23 +1000 Subject: [PATCH 07/14] Update .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0e8f0a1b57..7ee61d714f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,5 @@ install: - pip install -r requirements.txt - pip install coveralls -script: coverage run `which trial` cyclone \ No newline at end of file +script: coverage run `which trial` cyclone +after_success: coveralls From 9d3f23c5c05c0070e5be1cee033856814a6ad0ac Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Thu, 31 Jul 2014 12:51:32 +1000 Subject: [PATCH 08/14] Ignore twisted for coverage --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000..0a23cf8ba9 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[report] +omit = */twisted/* From 9f32b217ab38ed61b22846cf87ace11f2305230e Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Thu, 31 Jul 2014 12:53:02 +1000 Subject: [PATCH 09/14] Use include instead. --- .coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 0a23cf8ba9..bebe57bfa9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,2 @@ [report] -omit = */twisted/* +include = */cyclone/* \ No newline at end of file From b5976785e1da4065a265b7127750777c947f9ff3 Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Sat, 2 Aug 2014 20:51:07 +1000 Subject: [PATCH 10/14] Removed redundant requirements. --- requirements.txt | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 513cbd2c68..0000000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -twisted>=12.0 -mock \ No newline at end of file From 8a8b775cbae3b258248722d4519a87d489dbdc0e Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Sat, 2 Aug 2014 20:52:01 +1000 Subject: [PATCH 11/14] test requirements --- cyclone/tests/test_requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 cyclone/tests/test_requirements.txt diff --git a/cyclone/tests/test_requirements.txt b/cyclone/tests/test_requirements.txt new file mode 100644 index 0000000000..787c3f06e3 --- /dev/null +++ b/cyclone/tests/test_requirements.txt @@ -0,0 +1,2 @@ +mock +twisted>=12.0 \ No newline at end of file From 298db69ac01ce94d9e51b49d7754424bfa3189a7 Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Sat, 2 Aug 2014 20:53:17 +1000 Subject: [PATCH 12/14] Cleanup of some docstrings --- cyclone/testing/__init__.py | 15 +++++++++++++++ cyclone/testing/client.py | 2 +- cyclone/testing/testcase.py | 8 ++++++-- cyclone/tests/test_app.py | 2 +- cyclone/tests/test_httpclient.py | 2 +- cyclone/tests/test_testing.py | 2 +- 6 files changed, 25 insertions(+), 6 deletions(-) diff --git a/cyclone/testing/__init__.py b/cyclone/testing/__init__.py index 6bb21994dc..8da1538bad 100644 --- a/cyclone/testing/__init__.py +++ b/cyclone/testing/__init__.py @@ -1,2 +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 \ No newline at end of file diff --git a/cyclone/testing/client.py b/cyclone/testing/client.py index 0fee1b0baa..053014c98c 100644 --- a/cyclone/testing/client.py +++ b/cyclone/testing/client.py @@ -1,5 +1,5 @@ # -# Copyright 2010 David Novakovic +# 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 diff --git a/cyclone/testing/testcase.py b/cyclone/testing/testcase.py index 4d7f42faa2..53957499c7 100644 --- a/cyclone/testing/testcase.py +++ b/cyclone/testing/testcase.py @@ -1,5 +1,5 @@ # -# Copyright 2010 David Novakovic +# 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 @@ -24,9 +24,13 @@ 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 + 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() diff --git a/cyclone/tests/test_app.py b/cyclone/tests/test_app.py index 51be53c68d..d53d012f00 100644 --- a/cyclone/tests/test_app.py +++ b/cyclone/tests/test_app.py @@ -1,5 +1,5 @@ # -# Copyright 2010 David Novakovic +# 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 diff --git a/cyclone/tests/test_httpclient.py b/cyclone/tests/test_httpclient.py index 358f271c90..2bfd4d7043 100644 --- a/cyclone/tests/test_httpclient.py +++ b/cyclone/tests/test_httpclient.py @@ -1,5 +1,5 @@ # -# Copyright 2010 David Novakovic +# 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 diff --git a/cyclone/tests/test_testing.py b/cyclone/tests/test_testing.py index 20fb68436f..88e04c34aa 100644 --- a/cyclone/tests/test_testing.py +++ b/cyclone/tests/test_testing.py @@ -1,5 +1,5 @@ # -# Copyright 2010 David Novakovic +# 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 From 7d858bbb70986abe898a6a952e99e4b9598ba4ae Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Sat, 2 Aug 2014 21:21:33 +1000 Subject: [PATCH 13/14] Added unit tests for JsonRPC --- cyclone/tests/test_httpclient.py | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/cyclone/tests/test_httpclient.py b/cyclone/tests/test_httpclient.py index 2bfd4d7043..024adb7db5 100644 --- a/cyclone/tests/test_httpclient.py +++ b/cyclone/tests/test_httpclient.py @@ -15,9 +15,13 @@ 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): @@ -121,3 +125,73 @@ def test_fetch_redirect(self): 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.") From fe744760c1d943e8af5615e00277cec1cf7f3c2d Mon Sep 17 00:00:00 2001 From: David Novakovic Date: Sat, 2 Aug 2014 21:25:19 +1000 Subject: [PATCH 14/14] Use test requirements --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7ee61d714f..fc1cdc3c41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: python python: 2.7 install: - - pip install -r requirements.txt + - pip install -r cyclone/tests/test_requirements.txt - pip install coveralls script: coverage run `which trial` cyclone