Commits

eduardo schettino committed 38031d1

move tests into package. use doit_python

Comments (0)

Files changed (12)

avalanche/tests/templates/sample_jinja.html

+X is {{x}}

avalanche/tests/test_core.py

+import pytest
+
+from webob import exc
+
+from avalanche.router import Route
+from avalanche.core import Request
+from avalanche.core import RequestHandler, WSGIApplication
+
+
+class TestRequestHandler(object):
+
+    def test_normalize_handler_method(self):
+        assert "x_y" == RequestHandler._normalize_handler_method('x-y')
+
+
+class TestRequestHandler_Dispatch(object):
+
+    # test method not allowed
+    def test_405(self):
+        # supports only post method
+        class OnlyPost(RequestHandler):
+            def post(self): # pragma: nocover
+                pass
+
+        app = WSGIApplication(())
+        handler = OnlyPost(app, Request.blank('/'))
+        try:
+            handler.dispatch()
+        except exc.HTTPMethodNotAllowed as exception:
+            # must list allowed methods
+            assert ('POST',) == exception.allow
+        else: # pragma: nocover
+            assert False
+
+
+    def test_dispatch_writes_on_default_response(self):
+        class Sample(RequestHandler):
+            def get(self):
+                self.response.write('hello')
+
+        app = WSGIApplication(())
+        handler = Sample(app, Request.blank('/'))
+        handler.dispatch()
+        assert 'hello' == handler.response.body
+
+
+    def test_dispatch_with_params(self):
+        class Sample(RequestHandler):
+            def get(self, *args, **kwargs):
+                return args, kwargs
+
+        app = WSGIApplication(())
+        handler = Sample(app, Request.blank('/'))
+        handler.dispatch(1, 2, a='xyz')
+        assert (1,2) == handler.response[0]
+        assert {'a':'xyz'} == handler.response[1]
+
+
+    def test_handle_exception_dafault(self):
+        # it actually re-raise last exception
+        class Sample(RequestHandler):
+            pass
+        handler = Sample(None, None)
+        try:
+            raise Exception('default handler')
+        except Exception:
+            try:
+                handler.handle_exception(None)
+            except Exception as exception:
+                assert 'default handler' in str(exception)
+            else: # pragma: nocover
+                assert False
+
+
+    def test_handle_exception_custom(self):
+        class Sample(RequestHandler):
+            def get(self):
+                raise Exception('xxx')
+
+            def handle_exception(self, exception):
+                return 'handled ok'
+
+        app = WSGIApplication(())
+        handler = Sample(app, Request.blank('/'))
+        handler.dispatch()
+        assert 'handled ok' == handler.response
+
+
+
+class TestRequestHandler_Redirect(object):
+
+    def pytest_funcarg__handler(self, request):
+        class Sample(RequestHandler):pass
+        app = WSGIApplication(())
+        return Sample(app, Request.blank('/one/two/three'))
+
+
+    def test_absolute_url(self, handler):
+        handler.redirect('/3b')
+        assert 'http://localhost/3b' == handler.response.location
+
+    def test_relative_url(self, handler):
+        handler.redirect('3b')
+        assert '3b' == handler.response.location
+
+    def test_relative_url_2(self, handler):
+        handler.redirect('../3b')
+        assert 'http://localhost/one/3b' == handler.response.location
+
+    def test_body_cleared(self, handler):
+        handler.response.write('trash')
+        handler.redirect('/')
+        assert '' == handler.response.body
+
+    def test_body_arg(self, handler):
+        handler.response.write('trash')
+        handler.redirect('/', body='moo')
+        assert 'moo' == handler.response.body
+
+
+    def test_code_default_302(self, handler):
+        handler.redirect('/')
+        assert 302 == handler.response.status_int
+
+    def test_code_permanent_301(self, handler):
+        handler.redirect('/', permanent=True)
+        assert 301 == handler.response.status_int
+
+    def test_code_custom(self, handler):
+        handler.redirect('/', code=307)
+        assert 307 == handler.response.status_int
+
+    def test_code_invalid(self, handler):
+        pytest.raises(AssertionError, handler.redirect, '/', code=400)
+
+    def test_redirect_to(self):
+        routes = [Route('/here', None, 'index')]
+        app = WSGIApplication(routes)
+        class Sample(RequestHandler):pass
+        handler = Sample(app, Request.blank('/one/two/three'))
+
+        assert '/here' == handler.uri_for('index')
+        handler.redirect_to('index', _code=303)
+        assert 'http://localhost/here' == handler.response.location
+        assert 303 == handler.response.status_int
+
+
+
+class TestWSGIApplication(object):
+
+    def test_http_not_implemented(self):
+        app = WSGIApplication([Route('/', RequestHandler)])
+        environ = {'REQUEST_METHOD': 'XXX'}
+        response = Request.blank('/', environ=environ).get_response(app)
+        assert 501 == response.status_int
+
+    def test_404(self):
+        app = WSGIApplication([Route('/p1', RequestHandler)])
+        response = Request.blank('/worng-path').get_response(app)
+        assert "/p1" not in response.body # routes are not displayed
+        assert 404 == response.status_int
+
+    def test_404_debug(self):
+        app = WSGIApplication([Route('/p1', RequestHandler)], debug=True)
+        response = Request.blank('/worng-path').get_response(app)
+        assert "/p1" in response.body # existing route displayed for debugging
+        assert 404 == response.status_int
+
+    def test_ok(self):
+        class Sample(RequestHandler):
+            def get(self):
+                self.response.write('hello test ok')
+        app = WSGIApplication([Route('/', Sample)])
+        response = Request.blank('/').get_response(app)
+        assert 'hello test ok' == response.body
+
+    def test_internal_error(self):
+        class Sample(RequestHandler):
+            def get(self):
+                raise Exception('i fail')
+        app = WSGIApplication([Route('/', Sample)])
+        response = Request.blank('/').get_response(app)
+        assert 500 == response.status_int
+        assert 'Internal Server Error' in response.body
+
+    def test_internal_error_html(self):
+        class Sample(RequestHandler):
+            def get(self):
+                raise Exception('i fail')
+        app = WSGIApplication([Route('/', Sample)], debug=True)
+        response = Request.blank('/').get_response(app)
+        assert 500 == response.status_int
+        assert 'i fail' in response.body
+
+    def test_invalid_response(self):
+        class Sample(RequestHandler):
+            def get(self):
+                return 'xxx'
+        app = WSGIApplication([Route('/', Sample)])
+        response = Request.blank('/').get_response(app)
+        assert 500 == response.status_int
+        assert 'Internal Server Error' in response.body
+
+    def test_handle_exception(self):
+        class Handle404(RequestHandler):
+            def get(self, exception):
+                self.response.write('not here')
+        app = WSGIApplication(error_handlers={404:Handle404})
+        response = Request.blank('/').get_response(app)
+        assert 404 == response.status_int
+        assert 'not here' == response.body
+

avalanche/tests/test_params.py

+
+from avalanche.core import Request, RequestHandler
+from avalanche.params import AddListItemDecorator, AvalancheParam, _PARAM_VAR
+from avalanche.params import UrlPathParam, url_path_param
+from avalanche.params import UrlQueryParam, url_query_param
+from avalanche.params import PostGroupParam, post_group_param
+from avalanche.params import ContextParam, context_param
+from avalanche.snow import _AvalancheHandler
+
+
+class Test_AddListItemDecorator(object):
+    def test_first_item(self):
+        @AddListItemDecorator('list_x', "X33")
+        def sample(xxx): #pragma: nocover
+            pass
+        assert 1 == len(sample.list_x)
+        assert "X33" == sample.list_x[0]
+
+    def test_two_items(self):
+        @AddListItemDecorator('xs', "X1")
+        @AddListItemDecorator('xs', "X2")
+        def sample(xxx): #pragma: nocover
+            pass
+        assert 2 == len(sample.xs)
+        assert "X2" == sample.xs[0]
+        assert "X1" == sample.xs[1]
+
+
+class Test_AvalanchParam(object):
+    def test_init(self):
+        aparam = AvalancheParam('n1', 'n2')
+        assert 'n1' == aparam.obj_name
+        assert 'n2' == aparam.str_name
+
+    def test_init_no_obj_name(self):
+        aparam = AvalancheParam('n')
+        assert 'n' == aparam.obj_name
+        assert 'n' == aparam.str_name
+
+    def test_get_obj_value(self):
+        aparam = AvalancheParam('n1', 'n2', int)
+        assert 2 == aparam.get_obj_value('2', None)
+
+    def test_get_obj_value_no_conversion(self):
+        aparam = AvalancheParam('n1', 'n2')
+        assert '2' == aparam.get_obj_value('2', None)
+
+    def test_get_obj_value_use_handler(self):
+        fake_handler = 'fake handler'
+        def my_converter(xxx, handler):
+            return "%s-%s" % (handler, xxx)
+        aparam = AvalancheParam('n1', 'n2', my_converter)
+        assert 'fake handler-2' == aparam.get_obj_value('2', fake_handler)
+
+    def test_get_obj_value_no_use_handler(self):
+        fake_handler = 'fake handler'
+        def my_converter(xxx, yyy='no'):
+            return "%s-%s" % (yyy, xxx)
+        aparam = AvalancheParam('n1', 'n2', my_converter)
+        assert 'no-2' == aparam.get_obj_value('2', fake_handler)
+
+    def test_repr(self):
+        aparam = AvalancheParam('n1', 'n2')
+        assert repr(aparam) == '<AvalancheParam:n1-n2>'
+
+
+class Test_UrlPathParam(object):
+    def test_get_str_value(self):
+        aparam = UrlPathParam('n')
+        request = Request.blank('/')
+        request.route_kwargs = {'n': 'n_value', 'o': 'other_value',}
+        assert 'n_value' == aparam.get_str_value(request)
+
+    def test_decorator(self):
+        aparam = url_path_param('x')
+        assert isinstance(aparam, AddListItemDecorator)
+        assert isinstance(aparam.item, UrlPathParam)
+        assert _PARAM_VAR == aparam.list_var
+
+
+class Test_UrlQueryParam(object):
+    def test_get_str_value(self):
+        aparam = UrlQueryParam('n')
+        request = Request.blank('/?n=n_value&o=other_value')
+        assert 'n_value' == aparam.get_str_value(request)
+
+    def test_decorator(self):
+        aparam = url_query_param('x')
+        assert isinstance(aparam, AddListItemDecorator)
+        assert isinstance(aparam.item, UrlQueryParam)
+        assert _PARAM_VAR == aparam.list_var
+
+
+class Test_PostGroupParam(object):
+    def test_get_value(self):
+        aparam = PostGroupParam('xy')
+        post_data = {'xy-a':1,'xy-bb':23, 'other':'other_value'}
+        request = Request.blank('/', POST=post_data)
+        handler = RequestHandler(None, request)
+        data = aparam.get_obj_value(aparam.get_str_value(request), handler)
+        assert 2 == len(data)
+        assert '1' == data['a']
+        assert '23' == data['bb']
+
+    def test_decorator(self):
+        aparam = post_group_param('xy', 'xdata')
+        assert isinstance(aparam, AddListItemDecorator)
+        assert isinstance(aparam.item, PostGroupParam)
+        assert _PARAM_VAR == aparam.list_var
+
+
+
+class Test_ContextParam(object):
+
+    def test_get_value(self):
+        aparam = ContextParam('this_user', 'user')
+        request = Request.blank('/')
+        handler = _AvalancheHandler()
+        handler.context = {'user': 'babu'}
+        data = aparam.get_obj_value(aparam.get_str_value(request), handler)
+        assert 'babu' == data
+
+    def test_decorator(self):
+        aparam = context_param('this_user', 'user')
+        assert isinstance(aparam, AddListItemDecorator)
+        assert isinstance(aparam.item, ContextParam)
+        assert _PARAM_VAR == aparam.list_var
+

avalanche/tests/test_router.py

+# -*- coding: utf-8 -*-
+
+import urllib
+
+import pytest
+
+from webob import Request
+from avalanche.router import Route, Router
+
+req = Request.blank
+
+class Test_Route_Repr(object):
+    def test(self):
+        route = Route('/xxx', None)
+        assert "<Route('/xxx', None)>" == repr(route)
+
+
+class Test_Route_Match(object):
+    def test_no_vars(self):
+        route = Route('/xxx', None)
+        assert ((),{}) == route.match(req('/xxx'))
+        assert None == route.match(req('/xxxx'))
+        assert None == route.match(req('/2xxx'))
+        assert None == route.match(req('2/xxx'))
+        assert None == route.match(req('/xxx/'))
+
+    def test_named_var(self):
+        route = Route('/edit/<key>', None)
+        assert ((), {'key':'123'}) == route.match(req('/edit/123'))
+        assert ((), {'key':'45'}) == route.match(req('/edit/45'))
+        assert None == route.match(req('/edit/123/45'))
+
+    def test_named_var_2(self):
+        route = Route('/edit-<key>', None)
+        assert ((), {'key':'123'}) == route.match(req('/edit-123'))
+        assert ((), {'key':'45'}) == route.match(req('/edit-45'))
+        assert None == route.match(req('/edit-123/45'))
+
+    def test_regex_var(self):
+        route = Route('/edit/<key:\d{3}>', None)
+        assert ((), {'key':'123'}) == route.match(req('/edit/123'))
+        assert None == route.match(req('/edit/45'))
+
+    def test_positional_var(self):
+        route = Route('/edit/<>', None)
+        assert (('123',), {}) == route.match(req('/edit/123'))
+        assert (('45',), {}) == route.match(req('/edit/45'))
+        assert None == route.match(req('/edit/xx/'))
+
+    def test_positional_var_2(self):
+        route = Route('/edit/<>/<>', None)
+        assert (('123', '45'), {}) == route.match(req('/edit/123/45'))
+        assert None == route.match(req('/edit/123'))
+
+    def test_named_positional_var(self):
+        route = Route('/edit/<>/<action>', None)
+        assert (('123',), {'action':'go'}) == route.match(req('/edit/123/go'))
+
+    def test_named_positional_var_2(self):
+        route = Route('/edit/<action>/<>', None)
+        assert (('123',), {'action':'go'}) == route.match(req('/edit/go/123'))
+
+
+class Test_Route_build(object):
+    def test_no_vars(self):
+        route = Route('/xxx', None)
+        assert '/xxx' == route.build((), {})
+        # positional argument is just ignored
+        assert '/xxx?d1=abc' == route.build('p1', d1='abc')
+
+    def test_named_var(self):
+        route = Route('/edit/<key>', None)
+        assert '/edit/123' == route.build(key='123')
+
+    def test_missing_var(self):
+        route = Route('/edit/<key>', None)
+        pytest.raises(Exception, route.build)
+
+    def test_var_obj(self):
+        route = Route('/edit/<key>', None)
+        assert '/edit/123' == route.build(key=123)
+
+    def test_var_unicode(self):
+        route = Route('/', None)
+        url = route.build(foo=u'olá')
+        assert urllib.unquote(url).decode('utf-8') == u'/?foo=olá'
+
+    def test_positional_var(self):
+        route = Route('/edit/<>', None)
+        assert '/edit/123' == route.build(123)
+
+    def test_named_positional_var(self):
+        route = Route('/edit/<>/<action>', None)
+        assert '/edit/123/go' == route.build(123, action='go')
+
+    def test_named_positional_var_2(self):
+        route = Route('/edit/<action>/<>', None)
+        assert '/edit/go/123' == route.build(123, action='go')
+
+    def test_fragment(self):
+        route = Route('/xxx', None)
+        built = route.build(action='go', _fragment='part2')
+        assert '/xxx?action=go#part2' == built
+
+    def test_scheme_netloc(self):
+        route = Route('/xxx', None)
+        built = route.build(action='go', _scheme='https', _netloc='example.com')
+        assert 'https://example.com/xxx?action=go' == built
+
+
+
+class TestRouter(object):
+    def test_match(self):
+        r1 = Route('/', None, 'index')
+        r2 = Route('/r2', None, 'page2')
+        r3 = Route('/r3', None)
+        router = Router(r1, r2, r3)
+
+        assert None == router.match(req('nono'))
+        assert (r2, (), {}) == router.match(req('/r2'))
+
+
+    def test_build(self):
+        r1 = Route('/', None, 'index')
+        r2 = Route('/r2/<name>', None, 'page2')
+        router = Router(r1, r2)
+
+        assert '/r2/col?y=1961' == router.build('page2', name='col', y=1961)
+        pytest.raises(Exception, router.build, 'nonono')

avalanche/tests/test_snow.py

+from urlparse import urlparse
+
+import jinja2
+
+from avalanche.router import Route
+from avalanche.core import Request, RequestHandler, WSGIApplication
+from avalanche.params import url_query_param, UrlQueryParam, UrlPathParam
+from avalanche.snow import _Mixer, make_handler
+from avalanche.snow import AvalancheException, ConfigurableMetaclass
+from avalanche.snow import _AvalancheHandler, BaseHandler
+from avalanche.snow import use_namespace, NoOpRenderer
+from avalanche.snow import JsonRenderer, JinjaRenderer
+
+
+def test_noop_renderer():
+    assert NoOpRenderer().render(None) is None
+
+class Test_Mixer(object):
+    def test_mix(self):
+        class A(object):
+            x = 'a'
+        class B(object):
+            x = 'b'
+            y = 7
+        mixed = _Mixer('Mixed', (A,B), {'y':8})
+        assert mixed.__name__ == 'Mixed'
+        assert mixed.__class__ == type
+        assert issubclass(mixed, A)
+        assert issubclass(mixed, B)
+        assert mixed.x == 'a'
+        assert mixed.y == 8
+
+
+class Test_MakeHandler(object):
+    def test_make(self):
+        class MyApp(BaseHandler):
+            x = 1
+        concrete = make_handler(RequestHandler, MyApp)
+        assert concrete.__name__ == 'MyAppHandler'
+        assert concrete.__class__ == ConfigurableMetaclass
+        assert issubclass(concrete, RequestHandler)
+        assert issubclass(concrete, _AvalancheHandler)
+        assert concrete.x == 1
+
+
+
+##### handler
+
+
+class Test_BaseHandler(object):
+    def test_a_config_dict_is_added_to_class(self):
+        class MyHandler(BaseHandler):
+            pass
+        assert MyHandler.a_config == {}
+
+    def test_a_config_get_values_from_methods(self):
+        class MyHandler(BaseHandler):
+            @url_query_param('x')
+            def builder_a(self, x): #pragma: no cover
+                pass
+        assert 'builder_a' in MyHandler.a_config
+        assert 'x' in MyHandler.a_config['builder_a']
+
+    def test_a_config_get_values_from_subclass(self):
+        class MyHandlerBase(BaseHandler):
+            @url_query_param('x')
+            def builder_a(self, x): #pragma: no cover
+                pass
+        class MyHandler(MyHandlerBase):pass
+        assert 'builder_a' in MyHandler.a_config
+        assert 'x' in MyHandler.a_config['builder_a']
+        assert MyHandler.a_config['builder_a']['x'].str_name == 'x'
+
+    def test_a_config_modified_by_subclass(self):
+        class MyHandlerBase(BaseHandler):
+            @url_query_param('x')
+            def builder_a(self, x): #pragma: no cover
+                pass
+        class MyHandler(MyHandlerBase):
+            @classmethod
+            def set_config(cls):
+                cls.a_config['builder_a']['x'] = UrlQueryParam('x', 'x2')
+        assert 'builder_a' in MyHandler.a_config
+        assert 'x' in MyHandler.a_config['builder_a']
+        assert MyHandler.a_config['builder_a']['x'].str_name == 'x2'
+        # base handler is not modified
+        assert MyHandlerBase.a_config['builder_a']['x'].str_name == 'x'
+
+    def test_a_config_subclass_of_modified(self):
+        class MyHandlerBase(BaseHandler):
+            @url_query_param('x')
+            def builder_a(self, x): #pragma: no cover
+                pass
+        class MyHandlerBase2(MyHandlerBase):
+            @classmethod
+            def set_config(cls):
+                cls.a_config['builder_a']['x'] = UrlQueryParam('x', 'x2')
+        class MyHandler(MyHandlerBase2):pass
+        assert 'builder_a' in MyHandler.a_config
+        assert 'x' in MyHandler.a_config['builder_a']
+        assert MyHandler.a_config['builder_a']['x'].str_name == 'x2'
+
+
+class Test_AvalancheHandler_convert_params(object):
+    def test_normal(self):
+        a_params = [UrlQueryParam('n_out', 'n_in', int),
+                    UrlQueryParam('x2'),
+                    ]
+        request = Request.blank('/?n_in=5&o=other_value')
+        params = _AvalancheHandler()._convert_params(request, a_params)
+        assert 2 == len(params)
+        assert 5 == params['n_out']
+        assert '' == params['x2']
+
+    def test_unused_param(self):
+        a_params = [UrlPathParam('n')]
+        request = Request.blank('/')
+        request.route_kwargs = {}
+        params = _AvalancheHandler()._convert_params(request, a_params)
+        assert 0 == len(params)
+
+
+class Test_AvalancheHandler_builder(object):
+
+    def test_ok(self):
+        class MyHandler(_AvalancheHandler):
+            def my_builder(self): # pragma: nocover
+                pass
+        handler = MyHandler()
+        assert handler.my_builder == handler._builder('my_builder')
+
+    def test_invalid_list_context_builder(self):
+        class MyHandler(_AvalancheHandler):
+            pass
+        handler = MyHandler()
+        try:
+            handler._builder('a_get')
+        except Exception, error:
+            assert 'MyHandler' in str(error)
+            assert 'a_get' in str(error)
+        else: # pragma: nocover
+            assert False, 'didnt raise exception'
+
+    def test_invalid_list_context_builder_type(self):
+        class MyHandler(_AvalancheHandler):
+            pass
+        handler = MyHandler()
+        try:
+            handler._builder(345)
+        except AvalancheException, error:
+            assert 'wrong type' in str(error)
+            assert 'string' in str(error)
+            assert 'MyHandler' in str(error)
+            assert '345' in str(error)
+        else: # pragma: nocover
+            assert False, 'didnt raise exception'
+
+
+class Test_AvalancheHandler_build_context(object):
+    def test_no_param(self):
+        class MyHandler(_AvalancheHandler):
+            def a_get(self):
+                return {'x': None}
+        handler = MyHandler()
+        request = Request.blank('/')
+        handler._build_context(handler.context_get, request)
+        assert {'x': None} == handler.context
+
+    def test_error_no_param(self):
+        class MyHandler(_AvalancheHandler):
+            def a_get(self, number): # pragma: nocover
+                pass
+
+        handler = MyHandler()
+        request = Request.blank('/?n=12')
+        try:
+            handler._build_context(handler.context_get, request)
+        except AvalancheException, error:
+            assert 'a_get' in str(error)
+        else: # pragma: nocover
+            assert False, 'didnt raise exception'
+
+
+    def test_builder_specific_param(self):
+        class MyHandler(_AvalancheHandler):
+            @url_query_param('number', 'n', int)
+            def a_get(self, number):
+                return {'x': number}
+        handler = MyHandler()
+        request = Request.blank('/?n=12')
+        handler._build_context(handler.context_get, request)
+        assert {'x':12} == handler.context
+
+
+    def test_context_raises_exception(self):
+        class MyHandler(_AvalancheHandler):
+            def a_get(self):
+                raise TypeError('user code')
+
+        handler = MyHandler()
+        request = Request.blank('/')
+        try:
+            handler._build_context(handler.context_get, request)
+        except TypeError, error:
+            assert 'user code' == str(error)
+        else: # pragma: nocover
+            assert False, 'didnt raise exception'
+
+    def test_context_namespace(self):
+        class MyHandler(_AvalancheHandler):
+            context_get = ['xxx']
+            @use_namespace
+            def xxx(self):
+                return {'a':'A'}
+        handler = MyHandler()
+        request = Request.blank('/')
+        handler._build_context(handler.context_get, request)
+        assert {'xxx':{'a':'A'}} == handler.context
+
+
+    def test_context_built_is_None(self):
+        class MyHandler(_AvalancheHandler):
+            def a_get(self):
+                pass
+        handler = MyHandler()
+        request = Request.blank('/')
+        handler._build_context(handler.context_get, request)
+        assert {} == handler.context
+
+    def test_context_built_is_None_with_namespace(self):
+        class MyHandler(_AvalancheHandler):
+            def a_get(self):
+                return None
+            a_get.use_namespace = True
+        handler = MyHandler()
+        request = Request.blank('/')
+        handler._build_context(handler.context_get, request)
+        assert {'a_get':None} == handler.context
+
+    def test_context_stop(self):
+        class MyHandler(_AvalancheHandler):
+            # since 'fail' method doesnt exist test would fail if
+            # a_get does not set a_stop
+            context_get = ['a_get', 'fail']
+            def a_get(self):
+                self.a_stop = True
+                return {'a':5}
+        handler = MyHandler()
+        request = Request.blank('/')
+        handler._build_context(handler.context_get, request)
+        assert {'a':5} == handler.context
+
+
+
+###################################################
+
+
+class _AppHandler(BaseHandler):
+    renderer = JinjaRenderer(jinja2.Environment(
+            loader=jinja2.FileSystemLoader('avalanche/tests/templates'),
+            undefined=jinja2.DebugUndefined,
+            autoescape=True,
+            ))
+
+AppHandler = make_handler(RequestHandler, _AppHandler)
+
+base_app = WSGIApplication([Route('/', BaseHandler, 'index')])
+
+
+
+class Test_AppHandler_get(object):
+    def test_get_render_jinja(self):
+        class MyHandler(AppHandler):
+            template = "sample_jinja.html"
+            def a_get(self):
+                return {'x': 33}
+        handler = MyHandler(base_app, Request.blank('/'))
+        handler.dispatch()
+        assert 'X is 33' == handler.response.body
+
+    def test_render_shortcut(self):
+        class MyHandler(AppHandler):
+            template = "sample_jinja.html"
+        handler = MyHandler(base_app, Request.blank('/'))
+        handler.render(x=33)
+        assert 'X is 33' == handler.response.body
+
+
+    def test_get_no_render(self):
+        # no template
+        class MyHandler(AppHandler):
+            def a_get(self):
+                pass
+        request = Request.blank('/')
+        handler = MyHandler(base_app, request)
+        handler.get()
+        assert '' == handler.response.body
+
+    def test_get_render_json(self):
+        class MyHandler(AppHandler):
+            renderer = JsonRenderer()
+            def a_get(self):
+                return {'x': 33}
+        request = Request.blank('/')
+        handler = MyHandler(base_app, request)
+        handler.get()
+        assert '{"x": 33}' == handler.response.body
+
+    def test_redirect(self):
+        class MyHandler(AppHandler):
+            def a_get(self):
+                self.a_redirect('index', par1='one')
+        request = Request.blank('/')
+        handler = MyHandler(base_app, request)
+        handler.get()
+        assert True == handler.a_stop
+        assert handler.response.status_int == 302
+        assert urlparse(handler.response.location).path == handler.uri_for('index')
+
+    def test_redirect_no_stop(self):
+        class MyHandler(AppHandler):
+            def a_get(self):
+                self.a_redirect('index', _stop=False, par1='one')
+        request = Request.blank('/')
+        handler = MyHandler(base_app, request)
+        handler.get()
+        assert not handler.a_stop
+        assert handler.response.status_int == 302
+        assert urlparse(handler.response.location).path == handler.uri_for('index')
+
+
+
+class Test_AppHandler_post(object):
+    def test_post(self):
+        called = [] # modifies this
+        class MyHandler(AppHandler):
+            def a_post(self):
+                called.append(True)
+        request = Request.blank('/')
+        handler = MyHandler(base_app, request)
+        handler.post()
+        assert [True] == called
+
+    def test_redirect(self):
+        class MyHandler(AppHandler):
+            def a_post(self):
+                self.a_redirect('index', par1='one')
+        request = Request.blank('/')
+        handler = MyHandler(base_app, request)
+        handler.post()
+        assert handler.response.status_int == 302
+        assert urlparse(handler.response.location).path == handler.uri_for('index')
+
+
 import glob
 
-import pytest
+from doit_python import PythonTasks
 
 
-DOIT_CONFIG = {'default_tasks': ['checker', 'ut',]}
+DOIT_CONFIG = {'default_tasks': ['pyflakes', 'ut',]}
 
-ROOT_MODULES = glob.glob("*.py")
-CODE_FILES = glob.glob("avalanche/*.py")
-TEST_FILES = glob.glob("tests/test_*.py")
-PY_MODULES = (ROOT_MODULES + CODE_FILES + TEST_FILES)
 DOC_FILES = glob.glob("doc/*.rst")
 
-SKIP_CHECKER = set(['setup.py'])
 
+PACKAGES = ('avalanche',)
+py_generator = PythonTasks(PACKAGES)
 
-def task_checker():
-    """run pyflakes on all project modules"""
-    for module in PY_MODULES:
-        if module in SKIP_CHECKER:
-            continue
-        yield {'actions': ["pyflakes %s" % module],
-               'name':module,
-               'file_dep':(module,),
-               }
-
-def run_test(test):
-    return not bool(pytest.main(test))
-def task_ut():
-    """run unit-tests"""
-    for test in TEST_FILES:
-        yield {'name': test,
-               'actions': [(run_test, (test,))],
-               'file_dep': CODE_FILES,
-               'verbosity': 0}
-
-def task_coverage():
-    """show coverage for all modules including tests"""
-    return {'actions':
-                ["coverage run --branch `which py.test` tests",
-                 ("coverage report --show-missing %s" %
-                  " ".join(CODE_FILES + TEST_FILES))
-                 ],
-            'verbosity': 2}
-
-
-def task_coverage_code():
-    """show coverage for all modules (exclude tests)"""
-    return {'actions':
-                ["coverage run --branch `which py.test` ",
-                 "coverage report --show-missing %s" % " ".join(CODE_FILES)],
-            'verbosity': 2}
-
-
-def task_coverage_module():
-    """show coverage for individual modules"""
-    to_strip = len('tests/test_')
-    for test in TEST_FILES:
-        source = "avalanche/" + test[to_strip:]
-        yield {'name': test,
-               'actions':
-                   ["coverage run --branch `which py.test` -v %s" % test,
-                    "coverage report --show-missing %s %s" % (source, test)],
-               'verbosity': 2}
-
+def task_python():
+    yield py_generator.gen_pyflakes(['setup.py'])
+    yield py_generator.gen_ut()
+    yield py_generator.gen_coverage()
+    yield py_generator.gen_coverage_code()
+    yield py_generator.gen_coverage_module()
 
 
 
     action = "sphinx-build -b html -d %s_build/doctrees %s %s"
     return {
         'actions': [action % (DOC_ROOT, DOC_ROOT, DOC_BUILD_PATH)],
-        'file_dep': DOC_FILES + CODE_FILES,
+        'file_dep': DOC_FILES + py_generator.packages['avalanche'].modules,
         }
 
 def task_manifest():
+import glob
+
+class PythonPackage(object):
+    # TODO should track sub-packages
+    def __init__(self, name):
+        self.name = name
+        base = (name+'/') if name else ''
+        self.modules = glob.glob(base + "*.py")
+        self.tests = glob.glob(base + "tests/test_*.py")
+        # TODO other python files on test folders?
+
+
+class PythonTasks(object):
+    """generate tasks for python files pyflakes, ut, coverage
+
+    @ivar packages: (dict) package(str): PythonPackage
+    """
+    def __init__(self, packages, root=True):
+        """
+        @param packages: (list-str)
+        @param root: True include all *.py in root folder
+        """
+        self.packages = dict((p, PythonPackage(p)) for p in packages)
+        if root is True:
+            self.packages[''] = PythonPackage('')
+
+    def gen_pyflakes(self, skip=()):
+        """run pyflakes on all project modules"""
+        skip_modules = set(skip)
+        for package in self.packages.itervalues():
+            for module in package.modules + package.tests:
+                if module in skip_modules:
+                    continue
+                yield {
+                    'basename': 'pyflakes',
+                    'name': module,
+                    'actions': ["pyflakes %s" % module],
+                    'file_dep':(module,),
+                    }
+
+    def gen_ut(self):
+        """run unit-tests"""
+        for package in self.packages.itervalues():
+            for test in package.tests:
+                yield {
+                    'basename': 'ut',
+                    'name': test,
+                    'actions': ["py.test %s" % test],
+                    # XXX not correct dependencies
+                    #'file_dep': package.modules + [test],
+                    'verbosity': 0}
+
+
+    def gen_coverage(self, ignore_root=True):
+        """show coverage for all modules including tests"""
+        all_modules = []
+        for name, package in self.packages.iteritems():
+            if ignore_root and name=='':
+                continue
+            for module in package.modules + package.tests:
+                all_modules.append(module)
+
+        return {
+            'basename': 'coverage',
+            'actions':[
+                "coverage run --branch `which py.test`",
+                "coverage report --show-missing %s" % " ".join(all_modules),
+                ],
+            'verbosity': 2}
+
+
+    def gen_coverage_code(self, ignore_root=True):
+        """show coverage for all modules (exclude tests)"""
+        # TODO DRY
+        all_modules = []
+        for name, package in self.packages.iteritems():
+            if ignore_root and name=='':
+                continue
+            for module in package.modules:
+                all_modules.append(module)
+
+        return {
+            'basename': 'coverage_code',
+            'actions':[
+                "coverage run --branch `which py.test`",
+                "coverage report --show-missing %s" % " ".join(all_modules),
+                ],
+            'verbosity': 2}
+
+
+    def gen_coverage_module(self):
+        """show coverage for individual modules"""
+        to_strip = len('tests/test_')
+        for name, package in self.packages.iteritems():
+            base = name + "/"
+            for test in package.tests:
+                source =  base + test[(len(base) + to_strip):]
+                yield {
+                    'basename': 'coverage_module',
+                    'name': test,
+                    'actions':
+                        ["coverage run --branch `which py.test` -v %s" % test,
+                         "coverage report --show-missing %s %s" % (source, test)],
+                    'verbosity': 2}
+

tests/templates/sample_jinja.html

-X is {{x}}

tests/test_core.py

-import pytest
-
-from webob import exc
-
-from avalanche.router import Route
-from avalanche.core import Request
-from avalanche.core import RequestHandler, WSGIApplication
-
-
-class TestRequestHandler(object):
-
-    def test_normalize_handler_method(self):
-        assert "x_y" == RequestHandler._normalize_handler_method('x-y')
-
-
-class TestRequestHandler_Dispatch(object):
-
-    # test method not allowed
-    def test_405(self):
-        # supports only post method
-        class OnlyPost(RequestHandler):
-            def post(self): # pragma: nocover
-                pass
-
-        app = WSGIApplication(())
-        handler = OnlyPost(app, Request.blank('/'))
-        try:
-            handler.dispatch()
-        except exc.HTTPMethodNotAllowed as exception:
-            # must list allowed methods
-            assert ('POST',) == exception.allow
-        else: # pragma: nocover
-            assert False
-
-
-    def test_dispatch_writes_on_default_response(self):
-        class Sample(RequestHandler):
-            def get(self):
-                self.response.write('hello')
-
-        app = WSGIApplication(())
-        handler = Sample(app, Request.blank('/'))
-        handler.dispatch()
-        assert 'hello' == handler.response.body
-
-
-    def test_dispatch_with_params(self):
-        class Sample(RequestHandler):
-            def get(self, *args, **kwargs):
-                return args, kwargs
-
-        app = WSGIApplication(())
-        handler = Sample(app, Request.blank('/'))
-        handler.dispatch(1, 2, a='xyz')
-        assert (1,2) == handler.response[0]
-        assert {'a':'xyz'} == handler.response[1]
-
-
-    def test_handle_exception_dafault(self):
-        # it actually re-raise last exception
-        class Sample(RequestHandler):
-            pass
-        handler = Sample(None, None)
-        try:
-            raise Exception('default handler')
-        except Exception:
-            try:
-                handler.handle_exception(None)
-            except Exception as exception:
-                assert 'default handler' in str(exception)
-            else: # pragma: nocover
-                assert False
-
-
-    def test_handle_exception_custom(self):
-        class Sample(RequestHandler):
-            def get(self):
-                raise Exception('xxx')
-
-            def handle_exception(self, exception):
-                return 'handled ok'
-
-        app = WSGIApplication(())
-        handler = Sample(app, Request.blank('/'))
-        handler.dispatch()
-        assert 'handled ok' == handler.response
-
-
-
-class TestRequestHandler_Redirect(object):
-
-    def pytest_funcarg__handler(self, request):
-        class Sample(RequestHandler):pass
-        app = WSGIApplication(())
-        return Sample(app, Request.blank('/one/two/three'))
-
-
-    def test_absolute_url(self, handler):
-        handler.redirect('/3b')
-        assert 'http://localhost/3b' == handler.response.location
-
-    def test_relative_url(self, handler):
-        handler.redirect('3b')
-        assert '3b' == handler.response.location
-
-    def test_relative_url_2(self, handler):
-        handler.redirect('../3b')
-        assert 'http://localhost/one/3b' == handler.response.location
-
-    def test_body_cleared(self, handler):
-        handler.response.write('trash')
-        handler.redirect('/')
-        assert '' == handler.response.body
-
-    def test_body_arg(self, handler):
-        handler.response.write('trash')
-        handler.redirect('/', body='moo')
-        assert 'moo' == handler.response.body
-
-
-    def test_code_default_302(self, handler):
-        handler.redirect('/')
-        assert 302 == handler.response.status_int
-
-    def test_code_permanent_301(self, handler):
-        handler.redirect('/', permanent=True)
-        assert 301 == handler.response.status_int
-
-    def test_code_custom(self, handler):
-        handler.redirect('/', code=307)
-        assert 307 == handler.response.status_int
-
-    def test_code_invalid(self, handler):
-        pytest.raises(AssertionError, handler.redirect, '/', code=400)
-
-    def test_redirect_to(self):
-        routes = [Route('/here', None, 'index')]
-        app = WSGIApplication(routes)
-        class Sample(RequestHandler):pass
-        handler = Sample(app, Request.blank('/one/two/three'))
-
-        assert '/here' == handler.uri_for('index')
-        handler.redirect_to('index', _code=303)
-        assert 'http://localhost/here' == handler.response.location
-        assert 303 == handler.response.status_int
-
-
-
-class TestWSGIApplication(object):
-
-    def test_http_not_implemented(self):
-        app = WSGIApplication([Route('/', RequestHandler)])
-        environ = {'REQUEST_METHOD': 'XXX'}
-        response = Request.blank('/', environ=environ).get_response(app)
-        assert 501 == response.status_int
-
-    def test_404(self):
-        app = WSGIApplication([Route('/p1', RequestHandler)])
-        response = Request.blank('/worng-path').get_response(app)
-        assert "/p1" not in response.body # routes are not displayed
-        assert 404 == response.status_int
-
-    def test_404_debug(self):
-        app = WSGIApplication([Route('/p1', RequestHandler)], debug=True)
-        response = Request.blank('/worng-path').get_response(app)
-        assert "/p1" in response.body # existing route displayed for debugging
-        assert 404 == response.status_int
-
-    def test_ok(self):
-        class Sample(RequestHandler):
-            def get(self):
-                self.response.write('hello test ok')
-        app = WSGIApplication([Route('/', Sample)])
-        response = Request.blank('/').get_response(app)
-        assert 'hello test ok' == response.body
-
-    def test_internal_error(self):
-        class Sample(RequestHandler):
-            def get(self):
-                raise Exception('i fail')
-        app = WSGIApplication([Route('/', Sample)])
-        response = Request.blank('/').get_response(app)
-        assert 500 == response.status_int
-        assert 'Internal Server Error' in response.body
-
-    def test_internal_error_html(self):
-        class Sample(RequestHandler):
-            def get(self):
-                raise Exception('i fail')
-        app = WSGIApplication([Route('/', Sample)], debug=True)
-        response = Request.blank('/').get_response(app)
-        assert 500 == response.status_int
-        assert 'i fail' in response.body
-
-    def test_invalid_response(self):
-        class Sample(RequestHandler):
-            def get(self):
-                return 'xxx'
-        app = WSGIApplication([Route('/', Sample)])
-        response = Request.blank('/').get_response(app)
-        assert 500 == response.status_int
-        assert 'Internal Server Error' in response.body
-
-    def test_handle_exception(self):
-        class Handle404(RequestHandler):
-            def get(self, exception):
-                self.response.write('not here')
-        app = WSGIApplication(error_handlers={404:Handle404})
-        response = Request.blank('/').get_response(app)
-        assert 404 == response.status_int
-        assert 'not here' == response.body
-

tests/test_params.py

-
-from avalanche.core import Request, RequestHandler
-from avalanche.params import AddListItemDecorator, AvalancheParam, _PARAM_VAR
-from avalanche.params import UrlPathParam, url_path_param
-from avalanche.params import UrlQueryParam, url_query_param
-from avalanche.params import PostGroupParam, post_group_param
-from avalanche.params import ContextParam, context_param
-from avalanche.snow import _AvalancheHandler
-
-
-class Test_AddListItemDecorator(object):
-    def test_first_item(self):
-        @AddListItemDecorator('list_x', "X33")
-        def sample(xxx): #pragma: nocover
-            pass
-        assert 1 == len(sample.list_x)
-        assert "X33" == sample.list_x[0]
-
-    def test_two_items(self):
-        @AddListItemDecorator('xs', "X1")
-        @AddListItemDecorator('xs', "X2")
-        def sample(xxx): #pragma: nocover
-            pass
-        assert 2 == len(sample.xs)
-        assert "X2" == sample.xs[0]
-        assert "X1" == sample.xs[1]
-
-
-class Test_AvalanchParam(object):
-    def test_init(self):
-        aparam = AvalancheParam('n1', 'n2')
-        assert 'n1' == aparam.obj_name
-        assert 'n2' == aparam.str_name
-
-    def test_init_no_obj_name(self):
-        aparam = AvalancheParam('n')
-        assert 'n' == aparam.obj_name
-        assert 'n' == aparam.str_name
-
-    def test_get_obj_value(self):
-        aparam = AvalancheParam('n1', 'n2', int)
-        assert 2 == aparam.get_obj_value('2', None)
-
-    def test_get_obj_value_no_conversion(self):
-        aparam = AvalancheParam('n1', 'n2')
-        assert '2' == aparam.get_obj_value('2', None)
-
-    def test_get_obj_value_use_handler(self):
-        fake_handler = 'fake handler'
-        def my_converter(xxx, handler):
-            return "%s-%s" % (handler, xxx)
-        aparam = AvalancheParam('n1', 'n2', my_converter)
-        assert 'fake handler-2' == aparam.get_obj_value('2', fake_handler)
-
-    def test_get_obj_value_no_use_handler(self):
-        fake_handler = 'fake handler'
-        def my_converter(xxx, yyy='no'):
-            return "%s-%s" % (yyy, xxx)
-        aparam = AvalancheParam('n1', 'n2', my_converter)
-        assert 'no-2' == aparam.get_obj_value('2', fake_handler)
-
-    def test_repr(self):
-        aparam = AvalancheParam('n1', 'n2')
-        assert repr(aparam) == '<AvalancheParam:n1-n2>'
-
-
-class Test_UrlPathParam(object):
-    def test_get_str_value(self):
-        aparam = UrlPathParam('n')
-        request = Request.blank('/')
-        request.route_kwargs = {'n': 'n_value', 'o': 'other_value',}
-        assert 'n_value' == aparam.get_str_value(request)
-
-    def test_decorator(self):
-        aparam = url_path_param('x')
-        assert isinstance(aparam, AddListItemDecorator)
-        assert isinstance(aparam.item, UrlPathParam)
-        assert _PARAM_VAR == aparam.list_var
-
-
-class Test_UrlQueryParam(object):
-    def test_get_str_value(self):
-        aparam = UrlQueryParam('n')
-        request = Request.blank('/?n=n_value&o=other_value')
-        assert 'n_value' == aparam.get_str_value(request)
-
-    def test_decorator(self):
-        aparam = url_query_param('x')
-        assert isinstance(aparam, AddListItemDecorator)
-        assert isinstance(aparam.item, UrlQueryParam)
-        assert _PARAM_VAR == aparam.list_var
-
-
-class Test_PostGroupParam(object):
-    def test_get_value(self):
-        aparam = PostGroupParam('xy')
-        post_data = {'xy-a':1,'xy-bb':23, 'other':'other_value'}
-        request = Request.blank('/', POST=post_data)
-        handler = RequestHandler(None, request)
-        data = aparam.get_obj_value(aparam.get_str_value(request), handler)
-        assert 2 == len(data)
-        assert '1' == data['a']
-        assert '23' == data['bb']
-
-    def test_decorator(self):
-        aparam = post_group_param('xy', 'xdata')
-        assert isinstance(aparam, AddListItemDecorator)
-        assert isinstance(aparam.item, PostGroupParam)
-        assert _PARAM_VAR == aparam.list_var
-
-
-
-class Test_ContextParam(object):
-
-    def test_get_value(self):
-        aparam = ContextParam('this_user', 'user')
-        request = Request.blank('/')
-        handler = _AvalancheHandler()
-        handler.context = {'user': 'babu'}
-        data = aparam.get_obj_value(aparam.get_str_value(request), handler)
-        assert 'babu' == data
-
-    def test_decorator(self):
-        aparam = context_param('this_user', 'user')
-        assert isinstance(aparam, AddListItemDecorator)
-        assert isinstance(aparam.item, ContextParam)
-        assert _PARAM_VAR == aparam.list_var
-

tests/test_router.py

-# -*- coding: utf-8 -*-
-
-import urllib
-
-import pytest
-
-from webob import Request
-from avalanche.router import Route, Router
-
-req = Request.blank
-
-class Test_Route_Repr(object):
-    def test(self):
-        route = Route('/xxx', None)
-        assert "<Route('/xxx', None)>" == repr(route)
-
-
-class Test_Route_Match(object):
-    def test_no_vars(self):
-        route = Route('/xxx', None)
-        assert ((),{}) == route.match(req('/xxx'))
-        assert None == route.match(req('/xxxx'))
-        assert None == route.match(req('/2xxx'))
-        assert None == route.match(req('2/xxx'))
-        assert None == route.match(req('/xxx/'))
-
-    def test_named_var(self):
-        route = Route('/edit/<key>', None)
-        assert ((), {'key':'123'}) == route.match(req('/edit/123'))
-        assert ((), {'key':'45'}) == route.match(req('/edit/45'))
-        assert None == route.match(req('/edit/123/45'))
-
-    def test_named_var_2(self):
-        route = Route('/edit-<key>', None)
-        assert ((), {'key':'123'}) == route.match(req('/edit-123'))
-        assert ((), {'key':'45'}) == route.match(req('/edit-45'))
-        assert None == route.match(req('/edit-123/45'))
-
-    def test_regex_var(self):
-        route = Route('/edit/<key:\d{3}>', None)
-        assert ((), {'key':'123'}) == route.match(req('/edit/123'))
-        assert None == route.match(req('/edit/45'))
-
-    def test_positional_var(self):
-        route = Route('/edit/<>', None)
-        assert (('123',), {}) == route.match(req('/edit/123'))
-        assert (('45',), {}) == route.match(req('/edit/45'))
-        assert None == route.match(req('/edit/xx/'))
-
-    def test_positional_var_2(self):
-        route = Route('/edit/<>/<>', None)
-        assert (('123', '45'), {}) == route.match(req('/edit/123/45'))
-        assert None == route.match(req('/edit/123'))
-
-    def test_named_positional_var(self):
-        route = Route('/edit/<>/<action>', None)
-        assert (('123',), {'action':'go'}) == route.match(req('/edit/123/go'))
-
-    def test_named_positional_var_2(self):
-        route = Route('/edit/<action>/<>', None)
-        assert (('123',), {'action':'go'}) == route.match(req('/edit/go/123'))
-
-
-class Test_Route_build(object):
-    def test_no_vars(self):
-        route = Route('/xxx', None)
-        assert '/xxx' == route.build((), {})
-        # positional argument is just ignored
-        assert '/xxx?d1=abc' == route.build('p1', d1='abc')
-
-    def test_named_var(self):
-        route = Route('/edit/<key>', None)
-        assert '/edit/123' == route.build(key='123')
-
-    def test_missing_var(self):
-        route = Route('/edit/<key>', None)
-        pytest.raises(Exception, route.build)
-
-    def test_var_obj(self):
-        route = Route('/edit/<key>', None)
-        assert '/edit/123' == route.build(key=123)
-
-    def test_var_unicode(self):
-        route = Route('/', None)
-        url = route.build(foo=u'olá')
-        assert urllib.unquote(url).decode('utf-8') == u'/?foo=olá'
-
-    def test_positional_var(self):
-        route = Route('/edit/<>', None)
-        assert '/edit/123' == route.build(123)
-
-    def test_named_positional_var(self):
-        route = Route('/edit/<>/<action>', None)
-        assert '/edit/123/go' == route.build(123, action='go')
-
-    def test_named_positional_var_2(self):
-        route = Route('/edit/<action>/<>', None)
-        assert '/edit/go/123' == route.build(123, action='go')
-
-    def test_fragment(self):
-        route = Route('/xxx', None)
-        built = route.build(action='go', _fragment='part2')
-        assert '/xxx?action=go#part2' == built
-
-    def test_scheme_netloc(self):
-        route = Route('/xxx', None)
-        built = route.build(action='go', _scheme='https', _netloc='example.com')
-        assert 'https://example.com/xxx?action=go' == built
-
-
-
-class TestRouter(object):
-    def test_match(self):
-        r1 = Route('/', None, 'index')
-        r2 = Route('/r2', None, 'page2')
-        r3 = Route('/r3', None)
-        router = Router(r1, r2, r3)
-
-        assert None == router.match(req('nono'))
-        assert (r2, (), {}) == router.match(req('/r2'))
-
-
-    def test_build(self):
-        r1 = Route('/', None, 'index')
-        r2 = Route('/r2/<name>', None, 'page2')
-        router = Router(r1, r2)
-
-        assert '/r2/col?y=1961' == router.build('page2', name='col', y=1961)
-        pytest.raises(Exception, router.build, 'nonono')

tests/test_snow.py

-from urlparse import urlparse
-
-import jinja2
-
-from avalanche.router import Route
-from avalanche.core import Request, RequestHandler, WSGIApplication
-from avalanche.params import url_query_param, UrlQueryParam, UrlPathParam
-from avalanche.snow import _Mixer, make_handler
-from avalanche.snow import AvalancheException, ConfigurableMetaclass
-from avalanche.snow import _AvalancheHandler, BaseHandler
-from avalanche.snow import use_namespace, NoOpRenderer
-from avalanche.snow import JsonRenderer, JinjaRenderer
-
-
-def test_noop_renderer():
-    assert NoOpRenderer().render(None) is None
-
-class Test_Mixer(object):
-    def test_mix(self):
-        class A(object):
-            x = 'a'
-        class B(object):
-            x = 'b'
-            y = 7
-        mixed = _Mixer('Mixed', (A,B), {'y':8})
-        assert mixed.__name__ == 'Mixed'
-        assert mixed.__class__ == type
-        assert issubclass(mixed, A)
-        assert issubclass(mixed, B)
-        assert mixed.x == 'a'
-        assert mixed.y == 8
-
-
-class Test_MakeHandler(object):
-    def test_make(self):
-        class MyApp(BaseHandler):
-            x = 1
-        concrete = make_handler(RequestHandler, MyApp)
-        assert concrete.__name__ == 'MyAppHandler'
-        assert concrete.__class__ == ConfigurableMetaclass
-        assert issubclass(concrete, RequestHandler)
-        assert issubclass(concrete, _AvalancheHandler)
-        assert concrete.x == 1
-
-
-
-##### handler
-
-
-class Test_BaseHandler(object):
-    def test_a_config_dict_is_added_to_class(self):
-        class MyHandler(BaseHandler):
-            pass
-        assert MyHandler.a_config == {}
-
-    def test_a_config_get_values_from_methods(self):
-        class MyHandler(BaseHandler):
-            @url_query_param('x')
-            def builder_a(self, x): #pragma: no cover
-                pass
-        assert 'builder_a' in MyHandler.a_config
-        assert 'x' in MyHandler.a_config['builder_a']
-
-    def test_a_config_get_values_from_subclass(self):
-        class MyHandlerBase(BaseHandler):
-            @url_query_param('x')
-            def builder_a(self, x): #pragma: no cover
-                pass
-        class MyHandler(MyHandlerBase):pass
-        assert 'builder_a' in MyHandler.a_config
-        assert 'x' in MyHandler.a_config['builder_a']
-        assert MyHandler.a_config['builder_a']['x'].str_name == 'x'
-
-    def test_a_config_modified_by_subclass(self):
-        class MyHandlerBase(BaseHandler):
-            @url_query_param('x')
-            def builder_a(self, x): #pragma: no cover
-                pass
-        class MyHandler(MyHandlerBase):
-            @classmethod
-            def set_config(cls):
-                cls.a_config['builder_a']['x'] = UrlQueryParam('x', 'x2')
-        assert 'builder_a' in MyHandler.a_config
-        assert 'x' in MyHandler.a_config['builder_a']
-        assert MyHandler.a_config['builder_a']['x'].str_name == 'x2'
-        # base handler is not modified
-        assert MyHandlerBase.a_config['builder_a']['x'].str_name == 'x'
-
-    def test_a_config_subclass_of_modified(self):
-        class MyHandlerBase(BaseHandler):
-            @url_query_param('x')
-            def builder_a(self, x): #pragma: no cover
-                pass
-        class MyHandlerBase2(MyHandlerBase):
-            @classmethod
-            def set_config(cls):
-                cls.a_config['builder_a']['x'] = UrlQueryParam('x', 'x2')
-        class MyHandler(MyHandlerBase2):pass
-        assert 'builder_a' in MyHandler.a_config
-        assert 'x' in MyHandler.a_config['builder_a']
-        assert MyHandler.a_config['builder_a']['x'].str_name == 'x2'
-
-
-class Test_AvalancheHandler_convert_params(object):
-    def test_normal(self):
-        a_params = [UrlQueryParam('n_out', 'n_in', int),
-                    UrlQueryParam('x2'),
-                    ]
-        request = Request.blank('/?n_in=5&o=other_value')
-        params = _AvalancheHandler()._convert_params(request, a_params)
-        assert 2 == len(params)
-        assert 5 == params['n_out']
-        assert '' == params['x2']
-
-    def test_unused_param(self):
-        a_params = [UrlPathParam('n')]
-        request = Request.blank('/')
-        request.route_kwargs = {}
-        params = _AvalancheHandler()._convert_params(request, a_params)
-        assert 0 == len(params)
-
-
-class Test_AvalancheHandler_builder(object):
-
-    def test_ok(self):
-        class MyHandler(_AvalancheHandler):
-            def my_builder(self): # pragma: nocover
-                pass
-        handler = MyHandler()
-        assert handler.my_builder == handler._builder('my_builder')
-
-    def test_invalid_list_context_builder(self):
-        class MyHandler(_AvalancheHandler):
-            pass
-        handler = MyHandler()
-        try:
-            handler._builder('a_get')
-        except Exception, error:
-            assert 'MyHandler' in str(error)
-            assert 'a_get' in str(error)
-        else: # pragma: nocover
-            assert False, 'didnt raise exception'
-
-    def test_invalid_list_context_builder_type(self):
-        class MyHandler(_AvalancheHandler):
-            pass
-        handler = MyHandler()
-        try:
-            handler._builder(345)
-        except AvalancheException, error:
-            assert 'wrong type' in str(error)
-            assert 'string' in str(error)
-            assert 'MyHandler' in str(error)
-            assert '345' in str(error)
-        else: # pragma: nocover
-            assert False, 'didnt raise exception'
-
-
-class Test_AvalancheHandler_build_context(object):
-    def test_no_param(self):
-        class MyHandler(_AvalancheHandler):
-            def a_get(self):
-                return {'x': None}
-        handler = MyHandler()
-        request = Request.blank('/')
-        handler._build_context(handler.context_get, request)
-        assert {'x': None} == handler.context
-
-    def test_error_no_param(self):
-        class MyHandler(_AvalancheHandler):
-            def a_get(self, number): # pragma: nocover
-                pass
-
-        handler = MyHandler()
-        request = Request.blank('/?n=12')
-        try:
-            handler._build_context(handler.context_get, request)
-        except AvalancheException, error:
-            assert 'a_get' in str(error)
-        else: # pragma: nocover
-            assert False, 'didnt raise exception'
-
-
-    def test_builder_specific_param(self):
-        class MyHandler(_AvalancheHandler):
-            @url_query_param('number', 'n', int)
-            def a_get(self, number):
-                return {'x': number}
-        handler = MyHandler()
-        request = Request.blank('/?n=12')
-        handler._build_context(handler.context_get, request)
-        assert {'x':12} == handler.context
-
-
-    def test_context_raises_exception(self):
-        class MyHandler(_AvalancheHandler):
-            def a_get(self):
-                raise TypeError('user code')
-
-        handler = MyHandler()
-        request = Request.blank('/')
-        try:
-            handler._build_context(handler.context_get, request)
-        except TypeError, error:
-            assert 'user code' == str(error)
-        else: # pragma: nocover
-            assert False, 'didnt raise exception'
-
-    def test_context_namespace(self):
-        class MyHandler(_AvalancheHandler):
-            context_get = ['xxx']
-            @use_namespace
-            def xxx(self):
-                return {'a':'A'}
-        handler = MyHandler()
-        request = Request.blank('/')
-        handler._build_context(handler.context_get, request)
-        assert {'xxx':{'a':'A'}} == handler.context
-
-
-    def test_context_built_is_None(self):
-        class MyHandler(_AvalancheHandler):
-            def a_get(self):
-                pass
-        handler = MyHandler()
-        request = Request.blank('/')
-        handler._build_context(handler.context_get, request)
-        assert {} == handler.context
-
-    def test_context_built_is_None_with_namespace(self):
-        class MyHandler(_AvalancheHandler):
-            def a_get(self):
-                return None
-            a_get.use_namespace = True
-        handler = MyHandler()
-        request = Request.blank('/')
-        handler._build_context(handler.context_get, request)
-        assert {'a_get':None} == handler.context
-
-    def test_context_stop(self):
-        class MyHandler(_AvalancheHandler):
-            # since 'fail' method doesnt exist test would fail if
-            # a_get does not set a_stop
-            context_get = ['a_get', 'fail']
-            def a_get(self):
-                self.a_stop = True
-                return {'a':5}
-        handler = MyHandler()
-        request = Request.blank('/')
-        handler._build_context(handler.context_get, request)
-        assert {'a':5} == handler.context
-
-
-
-###################################################
-
-
-class _AppHandler(BaseHandler):
-    renderer = JinjaRenderer(jinja2.Environment(
-            loader=jinja2.FileSystemLoader('tests/templates'),
-            undefined=jinja2.DebugUndefined,
-            autoescape=True,
-            ))
-
-AppHandler = make_handler(RequestHandler, _AppHandler)
-
-base_app = WSGIApplication([Route('/', BaseHandler, 'index')])
-
-
-
-class Test_AppHandler_get(object):
-    def test_get_render_jinja(self):
-        class MyHandler(AppHandler):
-            template = "sample_jinja.html"
-            def a_get(self):
-                return {'x': 33}
-        handler = MyHandler(base_app, Request.blank('/'))
-        handler.dispatch()
-        assert 'X is 33' == handler.response.body
-
-    def test_render_shortcut(self):
-        class MyHandler(AppHandler):
-            template = "sample_jinja.html"
-        handler = MyHandler(base_app, Request.blank('/'))
-        handler.render(x=33)
-        assert 'X is 33' == handler.response.body
-
-
-    def test_get_no_render(self):
-        # no template
-        class MyHandler(AppHandler):
-            def a_get(self):
-                pass
-        request = Request.blank('/')
-        handler = MyHandler(base_app, request)
-        handler.get()
-        assert '' == handler.response.body
-
-    def test_get_render_json(self):
-        class MyHandler(AppHandler):
-            renderer = JsonRenderer()
-            def a_get(self):
-                return {'x': 33}
-        request = Request.blank('/')
-        handler = MyHandler(base_app, request)
-        handler.get()
-        assert '{"x": 33}' == handler.response.body
-
-    def test_redirect(self):
-        class MyHandler(AppHandler):
-            def a_get(self):
-                self.a_redirect('index', par1='one')
-        request = Request.blank('/')
-        handler = MyHandler(base_app, request)
-        handler.get()
-        assert True == handler.a_stop
-        assert handler.response.status_int == 302
-        assert urlparse(handler.response.location).path == handler.uri_for('index')
-
-    def test_redirect_no_stop(self):
-        class MyHandler(AppHandler):
-            def a_get(self):
-                self.a_redirect('index', _stop=False, par1='one')
-        request = Request.blank('/')
-        handler = MyHandler(base_app, request)
-        handler.get()
-        assert not handler.a_stop
-        assert handler.response.status_int == 302
-        assert urlparse(handler.response.location).path == handler.uri_for('index')
-
-
-
-class Test_AppHandler_post(object):
-    def test_post(self):
-        called = [] # modifies this
-        class MyHandler(AppHandler):
-            def a_post(self):
-                called.append(True)
-        request = Request.blank('/')
-        handler = MyHandler(base_app, request)
-        handler.post()
-        assert [True] == called
-
-    def test_redirect(self):
-        class MyHandler(AppHandler):
-            def a_post(self):
-                self.a_redirect('index', par1='one')
-        request = Request.blank('/')
-        handler = MyHandler(base_app, request)
-        handler.post()
-        assert handler.response.status_int == 302
-        assert urlparse(handler.response.location).path == handler.uri_for('index')
-
-