Esempio n. 1
0
def init_tests( webdriver, flask_app, dbconn, **kwargs ):
    """Prepare to run tests."""

    # initialize
    global _webdriver, _flask_app
    _webdriver = webdriver
    _flask_app = flask_app

    # initialize the database
    fixtures = kwargs.pop( "fixtures", None )
    if dbconn:
        Session = sqlalchemy.orm.sessionmaker( bind=dbconn )
        session = Session()
        load_fixtures( session, fixtures )
    else:
        assert fixtures is None
        session = None

    # never highlight search results unless explicitly enabled
    if "no_sr_hilite" not in kwargs:
        kwargs[ "no_sr_hilite" ] = 1

    # load the home page
    if webdriver:
        if to_bool( kwargs.pop( "disable_constraints", True ) ):
            kwargs[ "disable_constraints" ] = 1
        if to_bool( kwargs.pop( "disable_confirm_discard_changes", True ) ):
            kwargs[ "disable_confirm_discard_changes" ] = 1
        webdriver.get( webdriver.make_url( "", **kwargs ) )
        wait_for_elem( 2, "#search-form" )

    return session
Esempio n. 2
0
def init_tests(webdriver, flask_app, dbconn, **kwargs):
    """Prepare to run tests."""

    # initialize
    global _webdriver, _flask_app
    _webdriver = webdriver
    _flask_app = flask_app
    fixtures_dir = os.path.join(os.path.dirname(__file__), "fixtures/")

    # initialize the database
    fixtures = kwargs.pop("fixtures", None)
    if dbconn:
        Session = sqlalchemy.orm.sessionmaker(bind=dbconn)
        session = Session()
        load_fixtures(session, fixtures)
    else:
        assert fixtures is None
        session = None

    # re-initialize the search engine
    if flask_app:
        url = flask_app.url_for("init_search_for_test")
        _ = urllib.request.urlopen(url).read()

    # initialize the documents directory
    dname = kwargs.pop("docs", None)
    if dname:
        flask_app.config["EXTERNAL_DOCS_BASEDIR"] = os.path.join(
            fixtures_dir, dname)
    else:
        if flask_app:
            flask_app.config.pop("EXTERNAL_DOCS_BASEDIR", None)

    # never highlight search results unless explicitly enabled
    if "no_sr_hilite" not in kwargs:
        kwargs["no_sr_hilite"] = 1

    # load the home page
    if webdriver:
        if to_bool(kwargs.pop("disable_constraints", True)):
            kwargs["disable_constraints"] = 1
        if to_bool(kwargs.pop("disable_confirm_discard_changes", True)):
            kwargs["disable_confirm_discard_changes"] = 1
        webdriver.get(webdriver.make_url("", **kwargs))
        wait_for_elem(2, "#search-form")

    return session
Esempio n. 3
0
def dbconn(request):
    """Prepare a database connection."""

    # initialize
    conn_string = request.config.getoption("--dbconn")
    temp_fname = None
    if conn_string:
        if os.path.isfile(conn_string):
            # a file was specified - we assume it's an SQLite database
            conn_string = "sqlite:///{}".format(conn_string)
    else:
        # create a temp file and install our database schema into it
        with tempfile.NamedTemporaryFile(delete=False) as temp_file:
            temp_fname = temp_file.name
        dname = os.path.join(os.path.split(__file__)[0], "alembic/")
        cfg = alembic.config.Config(os.path.join(dname, "alembic.ini"))
        cfg.set_main_option("script_location", dname)
        conn_string = "sqlite:///{}".format(temp_fname)
        cfg.set_main_option("sqlalchemy.url", conn_string)
        alembic.command.upgrade(cfg, "head")

    # connect to the database
    engine = sqlalchemy.create_engine(conn_string,
                                      echo=to_bool(
                                          app.config.get("SQLALCHEMY_ECHO")))

    # IMPORTANT! The test suite often loads the database with test data, and then runs searches to see what happens.
    # In the normal case, this works fine:
    # - we use either an existing database, or create a temp file as an sqlite database (see above)
    # - this database is then installed into the temp Flask server that the "flask_app" fixture spun up, thus ensuring
    #   that both the backend server and test code are working with the same database.
    # However, this doesn't work when the test suite is talking to a remote Flask server (via the --flask-url argument),
    # since it has no way of ensuring that the remote Flask server is talking to the same database. In this case,
    # it's the developer's responsibility to make sure that this is the case (by configuring the database in site.cfg).

    prev_db_state = None
    try:
        flask_url = request.config.getoption("--flask-url")  #pylint: disable=no-member
        if flask_url:
            # we are talking to a remote Flask server, we assume it's already talking to the database we are
            pass
        else:
            # remember the database our temp Flask server is currently using
            prev_db_state = (app.config["SQLALCHEMY_DATABASE_URI"],
                             asl_articles.db)
            # replace the database the temp Flask server with ours
            app.config["SQLALCHEMY_DATABASE_URI"] = conn_string
            app.db = SQLAlchemy(app)
        # return the database connection to the caller
        yield engine
    finally:
        # restore the original database into our temp Flask server
        if prev_db_state:
            app.config["SQLALCHEMY_DATABASE_URI"] = prev_db_state[0]
            asl_articles.db = prev_db_state[1]
        # clean up
        if temp_fname:
            os.unlink(temp_fname)
Esempio n. 4
0
def _do_fts_search(fts_query_string, col_names, results=None):  #pylint: disable=too-many-locals
    """Run an FTS search."""

    _logger.debug("FTS query string: %s", fts_query_string)
    if results is None:
        results = []
    no_hilite = request.json and to_bool(request.json.get("no_hilite"))

    # NOTE: We would like to cache the connection, but SQLite connections can only be used
    # in the same thread they were created in.
    with SearchDbConn() as dbconn:

        # generate the search weights
        weights = []
        weights.append(0.0)  # nb: this is for the "owner" column
        for col_name in _SEARCHABLE_COL_NAMES:
            weights.append(_search_weights.get(col_name, 1.0))

        # run the search
        hilites = ["", ""] if no_hilite else [BEGIN_HILITE, END_HILITE]

        def highlight(n):
            return "highlight( searchable, {}, '{}', '{}' )".format(
                n, hilites[0], hilites[1])
        sql = "SELECT owner, bm25(searchable,{}) AS rank, {}, {}, {}, {}, {}, {}, rating FROM searchable" \
            " WHERE searchable MATCH ?" \
            " ORDER BY rating DESC, rank".format(
                ",".join( str(w) for w in weights ),
                highlight(1), highlight(2), highlight(3), highlight(4), highlight(5), highlight(6)
            )
        match = "{{ {} }}: {}".format(
            " ".join(col_names or _SEARCHABLE_COL_NAMES), fts_query_string)
        curs = dbconn.conn.execute(sql, (match, ))

        # get the results
        for row in curs:

            # get the next result
            owner_type, owner_id = row[0].split(":")
            model = get_model_from_table_name(owner_type)
            obj = model.query.get(owner_id)
            _logger.debug("- {} ({:.3f})".format(obj, row[1]))

            # prepare the result for the front-end
            result = globals()["_get_{}_vals".format(owner_type)](obj)
            result["type"] = owner_type
            result["rank"] = row[1]

            # return highlighted versions of the content to the caller
            fields = _FIELD_MAPPINGS[owner_type]
            assert _SEARCHABLE_COL_NAMES[:3] == [
                "name", "name2", "description"
            ]
            for col_no, col_name in enumerate(_SEARCHABLE_COL_NAMES[:3]):
                field = fields.get(col_name)
                if not field:
                    continue
                if row[2 + col_no] and BEGIN_HILITE in row[2 + col_no]:
                    # NOTE: We have to return both the highlighted and non-highlighted versions, since the front-end
                    # will show the highlighted version in the search results, but the non-highlighted version elsewhere
                    # e.g. an article's title in the titlebar of its edit dialog.
                    result[field + "!"] = row[2 + col_no]
            if row[5] and BEGIN_HILITE in row[5]:
                result["authors!"] = row[5].split("\n")
            if row[6] and BEGIN_HILITE in row[6]:
                result["scenarios!"] = [
                    s.split("\t") for s in row[6].split("\n")
                ]
            if row[7] and BEGIN_HILITE in row[7]:
                result["tags!"] = row[7].split("\n")

            # create links to the eASLRB
            if owner_type == "article":
                _create_aslrb_links(result)

            # add the result to the list
            results.append(result)

    # check if we should randomize the results
    if request.json and to_bool(request.json.get("randomize")):
        random.shuffle(results)

    return jsonify(results)
Esempio n. 5
0
# initialize Flask
base_dir = os.path.join(BASE_DIR, "..")
app = Flask(__name__)
app.config.update(_cfg)

# initialize the database connection
app.config["_IS_CONTAINER"] = _cfg.get("IS_CONTAINER")
if _cfg.get("IS_CONTAINER"):
    # if we are running in a container, the database must be specified in an env variable e.g.
    #   docker run -e DBCONN=...
    _dbconn_string = os.environ.get("DBCONN")
else:
    _dbconn_string = app.config.get("DB_CONN_STRING")
app.config["SQLALCHEMY_DATABASE_URI"] = _dbconn_string
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SQLALCHEMY_ECHO"] = to_bool(app.config.get("SQLALCHEMY_ECHO"))
db = SQLAlchemy(app)

# load the application
import asl_articles.globvars  #pylint: disable=cyclic-import
import asl_articles.startup  #pylint: disable=cyclic-import
import asl_articles.main  #pylint: disable=cyclic-import
import asl_articles.search  #pylint: disable=cyclic-import
import asl_articles.publishers  #pylint: disable=cyclic-import
import asl_articles.publications  #pylint: disable=cyclic-import
import asl_articles.articles  #pylint: disable=cyclic-import
import asl_articles.authors  #pylint: disable=cyclic-import
import asl_articles.scenarios  #pylint: disable=cyclic-import
import asl_articles.images  #pylint: disable=cyclic-import
import asl_articles.tags  #pylint: disable=cyclic-import
import asl_articles.docs  #pylint: disable=cyclic-import