Pylons / webtest

Wraps any WSGI application and makes it easy to send test requests to that application, without starting up an HTTP server.
https://docs.pylonsproject.org/projects/webtest/en/latest/
Other
336 stars 110 forks source link

Invalid handling of 400 responses #182

Closed Kulv3r closed 7 years ago

Kulv3r commented 7 years ago

I'm writing some tests for my Flask 0.12.1 with WebTest 2.0.27, and stuck with a weird error.

This view:

@blueprint.route('/account/<string:account_slug>/settings', subdomain=CREATE)
@login_required
def settings(account_slug):
    """
    Configure the account.
    """
    account = Account.query.filter(Account.slug == account_slug).first_or_404()
    return render_template('account/settings.html', account=account)

being tested by this code:

class TestAccountSettings(object):
    def test_open_settings(self, account, testapp):
        url = url_for('account.settings', account_slug=account.slug)
        testapp.get(url)

    def test_open_settings_invalid_account(self, testapp):
        url = url_for('account.settings', account_slug='lalala')
        testapp.get(url, status=404)

works fine for the first test, but fails weirdly for the 2nd:

=========== FAILURES ===========
_TestAccountSettings.test_open_settings_invalid_account _

self = <sqlalchemy.engine.base.Connection object at 0x113213240>, dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x11320a978>
constructor = <bound method DefaultExecutionContext._init_compiled of <class 'sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext'>>
statement = 'SELECT account.id AS account_id, account.created_at AS account_created_at, account.updated_at AS account_updated_at, ...ount.nav_text_active_color AS account_nav_text_active_color \nFROM account \nWHERE account.slug = ?\n LIMIT ? OFFSET ?'
parameters = ('x', 1, 0), args = (<sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x113213860>, [immutabledict({})])
conn = <sqlalchemy.pool._ConnectionFairy object at 0x11320af60>, context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x1131d9710>

    def _execute_context(self, dialect, constructor,
                         statement, parameters,
                         *args):
        """Create an :class:`.ExecutionContext` and execute, returning
            a :class:`.ResultProxy`."""
...
../venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1182:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x11320a978>, cursor = <sqlite3.Cursor object at 0x113133c00>
statement = 'SELECT account.id AS account_id, account.created_at AS account_created_at, account.updated_at AS account_updated_at, ...ount.nav_text_active_color AS account_nav_text_active_color \nFROM account \nWHERE account.slug = ?\n LIMIT ? OFFSET ?'
parameters = ('x', 1, 0), context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x1131d9710>

    def do_execute(self, cursor, statement, parameters, context=None):
>       cursor.execute(statement, parameters)
E       sqlite3.OperationalError: no such table: account

../venv3/lib/python3.6/site-packages/sqlalchemy/engine/default.py:470: OperationalError

The above exception was the direct cause of the following exception:

self = <tests.test_accounts.TestAccountSettings object at 0x113190b70>, testapp = <webtest.app.TestApp object at 0x11320a4e0>

    def test_open_settings_invalid_account(self, testapp):
        url = url_for('account.settings', account_slug='x')
        # testapp.get(url, status=404)
>       testapp.get(url, status=400)

tests/test_accounts.py:23:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../venv3/lib/python3.6/site-packages/webtest/app.py:330: in get
    expect_errors=expect_errors)
../venv3/lib/python3.6/site-packages/webtest/app.py:617: in do_request
    res = req.get_response(app, catch_exc_info=True)
../venv3/lib/python3.6/site-packages/webob/request.py:1312: in send
    application, catch_exc_info=True)
../venv3/lib/python3.6/site-packages/webob/request.py:1280: in call_application
    app_iter = application(self.environ, start_response)
../venv3/lib/python3.6/site-packages/webtest/lint.py:198: in lint_app
    iterator = application(environ, start_response_wrapper)
../venv3/lib/python3.6/site-packages/flask/app.py:1997: in __call__
    return self.wsgi_app(environ, start_response)
../venv3/lib/python3.6/site-packages/flask/app.py:1985: in wsgi_app
    response = self.handle_exception(e)
../venv3/lib/python3.6/site-packages/flask/app.py:1540: in handle_exception
    reraise(exc_type, exc_value, tb)
../venv3/lib/python3.6/site-packages/flask/_compat.py:33: in reraise
    raise value
../venv3/lib/python3.6/site-packages/flask/app.py:1982: in wsgi_app
    response = self.full_dispatch_request()
../venv3/lib/python3.6/site-packages/flask/app.py:1614: in full_dispatch_request
    rv = self.handle_user_exception(e)
../venv3/lib/python3.6/site-packages/flask/app.py:1517: in handle_user_exception
    reraise(exc_type, exc_value, tb)
../venv3/lib/python3.6/site-packages/flask/_compat.py:33: in reraise
    raise value
../venv3/lib/python3.6/site-packages/flask/app.py:1612: in full_dispatch_request
    rv = self.dispatch_request()
../venv3/lib/python3.6/site-packages/flask/app.py:1598: in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
../venv3/lib/python3.6/site-packages/flask_login/utils.py:225: in decorated_view
    return func(*args, **kwargs)
app/account/views.py:57: in settings
    account = Account.query.filter(Account.slug == account_slug).first_or_404()
../venv3/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:425: in first_or_404
    rv = self.first()
../venv3/lib/python3.6/site-packages/sqlalchemy/orm/query.py:2755: in first
    ret = list(self[0:1])
../venv3/lib/python3.6/site-packages/sqlalchemy/orm/query.py:2547: in __getitem__
    return list(res)
../venv3/lib/python3.6/site-packages/sqlalchemy/orm/query.py:2855: in __iter__
    return self._execute_and_instances(context)
../venv3/lib/python3.6/site-packages/sqlalchemy/orm/query.py:2878: in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
../venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:945: in execute
    return meth(self, multiparams, params)
../venv3/lib/python3.6/site-packages/sqlalchemy/sql/elements.py:263: in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
../venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1053: in _execute_clauseelement
    compiled_sql, distilled_params
../venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1189: in _execute_context
    context)
../venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1402: in _handle_dbapi_exception
    exc_info
../venv3/lib/python3.6/site-packages/sqlalchemy/util/compat.py:203: in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
../venv3/lib/python3.6/site-packages/sqlalchemy/util/compat.py:186: in reraise
    raise value.with_traceback(tb)
../venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1182: in _execute_context
    context)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x11320a978>, cursor = <sqlite3.Cursor object at 0x113133c00>
statement = 'SELECT account.id AS account_id, account.created_at AS account_created_at, account.updated_at AS account_updated_at, ...ount.nav_text_active_color AS account_nav_text_active_color \nFROM account \nWHERE account.slug = ?\n LIMIT ? OFFSET ?'
parameters = ('x', 1, 0), context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x1131d9710>

    def do_execute(self, cursor, statement, parameters, context=None):
>       cursor.execute(statement, parameters)
E       sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: account [SQL: 'SELECT account.id AS account_id, account.created_at AS account_created_at, account.updated_at AS account_updated_at, account.deleted_at AS account_deleted_at, account.name AS account_name, account.slug AS account_slug, account.nav_bar_color AS account_nav_bar_color, account.nav_text_color AS account_nav_text_color, account.nav_text_active_color AS account_nav_text_active_color \nFROM account \nWHERE account.slug = ?\n LIMIT ? OFFSET ?'] [parameters: ('x', 1, 0)]

../venv3/lib/python3.6/site-packages/sqlalchemy/engine/default.py:470: OperationalError

i.e. "sqlite3.OperationalError: no such table: account". The table is of course there. As far as i get it, it uses the wrong context maybe, but i dunno why.

gawel commented 7 years ago

I don't think this is related to webtest. I'm not a flask user but maybe fixtures are reset for each test and the "account" fixture create the table so the table does not exist in the second test.

Kulv3r commented 7 years ago

It was my bad. Solved the problem. It wasn't obvious for me that i need to pass a fixture as an argument to the test to have the fixture's table created.

So, for anyone around stumbling with similar problem, the solution is to explicitly add the fixture to your args, i.e. in my case:

    def test_open_settings_invalid_account(
            self,
            account,  # <-- ADD THIS FIXTURE to have the "accounts" table created. 
            testapp
        ):
        url = url_for('account.settings', account_slug='lalala')
        testapp.get(url, status=404)