from formencode import ForEach, Schema, validators from webtest import TestApp import os.path from pecan import make_app, expose, request, response, redirect, ValidationException from pecan.templating import _builtin_renderers as builtin_renderers try: from simplejson import dumps except ImportError: from json import dumps class TestValidation(object): template_path = os.path.join(os.path.dirname(__file__), 'templates') def test_simple_validation(self): class RegistrationSchema(Schema): first_name = validators.String(not_empty=True) last_name = validators.String(not_empty=True) email = validators.Email() username = validators.PlainText() password = validators.String() password_confirm = validators.String() age = validators.Int() chained_validators = [ validators.FieldsMatch('password', 'password_confirm') ] class RootController(object): @expose(schema=RegistrationSchema()) def index(self, first_name, last_name, email, username, password, password_confirm, age): assert age == 31 assert isinstance(age, int) return 'Success!' @expose(json_schema=RegistrationSchema()) def json(self, data): assert data['age'] == 31 assert isinstance(data['age'], int) return 'Success!' # test form submissions app = TestApp(make_app(RootController())) r = app.post('/', dict( first_name='Jonathan', last_name='LaCour', email='jonathan@cleverdevil.org', username='jlacour', password='123456', password_confirm='123456', age='31' )) assert r.status_int == 200 assert r.body == 'Success!' # test JSON submissions r = app.post('/json', dumps(dict( first_name='Jonathan', last_name='LaCour', email='jonathan@cleverdevil.org', username='jlacour', password='123456', password_confirm='123456', age='31' )), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' def test_simple_failure(self): class RegistrationSchema(Schema): first_name = validators.String(not_empty=True) last_name = validators.String(not_empty=True) email = validators.Email() username = validators.PlainText() password = validators.String() password_confirm = validators.String() age = validators.Int() chained_validators = [ validators.FieldsMatch('password', 'password_confirm') ] class RootController(object): @expose() def errors(self, *args, **kwargs): assert len(request.validation_errors) > 0 return 'There was an error!' @expose(schema=RegistrationSchema()) def index(self, first_name, last_name, email, username, password, password_confirm, age): assert len(request.validation_errors) > 0 return 'Success!' @expose(schema=RegistrationSchema(), error_handler='/errors') def with_handler(self, first_name, last_name, email, username, password, password_confirm, age): assert len(request.validation_errors) > 0 return 'Success!' @expose(json_schema=RegistrationSchema()) def json(self, data): assert len(request.validation_errors) > 0 return 'Success!' @expose(json_schema=RegistrationSchema(), error_handler='/errors') def json_with_handler(self, data): assert len(request.validation_errors) > 0 return 'Success!' # test without error handler app = TestApp(make_app(RootController())) r = app.post('/', dict( first_name='Jonathan', last_name='LaCour', email='jonathan@cleverdevil.org', username='jlacour', password='123456', password_confirm='654321', age='31' )) assert r.status_int == 200 assert r.body == 'Success!' # test with error handler r = app.post('/with_handler', dict( first_name='Jonathan', last_name='LaCour', email='jonathan@cleverdevil.org', username='jlacour', password='123456', password_confirm='654321', age='31' )) assert r.status_int == 200 assert r.body == 'There was an error!' # test JSON without error handler r = app.post('/json', dumps(dict( first_name='Jonathan', last_name='LaCour', email='jonathan@cleverdevil.org', username='jlacour', password='123456', password_confirm='654321', age='31' )), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' # test JSON with error handler r = app.post('/json_with_handler', dumps(dict( first_name='Jonathan', last_name='LaCour', email='jonathan@cleverdevil.org', username='jlacour', password='123456', password_confirm='654321', age='31' )), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'There was an error!' def test_with_variable_decode(self): class ColorSchema(Schema): colors = ForEach(validators.String(not_empty=True)) class RootController(object): @expose() def errors(self, *args, **kwargs): return 'Error with %s!' % ', '.join(request.validation_errors.keys()) @expose(schema=ColorSchema(), variable_decode=True) def index(self, **kwargs): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(schema=ColorSchema(), error_handler='/errors', variable_decode=True) def with_handler(self, **kwargs): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(json_schema=ColorSchema(), variable_decode=True) def json(self, data): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(json_schema=ColorSchema(), error_handler='/errors', variable_decode=True) def json_with_handler(self, data): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(schema=ColorSchema(), variable_decode=dict()) def custom(self, **kwargs): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(schema=ColorSchema(), error_handler='/errors', variable_decode=dict()) def custom_with_handler(self, **kwargs): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(json_schema=ColorSchema(), variable_decode=dict()) def custom_json(self, data): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(json_schema=ColorSchema(), error_handler='/errors', variable_decode=dict()) def custom_json_with_handler(self, data): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(schema=ColorSchema(), variable_decode=dict(dict_char='-', list_char='.')) def alternate(self, **kwargs): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(schema=ColorSchema(), error_handler='/errors', variable_decode=dict(dict_char='-', list_char='.')) def alternate_with_handler(self, **kwargs): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(json_schema=ColorSchema(), variable_decode=dict(dict_char='-', list_char='.')) def alternate_json(self, data): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' @expose(json_schema=ColorSchema(), error_handler='/errors', variable_decode=dict(dict_char='-', list_char='.')) def alternate_json_with_handler(self, data): if request.validation_errors: return ', '.join(request.validation_errors.keys()) else: return 'Success!' # test without error handler app = TestApp(make_app(RootController())) r = app.post('/', { 'colors-0' : 'blue', 'colors-1' : 'red' }) assert r.status_int == 200 assert r.body == 'Success!' # test failure without error handler r = app.post('/', { 'colors-0' : 'blue', 'colors-1' : '' }) assert r.status_int == 200 assert r.body == 'colors-1' # test with error handler r = app.post('/with_handler', { 'colors-0' : 'blue', 'colors-1' : 'red' }) assert r.status_int == 200 assert r.body == 'Success!' # test failure with error handler r = app.post('/with_handler', { 'colors-0' : '', 'colors-1' : 'red' }) assert r.status_int == 200 assert r.body == 'Error with colors-0!' # test JSON without error handler r = app.post('/json', dumps({ 'colors-0' : 'blue', 'colors-1' : 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' # test JSON failure without error handler r = app.post('/json', dumps({ 'colors-0' : 'blue', 'colors-1' : '' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'colors-1' # test JSON with error handler r = app.post('/json_with_handler', dumps({ 'colors-0' : 'blue', 'colors-1' : 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' # test JSON failure with error handler r = app.post('/json_with_handler', dumps({ 'colors-0' : '', 'colors-1' : 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Error with colors-0!' # test custom without error handler r = app.post('/custom', { 'colors-0' : 'blue', 'colors-1' : 'red' }) assert r.status_int == 200 assert r.body == 'Success!' # test custom failure without error handler r = app.post('/custom', { 'colors-0' : 'blue', 'colors-1' : '' }) assert r.status_int == 200 assert r.body == 'colors-1' # test custom with error handler r = app.post('/custom_with_handler', { 'colors-0' : 'blue', 'colors-1' : 'red' }) assert r.status_int == 200 assert r.body == 'Success!' # test custom failure with error handler r = app.post('/custom_with_handler', { 'colors-0' : '', 'colors-1' : 'red' }) assert r.status_int == 200 assert r.body == 'Error with colors-0!' # test custom JSON without error handler r = app.post('/custom_json', dumps({ 'colors-0' : 'blue', 'colors-1' : 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' # test custom JSON failure without error handler r = app.post('/custom_json', dumps({ 'colors-0' : 'blue', 'colors-1' : '' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'colors-1' # test custom JSON with error handler r = app.post('/custom_json_with_handler', dumps({ 'colors-0' : 'blue', 'colors-1' : 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' # test custom JSON failure with error handler r = app.post('/custom_json_with_handler', dumps({ 'colors-0' : '', 'colors-1' : 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Error with colors-0!' # test alternate without error handler r = app.post('/alternate', { 'colors.0' : 'blue', 'colors.1' : 'red' }) assert r.status_int == 200 assert r.body == 'Success!' # test alternate failure without error handler r = app.post('/alternate', { 'colors.0' : 'blue', 'colors.1' : '' }) assert r.status_int == 200 assert r.body == 'colors.1' # test alternate with error handler r = app.post('/alternate_with_handler', { 'colors.0' : 'blue', 'colors.1' : 'red' }) assert r.status_int == 200 assert r.body == 'Success!' # test alternate failure with error handler r = app.post('/alternate_with_handler', { 'colors.0' : '', 'colors.1' : 'red' }) assert r.status_int == 200 assert r.body == 'Error with colors.0!' # test alternate JSON without error handler r = app.post('/alternate_json', dumps({ 'colors.0' : 'blue', 'colors.1' : 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' # test alternate JSON failure without error handler r = app.post('/alternate_json', dumps({ 'colors.0' : 'blue', 'colors.1' : '' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'colors.1' # test alternate JSON with error handler r = app.post('/alternate_json_with_handler', dumps({ 'colors.0' : 'blue', 'colors.1' : 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' # test alternate JSON failure with error handler r = app.post('/alternate_json_with_handler', dumps({ 'colors.0' : '', 'colors.1' : 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Error with colors.0!' def test_htmlfill(self): if 'mako' not in builtin_renderers: return class ColorSchema(Schema): colors = ForEach(validators.String(not_empty=True)) class NameSchema(Schema): name = validators.String(not_empty=True) class RootController(object): @expose(template='mako:form_colors.html', schema=ColorSchema(), variable_decode=True) def index(self, **kwargs): if request.validation_errors: return dict() else: return dict(data=kwargs) @expose(schema=ColorSchema(), error_handler='/errors_with_handler', variable_decode=True) def with_handler(self, **kwargs): return ', '.join(kwargs['colors']) @expose('mako:form_colors.html') def errors_with_handler(self): return dict() @expose(template='mako:form_name.html', schema=NameSchema(), htmlfill=dict(auto_insert_errors=True)) def with_errors(self, **kwargs): return kwargs @expose(schema=NameSchema(), error_handler='/errors_with_handler_and_errors', htmlfill=dict(auto_insert_errors=True)) def with_handler_and_errors(self, **kwargs): return kwargs['name'] @expose('mako:form_name.html') def errors_with_handler_and_errors(self): return dict() @expose(template='json', schema=NameSchema(), htmlfill=dict(auto_insert_errors=True)) def json(self, **kwargs): if request.validation_errors: return dict(error_with=request.validation_errors.keys()) else: return kwargs def _get_contents(filename): return open(os.path.join(self.template_path, filename), 'r').read() # test without error handler app = TestApp(make_app(RootController(), template_path=self.template_path)) r = app.post('/', { 'colors-0' : 'blue', 'colors-1' : 'red' }) assert r.status_int == 200 assert r.body == _get_contents('form_colors_valid.html') # test failure without error handler r = app.post('/', { 'colors-0' : 'blue', 'colors-1' : '' }) assert r.status_int == 200 assert r.body == _get_contents('form_colors_invalid.html') # test with error handler r = app.post('/with_handler', { 'colors-0' : 'blue', 'colors-1' : 'red' }) assert r.status_int == 200 assert r.body == 'blue, red' # test failure with error handler r = app.post('/with_handler', { 'colors-0' : 'blue', 'colors-1' : '' }) assert r.status_int == 200 assert r.body == _get_contents('form_colors_invalid.html') # test with errors r = app.post('/with_errors', { 'name' : 'Yoann' }) assert r.status_int == 200 assert r.body == _get_contents('form_name_valid.html') # test failure with errors r = app.post('/with_errors', { 'name' : '' }) assert r.status_int == 200 assert r.body == _get_contents('form_name_invalid.html') # test with error handler and errors r = app.post('/with_handler_and_errors', { 'name' : 'Yoann' }) assert r.status_int == 200 assert r.body == 'Yoann' # test failure with error handler and errors r = app.post('/with_handler_and_errors', { 'name' : '' }) assert r.status_int == 200 assert r.body == _get_contents('form_name_invalid.html') # test JSON r = app.post('/json', { 'name' : 'Yoann' }) assert r.status_int == 200 assert r.body == dumps(dict(name='Yoann')) # test JSON failure r = app.post('/json', { 'name' : '' }) assert r.status_int == 200 assert r.body == dumps(dict(error_with=['name'])) def test_htmlfill_static(self): if 'mako' not in builtin_renderers: return class LoginSchema(Schema): username = validators.String(not_empty=True) password = validators.String(not_empty=True) class RootController(object): @expose(template='mako:form_login.html', schema=LoginSchema()) def index(self, **kwargs): if request.validation_errors: return dict() else: return dict(data=kwargs) @expose(schema=LoginSchema(), error_handler='/errors_with_handler') def with_handler(self, **kwargs): return '%s:%s' % (kwargs['username'], kwargs['password']) @expose('mako:form_login.html') def errors_with_handler(self): return dict() def _get_contents(filename): return open(os.path.join(self.template_path, filename), 'r').read() # test without error handler app = TestApp(make_app(RootController(), template_path=self.template_path)) r = app.post('/', { 'username' : 'ryan', 'password' : 'password' }) assert r.status_int == 200 assert r.body == _get_contents('form_login_valid.html') # test failure without error handler r = app.post('/', { 'username' : 'ryan', 'password' : '' }) assert r.status_int == 200 assert r.body == _get_contents('form_login_invalid.html') # test with error handler r = app.post('/with_handler', { 'username' : 'ryan', 'password' : 'password' }) assert r.status_int == 200 assert r.body == 'ryan:password' # test failure with error handler r = app.post('/with_handler', { 'username' : 'ryan', 'password' : '' }) assert r.status_int == 200 assert r.body == _get_contents('form_login_invalid.html') def test_error_for(self): if 'mako' not in builtin_renderers: return class ColorSchema(Schema): colors = ForEach(validators.String(not_empty=True)) class RootController(object): @expose(template='mako:error_for.html') def errors(self, *args, **kwargs): return dict() @expose(template='mako:error_for.html', schema=ColorSchema(), variable_decode=True) def index(self, **kwargs): return dict() @expose(schema=ColorSchema(), error_handler='/errors', variable_decode=True) def with_handler(self, **kwargs): return dict() @expose(template='mako:error_for.html', json_schema=ColorSchema(), variable_decode=True) def json(self, data): return dict() @expose(json_schema=ColorSchema(), error_handler='/errors', variable_decode=True) def json_with_handler(self, data): return dict() # test without error handler app = TestApp(make_app(RootController(), template_path=self.template_path)) r = app.post('/', { 'colors-0' : 'blue', 'colors-1' : 'red' }) assert r.status_int == 200 assert r.body == '' # test failure without error handler r = app.post('/', { 'colors-0' : 'blue', 'colors-1' : '' }) assert r.status_int == 200 assert r.body == 'Please enter a value' # test failure with error handler r = app.post('/with_handler', { 'colors-0' : 'blue', 'colors-1' : '' }) assert r.status_int == 200 assert r.body == 'Please enter a value' # test JSON failure without error handler r = app.post('/json', dumps({ 'colors-0' : 'blue', 'colors-1' : '' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Please enter a value' # test JSON failure with error handler r = app.post('/json_with_handler', dumps({ 'colors-0' : 'blue', 'colors-1' : '' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Please enter a value' def test_callable_error_handler(self): class ColorSchema(Schema): colors = ForEach(validators.String(not_empty=True)) class RootController(object): @expose() def errors(self, *args, **kwargs): return 'There was an error!' @expose(schema=ColorSchema(), error_handler=lambda: '/errors', variable_decode=True) def index(self, **kwargs): return 'Success!' # test with error handler app = TestApp(make_app(RootController())) r = app.post('/', { 'colors-0' : 'blue', 'colors-1' : 'red' }) assert r.status_int == 200 assert r.body == 'Success!' # test with error handler r = app.post('/', { 'colors-0' : 'blue', 'colors-1' : '' }) assert r.status_int == 200 assert r.body == 'There was an error!' def test_validation_exception(self): if 'mako' not in builtin_renderers: return class NameSchema(Schema): name = validators.String(not_empty=True) class SubController(object): @expose() def _route(self, *args): raise ValidationException('/success') class RootController(object): sub = SubController() @expose('mako:form_name.html') def errors_name(self): return dict() @expose(schema=NameSchema(), error_handler='/errors_name', htmlfill=dict(auto_insert_errors=True)) def name(self, name): raise ValidationException(errors={'name': 'Names must be unique'}) @expose() def success(self): return 'Success!' def _get_contents(filename): return open(os.path.join(self.template_path, filename), 'r').read() # test exception with no controller app = TestApp(make_app(RootController(), template_path=self.template_path)) r = app.get('/sub') assert r.status_int == 200 assert r.body == 'Success!' # test exception with additional errors r = app.post('/name', {'name': 'Yoann'}) assert r.status_int == 200 assert r.body == _get_contents('form_name_invalid_custom.html')