From edfedae3e7e502b06fce659710eb753fb13f90fa Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 17:19:46 -0800 Subject: [PATCH 01/11] Add initial functional tests --- tornado_json/test/func_test.py | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tornado_json/test/func_test.py diff --git a/tornado_json/test/func_test.py b/tornado_json/test/func_test.py new file mode 100644 index 0000000..1a4b89c --- /dev/null +++ b/tornado_json/test/func_test.py @@ -0,0 +1,62 @@ +import sys +import json +from tornado.testing import AsyncHTTPTestCase + +try: + sys.path.append('.') + from tornado_json import routes + from tornado_json import utils + from tornado_json import jsend + from tornado_json import application + sys.path.append('demos/helloworld') + import helloworld +except ImportError as e: + print("Please run `py.test` from the root project directory") + exit(1) + + +def jd(obj): + return json.dumps(obj) + + +def jl(s): + return json.loads(s.decode("utf-8")) + + +class APIFunctionalTest(AsyncHTTPTestCase): + + def get_app(self): + return application.Application( + routes=routes.get_routes(helloworld), + settings={}, + ) + + def test_synchronous_handler(self): + r = self.fetch( + "/api/helloworld" + ) + self.assertEqual(r.code, 200) + self.assertEqual( + jl(r.body)["data"], + "Hello world!" + ) + + def test_asynchronous_handler(self): + r = self.fetch( + "/api/asynchelloworld" + ) + self.assertEqual(r.code, 200) + self.assertEqual( + jl(r.body)["data"], + "Hello (asynchronous) world!" + ) + + def test_url_pattern_route(self): + r = self.fetch( + "/api/greeting/Martian" + ) + self.assertEqual(r.code, 200) + self.assertEqual( + jl(r.body)["data"], + "Greetings, Martian!" + ) \ No newline at end of file From e36dd04aa33e2fa870c5626cdc0011c67e6494e3 Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 18:22:42 -0800 Subject: [PATCH 02/11] Add POST request example and test --- demos/helloworld/helloworld/api.py | 97 ++++++++++++++++++-------- maintenance.md | 1 + tornado_json/test/func_test.py | 18 ++++- tornado_json/test/test_tornado_json.py | 2 + 4 files changed, 89 insertions(+), 29 deletions(-) diff --git a/demos/helloworld/helloworld/api.py b/demos/helloworld/helloworld/api.py index 908d63e..153da35 100755 --- a/demos/helloworld/helloworld/api.py +++ b/demos/helloworld/helloworld/api.py @@ -6,16 +6,15 @@ class HelloWorldHandler(APIHandler): - apid = { - "get": { - "input_schema": None, - "output_schema": { - "type": "string", - }, - "output_example": "Hello world!", - "input_example": None, - "doc": "Shouts hello to the world!", + apid = {} + apid["get"] = { + "input_schema": None, + "output_schema": { + "type": "string", }, + "output_example": "Hello world!", + "input_example": None, + "doc": "Shouts hello to the world!", } # Decorate any HTTP methods with the `io_schema` decorator @@ -30,16 +29,15 @@ def get(self): class AsyncHelloWorld(APIHandler): - apid = { - "get": { - "input_schema": None, - "output_schema": { - "type": "string", - }, - "output_example": "Hello (asynchronous) world!", - "input_example": None, - "doc": "Shouts hello to the world (asynchronously)!", + apid = {} + apid["get"] = { + "input_schema": None, + "output_schema": { + "type": "string", }, + "output_example": "Hello (asynchronous) world!", + "input_example": None, + "doc": "Shouts hello to the world (asynchronously)!", } def hello(self, callback=None): @@ -58,24 +56,67 @@ def get(self): # In Python 3.3, using `raise Return(value)` is no longer # necessary and can be replaced with simply `return value`. # For details, see: - # http://www.tornadoweb.org/en/branch3.2/gen.html#tornado.gen.Return + # http://www.tornadoweb.org/en/branch3.2/gen.html#tornado.gen.Return # return res # Python 3.3 raise gen.Return(res) # Python 2.7 -class Greeting(APIHandler): +class PostIt(APIHandler): - apid = { - "get": { - "input_schema": None, - "output_schema": { - "type": "string", + apid = {} + apid["post"] = { + "input_schema": { + "type": "object", + "properties": { + "title": {"type": "string"}, + "body": {"type": "string"}, + "index": {"type": "number"}, }, - "output_example": "Greetings, Greg!", - "input_example": None, - "doc": "Greets you.", }, + "input_example": { + "title": "Very Important Post-It Note", + "body": "Equally important message", + "index": 0 + }, + "output_schema": { + "type": "object", + "properties": { + "message": {"type": "string"} + } + }, + "output_example": { + "message": "Very Important Post-It Note was posted." + }, + "doc": """ +POST the required parameters to post a Post-It note + +* `title`: Title of the note +* `body`: Body of the note +* `index`: An easy index with which to find the note +""" + } + + @io_schema + def post(self): + # io_schema will JSON-decode `self.request.body` for us + # and set self.body as the result, so we can use that here + return { + "message": "{} was posted.".format(self.body["title"]) + } + + +class Greeting(APIHandler): + + apid = {} + apid["get"] = { + "input_schema": None, + "output_schema": { + "type": "string", + }, + "output_example": "Greetings, Greg!", + "input_example": None, + "doc": "Greets you.", } # When you include extra arguments in the signature of an HTTP diff --git a/maintenance.md b/maintenance.md index d0e4e8e..6e1712d 100644 --- a/maintenance.md +++ b/maintenance.md @@ -20,3 +20,4 @@ * Run tests from root project directory ```$ py.test --cov="tornado_json" --cov-report=term --cov-report=html``` + ```$ nosetests --with-cov --cov-report term-missing --cov tornado_json tornado_json/test/``` diff --git a/tornado_json/test/func_test.py b/tornado_json/test/func_test.py index 1a4b89c..fe24120 100644 --- a/tornado_json/test/func_test.py +++ b/tornado_json/test/func_test.py @@ -51,6 +51,22 @@ def test_asynchronous_handler(self): "Hello (asynchronous) world!" ) + def test_post_request(self): + r = self.fetch( + "/api/postit", + method="POST", + body=jd({ + "title": "Very Important Post-It Note", + "body": "Equally important message", + "index": 0 + }) + ) + self.assertEqual(r.code, 200) + self.assertEqual( + jl(r.body)["data"]["message"], + "Very Important Post-It Note was posted." + ) + def test_url_pattern_route(self): r = self.fetch( "/api/greeting/Martian" @@ -59,4 +75,4 @@ def test_url_pattern_route(self): self.assertEqual( jl(r.body)["data"], "Greetings, Martian!" - ) \ No newline at end of file + ) diff --git a/tornado_json/test/test_tornado_json.py b/tornado_json/test/test_tornado_json.py index 5ee7edb..026288f 100644 --- a/tornado_json/test/test_tornado_json.py +++ b/tornado_json/test/test_tornado_json.py @@ -49,6 +49,7 @@ def test_get_routes(self): helloworld)) == sorted([ ("/api/helloworld", helloworld.api.HelloWorldHandler), ("/api/asynchelloworld", helloworld.api.AsyncHelloWorld), + ("/api/postit", helloworld.api.PostIt), ("/api/greeting/(?P[a-zA-Z0-9_]+)/?$", helloworld.api.Greeting) ]) @@ -64,6 +65,7 @@ def test_get_module_routes(self): 'helloworld.api')) == sorted([ ("/api/helloworld", helloworld.api.HelloWorldHandler), ("/api/asynchelloworld", helloworld.api.AsyncHelloWorld), + ("/api/postit", helloworld.api.PostIt), ("/api/greeting/(?P[a-zA-Z0-9_]+)/?$", helloworld.api.Greeting) ]) From 197d452c01f6230c8c6033bdb4be6dd91c7743d9 Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 18:37:20 -0800 Subject: [PATCH 03/11] Decode UTF-8 on request body --- tornado_json/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tornado_json/utils.py b/tornado_json/utils.py index e7c270b..fca2919 100644 --- a/tornado_json/utils.py +++ b/tornado_json/utils.py @@ -62,7 +62,11 @@ def _wrapper(self, *args, **kwargs): if method_name not in ["get", "delete"]: # If input is not valid JSON, fail try: - input_ = json.loads(self.request.body) + # TODO: Assuming UTF-8 encoding for all requests, + # find a nice way of determining this from charset + # in headers if provided + encoding = "UTF-8" + input_ = json.loads(self.request.body.decode(encoding)) except ValueError as e: logging.error(str(e)) self.fail(str(e)) From 6a198158b64dcb69d64b2816e44a1ddb6bf619d1 Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 18:53:21 -0800 Subject: [PATCH 04/11] Add FreeWilledHandler --- .travis.yml | 1 + demos/helloworld/helloworld/api.py | 12 ++++++++++++ tornado_json/test/func_test.py | 3 +++ tornado_json/test/test_tornado_json.py | 6 ++++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b979cb8..daa3924 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ install: - "pip install pytest-cov" - "pip install coverage" - "pip install coveralls" + - "pip install mock" script: coverage run --source=tornado_json setup.py test after_success: diff --git a/demos/helloworld/helloworld/api.py b/demos/helloworld/helloworld/api.py index 153da35..d1b8797 100755 --- a/demos/helloworld/helloworld/api.py +++ b/demos/helloworld/helloworld/api.py @@ -127,3 +127,15 @@ class Greeting(APIHandler): @io_schema def get(self, name): return "Greetings, {}!".format(name) + + +class FreeWilledHandler(APIHandler): + + # And of course, you aren't forced to use schema validation; + # if you want your handlers to do something more custom, + # they definitely can. + def get(self): + self.success("I don't need no stinkin' schema validation.") + # If you're feeling really bold, you could even skip JSend + # altogether and do the following EVIL thing: + # self.write("I'm writing back a string that isn't JSON! Take that!") diff --git a/tornado_json/test/func_test.py b/tornado_json/test/func_test.py index fe24120..c9126c1 100644 --- a/tornado_json/test/func_test.py +++ b/tornado_json/test/func_test.py @@ -1,6 +1,7 @@ import sys import json from tornado.testing import AsyncHTTPTestCase +from mock import Mock try: sys.path.append('.') @@ -8,6 +9,7 @@ from tornado_json import utils from tornado_json import jsend from tornado_json import application + from tornado_json import requesthandlers sys.path.append('demos/helloworld') import helloworld except ImportError as e: @@ -29,6 +31,7 @@ def get_app(self): return application.Application( routes=routes.get_routes(helloworld), settings={}, + db_conn=Mock() ) def test_synchronous_handler(self): diff --git a/tornado_json/test/test_tornado_json.py b/tornado_json/test/test_tornado_json.py index 026288f..b6d6145 100644 --- a/tornado_json/test/test_tornado_json.py +++ b/tornado_json/test/test_tornado_json.py @@ -51,7 +51,8 @@ def test_get_routes(self): ("/api/asynchelloworld", helloworld.api.AsyncHelloWorld), ("/api/postit", helloworld.api.PostIt), ("/api/greeting/(?P[a-zA-Z0-9_]+)/?$", - helloworld.api.Greeting) + helloworld.api.Greeting), + ("/api/freewilled", helloworld.api.FreeWilledHandler) ]) def test_gen_submodule_names(self): @@ -67,7 +68,8 @@ def test_get_module_routes(self): ("/api/asynchelloworld", helloworld.api.AsyncHelloWorld), ("/api/postit", helloworld.api.PostIt), ("/api/greeting/(?P[a-zA-Z0-9_]+)/?$", - helloworld.api.Greeting) + helloworld.api.Greeting), + ("/api/freewilled", helloworld.api.FreeWilledHandler) ]) From 2bd0a7191063ff30a9986d09b246edf3a4e93566 Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 19:04:22 -0800 Subject: [PATCH 05/11] Add ExplodingHandler to test write_error --- demos/helloworld/API_Documentation.md | 64 +++++++++++++++++++++++++++ tornado_json/test/func_test.py | 60 ++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/demos/helloworld/API_Documentation.md b/demos/helloworld/API_Documentation.md index e3aef93..98aed90 100644 --- a/demos/helloworld/API_Documentation.md +++ b/demos/helloworld/API_Documentation.md @@ -85,3 +85,67 @@ null Shouts hello to the world! + + + + +# `/api/postit` + + Content-Type: application/json + +## POST +### Input Schema +```json +{ + "type": "object", + "properties": { + "body": { + "type": "string" + }, + "index": { + "type": "number" + }, + "title": { + "type": "string" + } + } +} +``` + +### Input Example +```json +{ + "body": "Equally important message", + "index": 0, + "title": "Very Important Post-It Note" +} +``` + +### Output Schema +```json +{ + "type": "object", + "properties": { + "message": { + "type": "string" + } + } +} +``` + +### Output Example +```json +{ + "message": "Very Important Post-It Note was posted." +} +``` + + + +POST the required parameters to post a Post-It note + +* `title`: Title of the note +* `body`: Body of the note +* `index`: An easy index with which to find the note + + diff --git a/tornado_json/test/func_test.py b/tornado_json/test/func_test.py index c9126c1..4214824 100644 --- a/tornado_json/test/func_test.py +++ b/tornado_json/test/func_test.py @@ -25,15 +25,73 @@ def jl(s): return json.loads(s.decode("utf-8")) +class ExplodingHandler(requesthandlers.APIHandler): + + apid = { + "get": { + "input_schema": "This doesn't matter because GET request", + "output_schema": { + "type": "number", + }, + "doc": """ +This handler is used for testing purposes and is explosive. +""" + }, + "post": { + "input_schema": { + "type": "number", + }, + "output_schema": { + "type": "number", + }, + "doc": """ +This handler is used for testing purposes and is explosive. +""" + }, + } + + @utils.io_schema + def get(self): + return "I am not the handler you are looking for." + + @utils.io_schema + def post(self): + return "Fission mailed." + + class APIFunctionalTest(AsyncHTTPTestCase): def get_app(self): + rts = routes.get_routes(helloworld) + rts += [("/api/explodinghandler", ExplodingHandler)] return application.Application( - routes=routes.get_routes(helloworld), + routes=rts, settings={}, db_conn=Mock() ) + def test_write_error(self): + # Test malformed output + r = self.fetch( + "/api/explodinghandler" + ) + self.assertEqual(r.code, 500) + self.assertEqual( + jl(r.body)["status"], + "error" + ) + # Test malformed input + r = self.fetch( + "/api/explodinghandler", + method="POST", + body=jd("This is going to end badly.") + ) + self.assertEqual(r.code, 400) + self.assertEqual( + jl(r.body)["status"], + "fail" + ) + def test_synchronous_handler(self): r = self.fetch( "/api/helloworld" From 1cd01202b9adc209e0c9e522c8c74ba707ede8ca Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 19:06:52 -0800 Subject: [PATCH 06/11] Remove useless check for exc_info since it should always exist --- tornado_json/requesthandlers.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tornado_json/requesthandlers.py b/tornado_json/requesthandlers.py index 62810c0..d8ec0df 100644 --- a/tornado_json/requesthandlers.py +++ b/tornado_json/requesthandlers.py @@ -61,14 +61,6 @@ def write_error(self, status_code, **kwargs): :param status_code: HTTP status code """ self.clear() - - # If exc_info is not in kwargs, something is very fubar - if not "exc_info" in list(kwargs.keys()): - logging.error("exc_info not provided") - self.set_status(500) - self.error(message="Internal Server Error", code=500) - self.finish() - self.set_status(status_code) # Any APIError exceptions raised will result in a JSend fail written From 6e6f5ea4e2afa6ae75df76fd6a6d824c92308ebe Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 19:19:49 -0800 Subject: [PATCH 07/11] Raise ValidationError on malformed input --- tornado_json/requesthandlers.py | 16 ++++++++++------ tornado_json/test/func_test.py | 2 +- tornado_json/utils.py | 12 +++++++----- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/tornado_json/requesthandlers.py b/tornado_json/requesthandlers.py index d8ec0df..c7d938f 100644 --- a/tornado_json/requesthandlers.py +++ b/tornado_json/requesthandlers.py @@ -75,10 +75,14 @@ def write_error(self, status_code, **kwargs): # ValidationError is always due to a malformed request if isinstance(exception, ValidationError): self.set_status(400) - self.fail(exception.log_message if - hasattr(exception, "log_message") else str(exception)) + self.fail( + exception.log_message if + hasattr(exception, "log_message") else str(exception) + ) else: - self.error(message=self._reason, - data=exception.log_message if self.settings.get( - "debug") else None, - code=status_code) + self.error( + message=self._reason, + data=exception.log_message if self.settings.get("debug") + else None, + code=status_code + ) diff --git a/tornado_json/test/func_test.py b/tornado_json/test/func_test.py index 4214824..4c390cd 100644 --- a/tornado_json/test/func_test.py +++ b/tornado_json/test/func_test.py @@ -84,7 +84,7 @@ def test_write_error(self): r = self.fetch( "/api/explodinghandler", method="POST", - body=jd("This is going to end badly.") + body='"Yup", "this is going to end badly."]' ) self.assertEqual(r.code, 400) self.assertEqual( diff --git a/tornado_json/utils.py b/tornado_json/utils.py index fca2919..351a270 100644 --- a/tornado_json/utils.py +++ b/tornado_json/utils.py @@ -68,13 +68,15 @@ def _wrapper(self, *args, **kwargs): encoding = "UTF-8" input_ = json.loads(self.request.body.decode(encoding)) except ValueError as e: - logging.error(str(e)) - self.fail(str(e)) - return + raise ValidationError( + "Input is malformed; could not decode JSON object." + ) # Validate the received input - validate(input_, type(self) - .apid[method_name]["input_schema"]) + validate( + input_, + type(self).apid[method_name]["input_schema"] + ) else: input_ = None From f0564ddd2d27db9c16be2e3500e0f097d2c9652e Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 19:26:55 -0800 Subject: [PATCH 08/11] Move test folder to root --- maintenance.md | 2 +- {tornado_json/test => tests}/__init__.py | 0 {tornado_json/test => tests}/func_test.py | 0 {tornado_json/test => tests}/test_tornado_json.py | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename {tornado_json/test => tests}/__init__.py (100%) rename {tornado_json/test => tests}/func_test.py (100%) rename {tornado_json/test => tests}/test_tornado_json.py (100%) diff --git a/maintenance.md b/maintenance.md index 6e1712d..fdac771 100644 --- a/maintenance.md +++ b/maintenance.md @@ -20,4 +20,4 @@ * Run tests from root project directory ```$ py.test --cov="tornado_json" --cov-report=term --cov-report=html``` - ```$ nosetests --with-cov --cov-report term-missing --cov tornado_json tornado_json/test/``` + ```$ nosetests --with-cov --cov-report term-missing --cov tornado_json tests/``` diff --git a/tornado_json/test/__init__.py b/tests/__init__.py similarity index 100% rename from tornado_json/test/__init__.py rename to tests/__init__.py diff --git a/tornado_json/test/func_test.py b/tests/func_test.py similarity index 100% rename from tornado_json/test/func_test.py rename to tests/func_test.py diff --git a/tornado_json/test/test_tornado_json.py b/tests/test_tornado_json.py similarity index 100% rename from tornado_json/test/test_tornado_json.py rename to tests/test_tornado_json.py From 195f3652d801919945812012c0f4330567a36ad2 Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 19:44:13 -0800 Subject: [PATCH 09/11] Change URL pattern gen test route to have two args --- demos/helloworld/API_Documentation.md | 4 ++-- demos/helloworld/helloworld/api.py | 10 +++++----- tests/func_test.py | 4 ++-- tests/test_tornado_json.py | 7 ++++--- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/demos/helloworld/API_Documentation.md b/demos/helloworld/API_Documentation.md index 98aed90..5f1f563 100644 --- a/demos/helloworld/API_Documentation.md +++ b/demos/helloworld/API_Documentation.md @@ -31,7 +31,7 @@ Shouts hello to the world (asynchronously)! -# `/api/greeting/(?P[a-zA-Z0-9_]+)/?$` +# `/api/greeting/(?P[a-zA-Z0-9_]+)/(?P[a-zA-Z0-9_]+)/?$` Content-Type: application/json @@ -50,7 +50,7 @@ null ### Output Example ```json -"Greetings, Greg!" +"Greetings, Named Person!" ``` diff --git a/demos/helloworld/helloworld/api.py b/demos/helloworld/helloworld/api.py index d1b8797..cd0d639 100755 --- a/demos/helloworld/helloworld/api.py +++ b/demos/helloworld/helloworld/api.py @@ -114,19 +114,19 @@ class Greeting(APIHandler): "output_schema": { "type": "string", }, - "output_example": "Greetings, Greg!", + "output_example": "Greetings, Named Person!", "input_example": None, "doc": "Greets you.", } # When you include extra arguments in the signature of an HTTP # method, Tornado-JSON will generate a route that matches the extra - # arguments; here, you can GET /api/greeting/Greg and you will - # get a response back that says, "Greetings, Greg!" + # arguments; here, you can GET /api/greeting/John/Smith and you will + # get a response back that says, "Greetings, John Smith!" # You can match the regex equivalent of `\w+`. @io_schema - def get(self, name): - return "Greetings, {}!".format(name) + def get(self, fname, lname): + return "Greetings, {} {}!".format(fname, lname) class FreeWilledHandler(APIHandler): diff --git a/tests/func_test.py b/tests/func_test.py index 4c390cd..500b363 100644 --- a/tests/func_test.py +++ b/tests/func_test.py @@ -130,10 +130,10 @@ def test_post_request(self): def test_url_pattern_route(self): r = self.fetch( - "/api/greeting/Martian" + "/api/greeting/John/Smith" ) self.assertEqual(r.code, 200) self.assertEqual( jl(r.body)["data"], - "Greetings, Martian!" + "Greetings, John Smith!" ) diff --git a/tests/test_tornado_json.py b/tests/test_tornado_json.py index b6d6145..d953ad9 100644 --- a/tests/test_tornado_json.py +++ b/tests/test_tornado_json.py @@ -50,7 +50,7 @@ def test_get_routes(self): ("/api/helloworld", helloworld.api.HelloWorldHandler), ("/api/asynchelloworld", helloworld.api.AsyncHelloWorld), ("/api/postit", helloworld.api.PostIt), - ("/api/greeting/(?P[a-zA-Z0-9_]+)/?$", + ("/api/greeting/(?P[a-zA-Z0-9_]+)/(?P[a-zA-Z0-9_]+)/?$", helloworld.api.Greeting), ("/api/freewilled", helloworld.api.FreeWilledHandler) ]) @@ -67,7 +67,7 @@ def test_get_module_routes(self): ("/api/helloworld", helloworld.api.HelloWorldHandler), ("/api/asynchelloworld", helloworld.api.AsyncHelloWorld), ("/api/postit", helloworld.api.PostIt), - ("/api/greeting/(?P[a-zA-Z0-9_]+)/?$", + ("/api/greeting/(?P[a-zA-Z0-9_]+)/(?P[a-zA-Z0-9_]+)/?$", helloworld.api.Greeting), ("/api/freewilled", helloworld.api.FreeWilledHandler) ]) @@ -149,9 +149,10 @@ def post(self): assert self.body == {"I am a": "JSON object"} return "Mail received." - # TODO: Test io_schema functionally instead; pytest.raises does + # DONE: Test io_schema functionally instead; pytest.raises does # not seem to be catching errors being thrown after change # to async compatible code. + # The following test left here as antiquity. # def test_io_schema(self): # """Tests the utils.io_schema decorator""" # th = self.TerribleHandler() From d18fe5d7dce3f2199507ec9f7cbfd4c2307843c8 Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 20:34:59 -0800 Subject: [PATCH 10/11] Test for db_conn; fix write_error bug; 100% coveragegs --- tests/func_test.py | 101 +++++++++++++++++++++++--------- tornado_json/requesthandlers.py | 11 ++-- 2 files changed, 80 insertions(+), 32 deletions(-) diff --git a/tests/func_test.py b/tests/func_test.py index 500b363..2f59769 100644 --- a/tests/func_test.py +++ b/tests/func_test.py @@ -1,13 +1,11 @@ import sys import json from tornado.testing import AsyncHTTPTestCase -from mock import Mock try: sys.path.append('.') from tornado_json import routes from tornado_json import utils - from tornado_json import jsend from tornado_json import application from tornado_json import requesthandlers sys.path.append('demos/helloworld') @@ -25,6 +23,26 @@ def jl(s): return json.loads(s.decode("utf-8")) +class DummyView(requesthandlers.ViewHandler): + + """Dummy ViewHandler for coverage""" + + def delete(self): + # Reference db_conn to test for AttributeError + self.db_conn + + +class DBTestHandler(requesthandlers.APIHandler): + + """APIHandler for testing db_conn""" + + def get(self): + # Set application.db_conn to test if db_conn BaseHandler + # property works + self.application.db_conn = {"data": "Nothing to see here."} + self.success(self.db_conn.get("data")) + + class ExplodingHandler(requesthandlers.APIHandler): apid = { @@ -63,33 +81,15 @@ class APIFunctionalTest(AsyncHTTPTestCase): def get_app(self): rts = routes.get_routes(helloworld) - rts += [("/api/explodinghandler", ExplodingHandler)] + rts += [ + ("/api/explodinghandler", ExplodingHandler), + ("/views/someview", DummyView), + ("/api/dbtest", DBTestHandler) + ] return application.Application( routes=rts, - settings={}, - db_conn=Mock() - ) - - def test_write_error(self): - # Test malformed output - r = self.fetch( - "/api/explodinghandler" - ) - self.assertEqual(r.code, 500) - self.assertEqual( - jl(r.body)["status"], - "error" - ) - # Test malformed input - r = self.fetch( - "/api/explodinghandler", - method="POST", - body='"Yup", "this is going to end badly."]' - ) - self.assertEqual(r.code, 400) - self.assertEqual( - jl(r.body)["status"], - "fail" + settings={"debug": True}, + db_conn=None ) def test_synchronous_handler(self): @@ -137,3 +137,50 @@ def test_url_pattern_route(self): jl(r.body)["data"], "Greetings, John Smith!" ) + + def test_write_error(self): + # Test malformed output + r = self.fetch( + "/api/explodinghandler" + ) + self.assertEqual(r.code, 500) + self.assertEqual( + jl(r.body)["status"], + "error" + ) + # Test malformed input + r = self.fetch( + "/api/explodinghandler", + method="POST", + body='"Yup", "this is going to end badly."]' + ) + self.assertEqual(r.code, 400) + self.assertEqual( + jl(r.body)["status"], + "fail" + ) + + def test_view_db_conn(self): + r = self.fetch( + "/views/someview", + method="DELETE" + ) + self.assertEqual(r.code, 500) + self.assertTrue( + "No database connection was provided." in r.body.decode("UTF-8") + ) + + def test_db_conn(self): + r = self.fetch( + "/api/dbtest", + method="GET" + ) + self.assertEqual(r.code, 200) + print(r.body) + self.assertEqual( + jl(r.body)["status"], + "success" + ) + self.assertTrue( + "Nothing to see here." in jl(r.body)["data"] + ) diff --git a/tornado_json/requesthandlers.py b/tornado_json/requesthandlers.py index c7d938f..7cd585b 100644 --- a/tornado_json/requesthandlers.py +++ b/tornado_json/requesthandlers.py @@ -60,6 +60,10 @@ def write_error(self, status_code, **kwargs): :type status_code: int :param status_code: HTTP status code """ + def get_exc_message(exception): + return exception.log_message if \ + hasattr(exception, "log_message") else str(exception) + self.clear() self.set_status(status_code) @@ -75,14 +79,11 @@ def write_error(self, status_code, **kwargs): # ValidationError is always due to a malformed request if isinstance(exception, ValidationError): self.set_status(400) - self.fail( - exception.log_message if - hasattr(exception, "log_message") else str(exception) - ) + self.fail(get_exc_message(exception)) else: self.error( message=self._reason, - data=exception.log_message if self.settings.get("debug") + data=get_exc_message(exception) if self.settings.get("debug") else None, code=status_code ) From 19a218574f2fa6c28676e25c6ee292978a95d660 Mon Sep 17 00:00:00 2001 From: hfaran Date: Sun, 16 Feb 2014 20:46:27 -0800 Subject: [PATCH 11/11] Bump version v0.14 --- tornado_json/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tornado_json/__init__.py b/tornado_json/__init__.py index 61acf08..cdaba45 100644 --- a/tornado_json/__init__.py +++ b/tornado_json/__init__.py @@ -5,4 +5,4 @@ # Alternatively, just put the version in a text file or something to avoid # this. -__version__ = '0.13' +__version__ = '0.14'