Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add testing helpers for grids #91

Closed
mtbrock opened this issue Feb 25, 2019 · 2 comments
Closed

Add testing helpers for grids #91

mtbrock opened this issue Feb 25, 2019 · 2 comments

Comments

@mtbrock
Copy link
Contributor

mtbrock commented Feb 25, 2019

There should be a base testing class for testing webrid grids. We use the following in one of our projects:

import re
import urllib.parse

from blazeutils.spreadsheets import workbook_to_reader
import flask
import flask_login
from pyquery import PyQuery
import pytest
import sqlalchemy


def query_to_str(statement, bind=None):
    """This function is copied directly from sqlalchemybwc.lib.testing

        returns a string of a sqlalchemy.orm.Query with parameters bound
        WARNING: this is dangerous and ONLY for testing, executing the results
        of this function can result in an SQL Injection attack.
    """
    if isinstance(statement, sqlalchemy.orm.Query):
        if bind is None:
            bind = statement.session.get_bind()
        statement = statement.statement
    elif bind is None:
        bind = statement.bind

    if bind is None:
        raise Exception('bind param (engine or connection object) required when using with an '
                        'unbound statement')

    dialect = bind.dialect
    compiler = statement._compiler(dialect)

    class LiteralCompiler(compiler.__class__):
        def visit_bindparam(
                self, bindparam, within_columns_clause=False,
                literal_binds=False, **kwargs
        ):
            return super(LiteralCompiler, self).render_literal_bindparam(
                bindparam, within_columns_clause=within_columns_clause,
                literal_binds=literal_binds, **kwargs
            )

    compiler = LiteralCompiler(dialect, statement)
    return 'TESTING ONLY BIND: ' + compiler.process(statement)


class GridBase(object):
    grid_cls = None
    filters = ()
    sort_tests = ()

    @classmethod
    def setup_class(cls):
        cls.user = User.testing_create()

        if hasattr(cls, 'init'):
            cls.init()

    def assert_in_query(self, look_for, **kwargs):
        pg = self.get_session_grid(**kwargs)
        query_str = query_to_str(pg.build_query())
        assert look_for in query_str, '"{0}" not found in: {1}'.format(look_for, query_str)

    def assert_not_in_query(self, look_for, **kwargs):
        pg = self.get_session_grid(**kwargs)
        query_str = query_to_str(pg.build_query())
        assert look_for not in query_str, '"{0}" found in: {1}'.format(look_for, query_str)

    def assert_regex_in_query(self, look_for, **kwargs):
        pg = self.get_session_grid(**kwargs)
        query_str = query_to_str(pg.build_query())

        if hasattr(look_for, 'search'):
            assert look_for.search(query_str), \
                '"{0}" not found in: {1}'.format(look_for.pattern, query_str)
        else:
            assert re.search(look_for, query_str), \
                '"{0}" not found in: {1}'.format(look_for, query_str)

    def get_session_grid(self, *args, **kwargs):
        flask_login.login_user(kwargs.pop('user', self.user), force=True)
        g = self.grid_cls(*args, **kwargs)
        g.apply_qs_args()
        return g

    def get_pyq(self, grid=None, **kwargs):
        pg = grid or self.get_session_grid(**kwargs)
        html = pg.html()
        return PyQuery('<html>{0}</html>'.format(html))

    def get_sheet(self, grid=None, **kwargs):
        pg = grid or self.get_session_grid(**kwargs)
        xls = pg.xls()
        return workbook_to_reader(xls).sheet_by_index(0)

    def check_filter(self, name, op, value, expected):
        qs_args = [('op({0})'.format(name), op)]
        if isinstance(value, (list, tuple)):
            for v in value:
                qs_args.append(('v1({0})'.format(name), v))
        else:
            qs_args.append(('v1({0})'.format(name), value))

        def sub_func(ex):
            url = '/?' + urllib.parse.urlencode(qs_args)
            with flask.current_app.test_request_context(url):
                if isinstance(ex, re.compile('').__class__):
                    self.assert_regex_in_query(ex)
                else:
                    self.assert_in_query(ex)
                self.get_pyq()  # ensures the query executes and the grid renders without error

        def page_func():
            url = '/?' + urllib.parse.urlencode([('onpage', 2), ('perpage', 1), *qs_args])
            with flask.current_app.test_request_context(url):
                pg = self.get_session_grid()
                if pg.page_count > 1:
                    self.get_pyq()

        if self.grid_cls.pager_on:
            page_func()

        return sub_func(expected)

    def test_filters(self):
        if callable(self.filters):
            cases = self.filters()
        else:
            cases = self.filters
        for name, op, value, expected in cases:
            self.check_filter(name, op, value, expected)

    def check_sort(self, k, ex, asc):
        if not asc:
            k = '-' + k
        d = {'sort1': k}

        def sub_func():
            with flask.current_app.test_request_context('/?' + urllib.parse.urlencode(d)):
                self.assert_in_query('ORDER BY %s%s' % (ex, '' if asc else ' DESC'))
                self.get_pyq()  # ensures the query executes and the grid renders without error

        def page_func():
            url = '/?' + urllib.parse.urlencode({'sort1': k, 'onpage': 2, 'perpage': 1})
            with flask.current_app.test_request_context(url):
                pg = self.get_session_grid()
                if pg.page_count > 1:
                    self.get_pyq()

        if self.grid_cls.pager_on:
            page_func()

        return sub_func()

    @pytest.mark.parametrize('asc', [True, False])
    def test_sort(self, asc):
        for col, expect in self.sort_tests:
            self.check_sort(col, expect, asc)

    def assert_table(self, table, grid=None, **kwargs):
        d = self.get_pyq(grid, **kwargs)

        assert len(d.find('table.records thead th')) == len(table[0])
        for idx, val in enumerate(table[0]):
            assert d.find('table.records thead th').eq(idx).text() == val

        assert len(d.find('table.records tbody tr')) == len(table[1:])
        for row_idx, row in enumerate(table[1:]):
            len(d.find('table.records tbody tr').eq(row_idx)('td')) == len(row)
            for col_idx, val in enumerate(row):
                read = d.find('table.records tbody tr').eq(row_idx)('td').eq(col_idx).text()
                assert read == val, 'row {} col {} {} != {}'.format(row_idx, col_idx, read, val)

    def expect_table_contents(self, expect, grid=None, **kwargs):
        d = self.get_pyq(grid, **kwargs)
        assert len(d.find('table.records tbody tr')) == len(expect)

        for row_idx, row in enumerate(expect):
            td = d.find('table.records tbody tr').eq(row_idx).find('td')
            assert len(td) == len(row)
            for col_idx, val in enumerate(row):
                assert td.eq(col_idx).text() == val
@guruofgentoo
Copy link
Member

I would rather see this in webgrid itself, with a consistent interface from what we use in our larger projects, and made available at the framework plugin level (i.e. webgrid.flask and webgrid.blazeweb) for any of the more specific needs (e.g. @inrequest, etc.).

@mtbrock mtbrock closed this as completed Oct 7, 2019
@bchopson
Copy link
Member

bchopson commented Apr 5, 2021

Moved to level12/webgrid#68

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants