From 67ea462eadcc02ca2f8244062c786bd98871e9e8 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 17 Aug 2010 23:46:16 -0700 Subject: Added unittests for wsgi and api. --- nova/api/__init__.py | 5 +- nova/api/test.py | 70 +++++++++++++++++++++++++++ nova/wsgi.py | 17 ++++--- nova/wsgi_test.py | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 215 insertions(+), 10 deletions(-) create mode 100644 nova/api/test.py create mode 100644 nova/wsgi_test.py (limited to 'nova') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index a6bb93348..b9b9e3988 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -32,7 +32,6 @@ class API(wsgi.Router): def __init__(self): mapper = routes.Mapper() - mapper.connect(None, "/v1.0/{path_info:.*}", - controller=rackspace.API()) - mapper.connect(None, "/ec2/{path_info:.*}", controller=ec2.API()) + mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API()) + mapper.connect("/ec2/{path_info:.*}", controller=ec2.API()) super(API, self).__init__(mapper) diff --git a/nova/api/test.py b/nova/api/test.py new file mode 100644 index 000000000..09f79c02e --- /dev/null +++ b/nova/api/test.py @@ -0,0 +1,70 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# 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. + +""" +Test for the root WSGI middleware for all API controllers. +""" + +import unittest + +import stubout + +from nova import api +from nova import wsgi_test + + +class Test(unittest.TestCase): + + def setUp(self): # pylint: disable-msg=C0103 + self.called = False + self.stubs = stubout.StubOutForTesting() + + def tearDown(self): # pylint: disable-msg=C0103 + self.stubs.UnsetAll() + + def test_rackspace(self): + self.stubs.Set(api.rackspace, 'API', get_api_stub(self)) + api.API()(wsgi_test.get_environ({'PATH_INFO': '/v1.0/cloud'}), + wsgi_test.start_response) + self.assertTrue(self.called) + + def test_ec2(self): + self.stubs.Set(api.ec2, 'API', get_api_stub(self)) + api.API()(wsgi_test.get_environ({'PATH_INFO': '/ec2/cloud'}), + wsgi_test.start_response) + self.assertTrue(self.called) + + def test_not_found(self): + self.stubs.Set(api.ec2, 'API', get_api_stub(self)) + self.stubs.Set(api.rackspace, 'API', get_api_stub(self)) + api.API()(wsgi_test.get_environ({'PATH_INFO': '/'}), + wsgi_test.start_response) + self.assertFalse(self.called) + + +def get_api_stub(test_object): + """Get a stub class that verifies next part of the request.""" + + class APIStub(object): + """Class to verify request and mark it was called.""" + test = test_object + + def __call__(self, environ, start_response): + self.test.assertEqual(environ['PATH_INFO'], '/cloud') + self.test.called = True + + return APIStub diff --git a/nova/wsgi.py b/nova/wsgi.py index a0a175dc7..baf6cccd9 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -83,7 +83,7 @@ class Application(object): raise NotImplementedError("You must implement __call__") -class Middleware(Application): # pylint: disable=W0223 +class Middleware(Application): """ Base WSGI middleware wrapper. These classes require an application to be initialized that will be called next. By default the middleware will @@ -91,11 +91,11 @@ class Middleware(Application): # pylint: disable=W0223 behavior. """ - def __init__(self, application): # pylint: disable=W0231 + def __init__(self, application): # pylint: disable-msg=W0231 self.application = application @webob.dec.wsgify - def __call__(self, req): + def __call__(self, req): # pylint: disable-msg=W0221 """Override to implement middleware behavior.""" return self.application @@ -113,7 +113,7 @@ class Debug(Middleware): resp = req.get_response(self.application) print ("*" * 40) + " RESPONSE HEADERS" - for (key, value) in resp.headers: + for (key, value) in resp.headers.iteritems(): print key, "=", value print @@ -127,7 +127,7 @@ class Debug(Middleware): Iterator that prints the contents of a wrapper string iterator when iterated. """ - print ("*" * 40) + "BODY" + print ("*" * 40) + " BODY" for part in app_iter: sys.stdout.write(part) sys.stdout.flush() @@ -176,8 +176,9 @@ class Router(object): """ return self._router + @staticmethod @webob.dec.wsgify - def _dispatch(self, req): + def _dispatch(req): """ Called by self._router after matching the incoming request to a route and putting the information into req.environ. Either returns 404 @@ -197,6 +198,7 @@ class Controller(object): must, in addition to their normal parameters, accept a 'req' argument which is the incoming webob.Request. """ + @webob.dec.wsgify def __call__(self, req): """ @@ -249,6 +251,7 @@ class Serializer(object): return repr(data) def _to_xml_node(self, doc, metadata, nodename, data): + """Recursive method to convert data members to XML nodes.""" result = doc.createElement(nodename) if type(data) is list: singular = metadata.get('plurals', {}).get(nodename, None) @@ -262,7 +265,7 @@ class Serializer(object): result.appendChild(node) elif type(data) is dict: attrs = metadata.get('attributes', {}).get(nodename, {}) - for k,v in data.items(): + for k, v in data.items(): if k in attrs: result.setAttribute(k, str(v)) else: diff --git a/nova/wsgi_test.py b/nova/wsgi_test.py new file mode 100644 index 000000000..02bf067d6 --- /dev/null +++ b/nova/wsgi_test.py @@ -0,0 +1,133 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# 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. + +""" +Test WSGI basics and provide some helper functions for other WSGI tests. +""" + +import unittest + +import routes + +from nova import wsgi + + +class Test(unittest.TestCase): + + def setUp(self): # pylint: disable-msg=C0103 + self.called = False + + def test_debug(self): + + class Application(wsgi.Application): + """Dummy application to test debug.""" + test = self + + def __call__(self, environ, test_start_response): + test_start_response("200", [("X-Test", "checking")]) + self.test.called = True + return ['Test response'] + + app = wsgi.Debug(Application())(get_environ(), start_response) + self.assertTrue(self.called) + for _ in app: + pass + + def test_router(self): + + class Application(wsgi.Application): + """Test application to call from router.""" + test = self + + def __call__(self, environ, test_start_response): + test_start_response("200", []) + self.test.called = True + return [] + + class Router(wsgi.Router): + """Test router.""" + + def __init__(self): + mapper = routes.Mapper() + mapper.connect("/test", controller=Application()) + super(Router, self).__init__(mapper) + + Router()(get_environ({'PATH_INFO': '/test'}), start_response) + self.assertTrue(self.called) + self.called = False + Router()(get_environ({'PATH_INFO': '/bad'}), start_response) + self.assertFalse(self.called) + + def test_controller(self): + + class Controller(wsgi.Controller): + """Test controller to call from router.""" + test = self + + def show(self, **kwargs): + """Mark that this has been called.""" + self.test.called = True + self.test.assertEqual(kwargs['id'], '123') + return "Test" + + class Router(wsgi.Router): + """Test router.""" + + def __init__(self): + mapper = routes.Mapper() + mapper.resource("test", "tests", controller=Controller()) + super(Router, self).__init__(mapper) + + Router()(get_environ({'PATH_INFO': '/tests/123'}), start_response) + self.assertTrue(self.called) + self.called = False + Router()(get_environ({'PATH_INFO': '/test/123'}), start_response) + self.assertFalse(self.called) + + def test_serializer(self): + # TODO(eday): Placeholder for serializer testing. + pass + + +def get_environ(overwrite={}): # pylint: disable-msg=W0102 + """Get a WSGI environment, overwriting any entries given.""" + environ = {'SERVER_PROTOCOL': 'HTTP/1.1', + 'GATEWAY_INTERFACE': 'CGI/1.1', + 'wsgi.version': (1, 0), + 'SERVER_PORT': '443', + 'SERVER_NAME': '127.0.0.1', + 'REMOTE_ADDR': '127.0.0.1', + 'wsgi.run_once': False, + 'wsgi.errors': None, + 'wsgi.multiprocess': False, + 'SCRIPT_NAME': '', + 'wsgi.url_scheme': 'https', + 'wsgi.input': None, + 'REQUEST_METHOD': 'GET', + 'PATH_INFO': '/', + 'CONTENT_TYPE': 'text/plain', + 'wsgi.multithread': True, + 'QUERY_STRING': '', + 'eventlet.input': None} + return dict(environ, **overwrite) + + +def start_response(_status, _headers): + """Dummy start_response to use with WSGI tests.""" + pass -- cgit