Esempio n. 1
0
    def teardown():
        with app.app_context():
            db.drop_all()

            sleep(10)  # Makes sure that ES is up.
            _es = app.extensions['invenio-search']
            list(_es.delete(ignore=[404]))
Esempio n. 2
0
def app(request):
    """Flask application fixture."""
    app = create_app()

    def teardown():
        with app.app_context():
            db.drop_all()

            sleep(10)  # Makes sure that ES is up.
            _es = app.extensions['invenio-search']
            list(_es.delete(ignore=[404]))

    request.addfinalizer(teardown)

    with app.app_context():
        # Imports must be local, otherwise tasks default to pickle serializer.
        from inspirehep.modules.migrator.tasks import add_citation_counts, migrate

        db.drop_all()
        db.create_all()

        sleep(10)  # Makes sure that ES is up.
        _es = app.extensions['invenio-search']
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))

        migrate('./inspirehep/demosite/data/demo-records.xml.gz', wait_for_results=True)
        es.indices.refresh('records-hep')  # Makes sure that all HEP records were migrated.

        add_citation_counts(request_timeout=40)
        es.indices.refresh('records-hep')  # Makes sure that all citation counts were added.

        yield app
Esempio n. 3
0
def app(request):
    """Flask application fixture.

    Creates a Flask application with a simple testing configuration,
    then creates an application context and inside of it recreates
    all databases and indices from the fixtures. Finally it yields,
    so that all tests that explicitly use the ``app`` fixture have
    access to an application context.

    See: http://flask.pocoo.org/docs/0.12/appcontext/.
    """
    app = create_app()
    app.config.update({'DEBUG': True})

    with app.app_context():
        # Celery task imports must be local, otherwise their
        # configuration would use the default pickle serializer.
        from inspirehep.modules.migrator.tasks import migrate

        db.drop_all()
        db.create_all()

        _es = app.extensions['invenio-search']
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))

        init_all_storage_paths()
        init_users_and_permissions()
        init_collections()

        migrate('./inspirehep/demosite/data/demo-records-acceptance.xml.gz', wait_for_results=True)
        es.indices.refresh('records-hep')

        yield app
Esempio n. 4
0
def script_info(request):
    """Get ScriptInfo object for testing CLI."""
    instance_path = tempfile.mkdtemp()
    app = Flask('testapp', instance_path=instance_path)
    app.config.update(
        ACCOUNTS_USE_CELERY=False,
        SECRET_KEY="CHANGE_ME",
        SECURITY_PASSWORD_SALT="CHANGE_ME_ALSO",
        SQLALCHEMY_DATABASE_URI=os.environ.get(
            'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db'),
        TESTING=True,
    )
    FlaskCLI(app)
    Babel(app)
    Mail(app)
    InvenioDB(app)
    InvenioAccounts(app)

    with app.app_context():
        if not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))
        db.drop_all()
        db.create_all()

    def teardown():
        with app.app_context():
            drop_database(str(db.engine.url))
        shutil.rmtree(instance_path)

    request.addfinalizer(teardown)
    return ScriptInfo(create_app=lambda info: app)
Esempio n. 5
0
def app(request):
    """Flask application fixture."""
    app = create_app()
    app.config.update({"DEBUG": True})

    with app.app_context():
        # Imports must be local, otherwise tasks default to pickle serializer.
        from inspirehep.modules.migrator.tasks import add_citation_counts, migrate
        from inspirehep.modules.fixtures.files import init_all_storage_paths
        from inspirehep.modules.fixtures.users import init_users_and_permissions

        db.drop_all()
        db.create_all()

        sleep(10)  # Makes sure that ES is up.
        _es = app.extensions["invenio-search"]
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))

        init_all_storage_paths()
        init_users_and_permissions()

        migrate("./inspirehep/demosite/data/demo-records.xml.gz", wait_for_results=True)
        es.indices.refresh("records-hep")  # Makes sure that all HEP records were migrated.

        add_citation_counts()
        es.indices.refresh("records-hep")  # Makes sure that all citation counts were added.

        yield app
Esempio n. 6
0
def app(request):
    """Flask application fixture."""
    # Set temporary instance path for sqlite
    instance_path = tempfile.mkdtemp()
    app = Flask("testapp", instance_path=instance_path)
    InvenioDB(app)
    InvenioPIDStore(app)

    app.config.update(
        SQLALCHEMY_DATABASE_URI=os.environ.get("SQLALCHEMY_DATABASE_URI", "sqlite:///test.db"), TESTING=True
    )

    with app.app_context():
        if not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))
        db.drop_all()
        db.create_all()

    def teardown():
        with app.app_context():
            drop_database(str(db.engine.url))
        shutil.rmtree(instance_path)

    request.addfinalizer(teardown)

    return app
Esempio n. 7
0
def test_alembic_revision_fddb3cfe7a9c(alembic_app):
    ext = alembic_app.extensions['invenio-db']

    if db.engine.name == 'sqlite':
        raise pytest.skip('Upgrades are not supported on SQLite.')

    db.drop_all()
    drop_alembic_version_table()

    inspector = inspect(db.engine)
    assert 'inspire_prod_records' not in inspector.get_table_names()
    assert 'workflows_audit_logging' not in inspector.get_table_names()
    assert 'workflows_pending_record' not in inspector.get_table_names()

    ext.alembic.upgrade(target='fddb3cfe7a9c')
    inspector = inspect(db.engine)
    assert 'inspire_prod_records' in inspector.get_table_names()
    assert 'workflows_audit_logging' in inspector.get_table_names()
    assert 'workflows_pending_record' in inspector.get_table_names()

    ext.alembic.downgrade(target='a82a46d12408')
    inspector = inspect(db.engine)
    assert 'inspire_prod_records' not in inspector.get_table_names()
    assert 'workflows_audit_logging' not in inspector.get_table_names()
    assert 'workflows_pending_record' not in inspector.get_table_names()

    drop_alembic_version_table()
Esempio n. 8
0
def app(request):
    """Flask application fixture."""
    instance_path = tempfile.mkdtemp()
    app = Flask('testapp', instance_path=instance_path)
    app.config.update(
        TESTING=True,
        SERVER_NAME='localhost:5000',
        SQLALCHEMY_DATABASE_URI=os.environ.get(
            'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db'
        )
    )
    FlaskCLI(app)
    InvenioDB(app)
    InvenioREST(app)
    InvenioRecords(app)
    InvenioPIDStore(app)
    InvenioRecordsREST(app)

    with app.app_context():
        if not database_exists(str(db.engine.url)) and \
           app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://':
            create_database(db.engine.url)
        db.drop_all()
        db.create_all()

    def finalize():
        with app.app_context():
            db.drop_all()
            if app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://':
                drop_database(db.engine.url)
            shutil.rmtree(instance_path)

    request.addfinalizer(finalize)
    return app
Esempio n. 9
0
def small_app():
    """Flask application fixture."""
    app = create_app()
    app.config.update({'DEBUG': True})

    with app.app_context():
        # Imports must be local, otherwise tasks default to pickle serializer.
        from inspirehep.modules.migrator.tasks.records import migrate
        from inspirehep.modules.fixtures.collections import init_collections
        from inspirehep.modules.fixtures.files import init_all_storage_paths
        from inspirehep.modules.fixtures.users import init_users_and_permissions

        db.drop_all()
        db.create_all()

        sleep(10)
        _es = app.extensions['invenio-search']
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))

        init_all_storage_paths()
        init_users_and_permissions()
        init_collections()

        migrate('./inspirehep/demosite/data/demo-records-small.xml', wait_for_results=True)
        es.indices.refresh('records-hep')

        yield app
Esempio n. 10
0
def app(request):
    """Flask application fixture."""
    instance_path = tempfile.mkdtemp()
    app = Flask('testapp', instance_path=instance_path)
    Babel(app)
    FlaskCLI(app)
    InvenioDB(app)
    LoginManager(app)
    app.admin_app = InvenioAdmin(app)
    protected_view = protected_adminview_factory(TestModelView)
    app.admin_app.admin.add_view(protected_view(TestModel, db.session))

    app.config.update(
        TESTING=True,
        SECRET_KEY="SECRET_KEY",
    )

    with app.app_context():
        if not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))
        db.drop_all()
        db.create_all()

    def teardown():
        with app.app_context():
            drop_database(str(db.engine.url))
        shutil.rmtree(instance_path)

    request.addfinalizer(teardown)
    return app
Esempio n. 11
0
def app(request):
    """Flask application fixture."""
    app = create_app(
        CELERY_ALWAYS_EAGER=True,
        CELERY_CACHE_BACKEND="memory",
        CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
        CELERY_RESULT_BACKEND="cache",
        DEBUG_TB_ENABLED=False,
        SECRET_KEY="CHANGE_ME",
        SECURITY_PASSWORD_SALT="CHANGE_ME",
        MAIL_SUPPRESS_SEND=True,
        SQLALCHEMY_DATABASE_URI=os.environ.get(
            'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db'),
        TESTING=True,
    )

    with app.app_context():
        if not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))
        db.drop_all()
        db.create_all()
        list(current_search.create())

    def teardown():
        with app.app_context():
            drop_database(str(db.engine.url))
            list(current_search.delete(ignore=[404]))

    request.addfinalizer(teardown)

    return app
Esempio n. 12
0
def base_app():
    """Flask application fixture."""
    instance_path = tempfile.mkdtemp()
    base_app = Flask(__name__, instance_path=instance_path)

    base_app.config.update(
        ACCOUNTS_USE_CELERY=False,
        LOGIN_DISABLED=False,
        SECRET_KEY='testing_key',
        SQLALCHEMY_DATABASE_URI=os.getenv('SQLALCHEMY_DATABASE_URI',
                                          'sqlite://'),
        TEST_USER_EMAIL='*****@*****.**',
        TEST_USER_PASSWORD='******',
        TESTING=True,
        WTF_CSRF_ENABLED=False,
    )
    Babel(base_app)
    Mail(base_app)
    Menu(base_app)
    InvenioDB(base_app)
    InvenioAccounts(base_app)
    base_app.register_blueprint(accounts_blueprint)

    with base_app.app_context():
        if str(db.engine.url) != "sqlite://" and \
           not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))
        db.drop_all()
        db.create_all()

    def teardown():
        drop_database(str(db.engine.url))
        shutil.rmtree(instance_path)

    return base_app
Esempio n. 13
0
def db(app):
    """Database fixture."""
    if not database_exists(str(db_.engine.url)):
        create_database(str(db_.engine.url))
    db_.create_all()
    yield db_
    db_.session.remove()
    db_.drop_all()
Esempio n. 14
0
def db(app):
    """Database fixture."""
    if not database_exists(str(db_.engine.url)):
        create_database(str(db_.engine.url))
    db_.create_all()
    yield db_
    db_.session.remove()
    db_.drop_all()
Esempio n. 15
0
def app(request):
    """Flask application fixture."""
    instance_path = tempfile.mkdtemp()
    app = Flask('testapp', instance_path=instance_path)
    es_index = 'invenio_records_rest_test_index'
    app.config.update(
        TESTING=True,
        SERVER_NAME='localhost:5000',
        SQLALCHEMY_DATABASE_URI=os.environ.get('SQLALCHEMY_DATABASE_URI',
                                               'sqlite:///test.db'),
        SQLALCHEMY_TRACK_MODIFICATIONS=True,
        RECORDS_REST_ENDPOINTS=config.RECORDS_REST_ENDPOINTS,
        # No permission checking
        RECORDS_REST_DEFAULT_CREATE_PERMISSION_FACTORY=None,
        RECORDS_REST_DEFAULT_READ_PERMISSION_FACTORY=None,
        RECORDS_REST_DEFAULT_UPDATE_PERMISSION_FACTORY=None,
        RECORDS_REST_DEFAULT_DELETE_PERMISSION_FACTORY=None,
        RECORDS_REST_DEFAULT_SEARCH_INDEX=es_index,
        RECORDS_REST_SORT_OPTIONS={
            es_index: dict(year=dict(fields=['year'], ))
        },
        SEARCH_QUERY_ENHANCERS=[filter_record_access_query_enhancer],
    )
    app.config['RECORDS_REST_ENDPOINTS']['recid']['search_index'] = es_index

    # update the application with the configuration provided by the test
    if hasattr(request, 'param') and 'config' in request.param:
        app.config.update(**request.param['config'])

    FlaskCLI(app)
    InvenioDB(app)
    InvenioREST(app)
    InvenioRecords(app)
    InvenioPIDStore(app)
    InvenioSearch(app)
    InvenioAccess(app)
    InvenioRecordsREST(app)

    with app.app_context():
        if not database_exists(str(db.engine.url)) and \
           app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://':
            create_database(db.engine.url)
        db.drop_all()
        db.create_all()
        if current_search_client.indices.exists(es_index):
            current_search_client.indices.delete(es_index)
            current_search_client.indices.create(es_index)
        prepare_indexing(app)

    def finalize():
        with app.app_context():
            db.drop_all()
            if app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://':
                drop_database(db.engine.url)
            shutil.rmtree(instance_path)

    request.addfinalizer(finalize)
    return app
Esempio n. 16
0
def app(request):
    """Flask application fixture for E2E/integration/selenium tests.
    Overrides the `app` fixture found in `../conftest.py`. Tests/files in this
    folder and subfolders will see this variant of the `app` fixture.
    """
    app = create_app()
    app.config.update(
        dict(TESTING=True,
             TEST_RUNNER="celery.contrib.test_runner.CeleryTestSuiteRunner",
             CELERY_ALWAYS_EAGER=True,
             CELERY_RESULT_BACKEND="cache",
             CELERY_CACHE_BACKEND="memory",
             MAIL_SUPPRESS_SEND=True,
             CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
             SQLALCHEMY_DATABASE_URI=os.environ.get(
                 'SQLALCHEMY_DATABASE_URI',
                 'postgresql+psycopg2://hepdata:hepdata@localhost/hepdata_test'
             )))

    with app.app_context():
        if not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))
        db.create_all()

    with app.app_context():
        db.drop_all()
        db.create_all()
        reindex_all(recreate=True)

        ctx = app.test_request_context()
        ctx.push()

        user_count = User.query.filter_by(email='*****@*****.**').count()
        if user_count == 0:
            user = User(email='*****@*****.**',
                        password='******',
                        active=True)
            admin_role = Role(name='admin')
            coordinator_role = Role(name='coordinator')

            user.roles.append(admin_role)
            user.roles.append(coordinator_role)

            db.session.add(admin_role)
            db.session.add(coordinator_role)
            db.session.add(user)
            db.session.commit()

        load_default_data(app)

    def teardown():
        with app.app_context():
            db.drop_all()
            ctx.pop()

    request.addfinalizer(teardown)

    return app
Esempio n. 17
0
def app():
    """
    Deprecated: do not use this fixtures for new tests, unless for very
    specific use cases. Use `isolated_app` instead.

    Flask application with demosite data and without any database isolation:
    any db transaction performed during the tests are persisted into the db.

    Creates a Flask application with a simple testing configuration,
    then creates an application context and inside of it recreates
    all databases and indices from the fixtures. Finally it yields,
    so that all tests that explicitly use the ``app`` fixtures have
    access to an application context.

    See: http://flask.pocoo.org/docs/0.12/appcontext/.
    """
    app = create_app(
        DEBUG=False,
        # Tests may fail when turned on because of Flask bug (A setup function was called after the first request was handled. when initializing - when Alembic initialization)
        WTF_CSRF_ENABLED=False,
        CELERY_TASK_ALWAYS_EAGER=True,
        CELERY_RESULT_BACKEND='cache',
        CELERY_CACHE_BACKEND='memory',
        CELERY_TASK_EAGER_PROPAGATES=True,
        SECRET_KEY='secret!',
        RECORD_EDITOR_FILE_UPLOAD_FOLDER='tests/integration/editor/temp',
        TESTING=True,
    )

    with app.app_context(), mock.patch(
            'inspirehep.modules.records.receivers.index_modified_citations_from_record.delay'
    ):
        # Celery task imports must be local, otherwise their
        # configuration would use the default pickle serializer.
        from inspirehep.modules.migrator.tasks import migrate_from_file

        db.session.close()
        db.drop_all()
        drop_alembic_version_table()

        alembic = Alembic(app=current_app)
        alembic.upgrade()

        _es = app.extensions['invenio-search']
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))

        init_all_storage_paths()
        init_users_and_permissions()
        init_authentication_token()

        migrate_from_file('./inspirehep/demosite/data/demo-records.xml.gz',
                          wait_for_results=True)

        es.indices.refresh(
            'records-hep')  # Makes sure that all HEP records were migrated.

        yield app
def test_db():
    """Test database backend."""
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get(
        'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db'
    )
    FlaskCLI(app)
    InvenioDB(app)
    InvenioRecords(app)

    with app.app_context():
        create_database(db.engine.url)
        db.create_all()
        assert len(db.metadata.tables) == 3

    data = {'title': 'Test'}
    from invenio_records.models import RecordMetadata as RM

    # Create a record
    with app.app_context():
        assert RM.query.count() == 0

        record_uuid = Record.create(data).id
        db.session.commit()

        assert RM.query.count() == 1
        db.session.commit()

    # Retrieve created record
    with app.app_context():
        record = Record.get_record(record_uuid)
        assert record.dumps() == data
        with pytest.raises(NoResultFound):
            Record.get_record(uuid.uuid4())
        record['field'] = True
        record = record.patch([
            {'op': 'add', 'path': '/hello', 'value': ['world']}
        ])
        assert record['hello'] == ['world']
        record.commit()
        db.session.commit()

    with app.app_context():
        record2 = Record.get_record(record_uuid)
        assert record2.model.version_id == 2
        assert record2['field']
        assert record2['hello'] == ['world']
        db.session.commit()

    # Cannot commit record without model (i.e. Record.create_record)
    with app.app_context():
        record3 = Record({'title': 'Not possible'})
        with pytest.raises(RecordNotCommitableError):
            record3.commit()

    with app.app_context():
        db.drop_all()
        drop_database(db.engine.url)
Esempio n. 19
0
def custom_db(app, CustomMetadata):
    """Database fixture."""
    InvenioDB(app)
    if not database_exists(str(db_.engine.url)):
        create_database(str(db_.engine.url))
    db_.create_all()
    yield db_
    db_.session.remove()
    db_.drop_all()
Esempio n. 20
0
def db(app):
    """Ensure that the database schema is created."""
    if not database_exists(str(db_.engine.url)):
        create_database(str(db_.engine.url))
    db_.create_all()

    yield db_
    db_.session.remove()
    db_.drop_all()
Esempio n. 21
0
def app(request):
    """Flask application fixture."""
    instance_path = tempfile.mkdtemp()
    app = Flask('testapp', instance_path=instance_path)
    app.config.update(
        TESTING=True,
        SECRET_KEY='SECRET_KEY',
        ADMIN_LOGIN_ENDPOINT='login',
        SQLALCHEMY_TRACK_MODIFICATIONS=True,
    )
    Babel(app)
    InvenioDB(app)
    Principal(app)
    LoginManager(app)

    # Install login and access loading.
    @app.login_manager.user_loader
    def load_user(user_id):
        return TestUser.get(user_id)

    @app.route('/login/')
    def login():
        from flask import current_app
        from flask import request as flask_request
        user = TestUser.get(flask_request.args.get('user', 1))
        login_user(user)
        identity_changed.send(
            current_app._get_current_object(),
            identity=Identity(user.id))
        return "Logged In"

    @identity_loaded.connect_via(app)
    def on_identity_loaded(sender, identity):
        identity.user = current_user
        identity.provides.add(UserNeed(current_user.id))
        if current_user.id == 1:
            identity.provides.add(action_admin_access)

    # Register admin view
    InvenioAdmin(
        app, permission_factory=lambda x: Permission(action_admin_access))
    app.extensions['invenio-admin'].register_view(TestModelView, TestModel)

    # Create database
    with app.app_context():
        if not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))
        db.drop_all()
        db.create_all()

    def teardown():
        with app.app_context():
            drop_database(str(db.engine.url))
        shutil.rmtree(instance_path)

    request.addfinalizer(teardown)
    return app
Esempio n. 22
0
def db(app):
    """Get setup database."""
    if not database_exists(str(db_.engine.url)):
        create_database(str(db_.engine.url))
    db_.create_all()
    yield db_
    db_.session.remove()
    db_.drop_all()
    drop_alembic_version_table()
Esempio n. 23
0
def db(app):
    """Ensure that the database schema is created."""
    if not database_exists(str(db_.engine.url)):
        create_database(str(db_.engine.url))
    db_.create_all()

    yield db_
    db_.session.remove()
    db_.drop_all()
Esempio n. 24
0
def app(request):
    """Flask application fixture."""
    instance_path = tempfile.mkdtemp()
    app = Flask('testapp', instance_path=instance_path)
    app.config.update(
        TESTING=True,
        SECRET_KEY='SECRET_KEY',
        ADMIN_LOGIN_ENDPOINT='login',
        SQLALCHEMY_TRACK_MODIFICATIONS=True,
    )
    Babel(app)
    InvenioDB(app)
    Principal(app)
    LoginManager(app)

    # Install login and access loading.
    @app.login_manager.user_loader
    def load_user(user_id):
        return TestUser.get(user_id)

    @app.route('/login/')
    def login():
        from flask import current_app
        from flask import request as flask_request
        user = TestUser.get(flask_request.args.get('user', 1))
        login_user(user)
        identity_changed.send(current_app._get_current_object(),
                              identity=Identity(user.id))
        return "Logged In"

    @identity_loaded.connect_via(app)
    def on_identity_loaded(sender, identity):
        identity.user = current_user
        identity.provides.add(UserNeed(current_user.id))
        if current_user.id == 1:
            identity.provides.add(action_admin_access)

    # Register admin view
    InvenioAdmin(app,
                 permission_factory=lambda x: Permission(action_admin_access))
    app.extensions['invenio-admin'].register_view(TestModelView, TestModel)

    # Create database
    with app.app_context():
        if not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))
        db.drop_all()
        db.create_all()

    def teardown():
        with app.app_context():
            drop_database(str(db.engine.url))
        shutil.rmtree(instance_path)

    request.addfinalizer(teardown)
    return app
Esempio n. 25
0
def app(request):
    """Flask application fixture."""
    instance_path = tempfile.mkdtemp()
    app = Flask('testapp', instance_path=instance_path)
    es_index = 'invenio_records_rest_test_index'
    app.config.update(
        TESTING=True,
        SERVER_NAME='localhost:5000',
        SQLALCHEMY_DATABASE_URI=os.environ.get(
            'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db'
        ),
        RECORDS_REST_ENDPOINTS=config.RECORDS_REST_ENDPOINTS,
        # No permission checking
        RECORDS_REST_DEFAULT_CREATE_PERMISSION_FACTORY=None,
        RECORDS_REST_DEFAULT_READ_PERMISSION_FACTORY=None,
        RECORDS_REST_DEFAULT_UPDATE_PERMISSION_FACTORY=None,
        RECORDS_REST_DEFAULT_DELETE_PERMISSION_FACTORY=None,
        RECORDS_REST_DEFAULT_SEARCH_INDEX=es_index,
        SEARCH_QUERY_ENHANCERS=[filter_record_access_query_enhancer],
        SEARCH_AUTOINDEX=[],
    )
    app.config['RECORDS_REST_ENDPOINTS']['recid']['search_index'] = es_index

    # update the application with the configuration provided by the test
    if hasattr(request, 'param') and 'config' in request.param:
        app.config.update(**request.param['config'])

    FlaskCLI(app)
    InvenioDB(app)
    InvenioREST(app)
    InvenioRecords(app)
    InvenioPIDStore(app)
    InvenioSearch(app)
    InvenioAccess(app)
    InvenioRecordsREST(app)

    with app.app_context():
        if not database_exists(str(db.engine.url)) and \
           app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://':
            create_database(db.engine.url)
        db.drop_all()
        db.create_all()
        if current_search_client.indices.exists(es_index):
            current_search_client.indices.delete(es_index)
            current_search_client.indices.create(es_index)
        prepare_indexing(app)

    def finalize():
        with app.app_context():
            db.drop_all()
            if app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://':
                drop_database(db.engine.url)
            shutil.rmtree(instance_path)

    request.addfinalizer(finalize)
    return app
Esempio n. 26
0
def app():
    """
    Deprecated: do not use this fixtures for new tests, unless for very
    specific use cases. Use `isolated_app` instead.

    Flask application with demosite data and without any database isolation:
    any db transaction performed during the tests are persisted into the db.

    Creates a Flask application with a simple testing configuration,
    then creates an application context and inside of it recreates
    all databases and indices from the fixtures. Finally it yields,
    so that all tests that explicitly use the ``app`` fixtures have
    access to an application context.

    See: http://flask.pocoo.org/docs/0.12/appcontext/.
    """
    app = create_app(
        DEBUG=False,
        # Tests may fail when turned on because of Flask bug (A setup function was called after the first request was handled. when initializing - when Alembic initialization)
        WTF_CSRF_ENABLED=False,
        CELERY_TASK_ALWAYS_EAGER=True,
        CELERY_RESULT_BACKEND='cache',
        CELERY_CACHE_BACKEND='memory',
        CELERY_TASK_EAGER_PROPAGATES=True,
        SECRET_KEY='secret!',
        RECORD_EDITOR_FILE_UPLOAD_FOLDER='tests/integration/editor/temp',
        TESTING=True,
    )

    with app.app_context(), mock.patch(
            'inspirehep.modules.records.receivers.index_modified_citations_from_record.delay'
    ):
        # Celery task imports must be local, otherwise their
        # configuration would use the default pickle serializer.
        from inspirehep.modules.migrator.tasks import migrate_from_file

        db.session.close()
        db.drop_all()
        drop_alembic_version_table()

        alembic = Alembic(app=current_app)
        alembic.upgrade()

        _es = app.extensions['invenio-search']
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))

        init_all_storage_paths()
        init_users_and_permissions()
        init_authentication_token()

        migrate_from_file('./inspirehep/demosite/data/demo-records.xml.gz', wait_for_results=True)

        es.indices.refresh('records-hep')  # Makes sure that all HEP records were migrated.

        yield app
Esempio n. 27
0
def db(app):
    """Database fixture."""
    if not database_exists(str(db_.engine.url)) and app.config["SQLALCHEMY_DATABASE_URI"] != "sqlite://":
        create_database(db_.engine.url)
    db_.create_all()

    yield db_

    db_.session.remove()
    db_.drop_all()
Esempio n. 28
0
def db():
    """Setup database."""
    if not database_exists(str(db_.engine.url)):
        create_database(str(db_.engine.url))
    db_.create_all()

    yield db_

    db_.session.remove()
    db_.drop_all()
Esempio n. 29
0
def app(request):
    """Flask application fixture for E2E/integration/selenium tests.
    Overrides the `app` fixture found in `../conftest.py`. Tests/files in this
    folder and subfolders will see this variant of the `app` fixture.
    """
    app = create_app()
    app.config.update(dict(
        TESTING=True,
        TEST_RUNNER="celery.contrib.test_runner.CeleryTestSuiteRunner",
        CELERY_ALWAYS_EAGER=True,
        CELERY_RESULT_BACKEND="cache",
        CELERY_CACHE_BACKEND="memory",
        MAIL_SUPPRESS_SEND=True,
        CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
        SQLALCHEMY_DATABASE_URI=os.environ.get(
            'SQLALCHEMY_DATABASE_URI', 'postgresql+psycopg2://localhost/hepdata_test')
    ))

    with app.app_context():
        if not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))
        db.create_all()

    with app.app_context():
        db.drop_all()
        db.create_all()
        reindex_all(recreate=True)

        ctx = app.test_request_context()
        ctx.push()

        user_count = User.query.filter_by(email='*****@*****.**').count()
        if user_count == 0:
            user = User(email='*****@*****.**', password='******', active=True)
            admin_role = Role(name='admin')
            coordinator_role = Role(name='coordinator')

            user.roles.append(admin_role)
            user.roles.append(coordinator_role)

            db.session.add(admin_role)
            db.session.add(coordinator_role)
            db.session.add(user)
            db.session.commit()

        load_default_data(app)

    def teardown():
        with app.app_context():
            db.drop_all()
            ctx.pop()

    request.addfinalizer(teardown)

    return app
Esempio n. 30
0
def db(app):
    """Database fixture."""
    if not database_exists(str(db_.engine.url)) and \
            app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://':
        create_database(db_.engine.url)
    db_.create_all()

    yield db_

    db_.session.remove()
    db_.drop_all()
Esempio n. 31
0
def db(app):
    """Setup database."""
    with app.app_context():
        db_.init_app(app)
        if not database_exists(str(db_.engine.url)):
            create_database(str(db_.engine.url))
        db_.create_all()
    yield db_
    with app.app_context():
        db_.session.remove()
        db_.drop_all()
Esempio n. 32
0
def init_db():
    LOGGER.info('Recreating the DB')
    db.session.close()
    db.drop_all()
    drop_alembic_version_table()

    alembic = Alembic(app=current_app)
    alembic.upgrade()

    db.session.commit()
    LOGGER.info('Recreating the DB: done')
    return jsonify("Db recreated")
def db(app):
    """Database fixture."""
    from invenio_db import db as db_
    if not database_exists(str(db_.engine.url)) and \
            app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://':
        create_database(db_.engine.url)
    db_.create_all()

    yield db_

    db_.session.remove()
    db_.drop_all()
def init_db():
    LOGGER.info('Recreating the DB')
    db.session.close()
    db.drop_all()
    drop_alembic_version_table()

    alembic = Alembic(app=current_app)
    alembic.upgrade()

    db.session.commit()
    LOGGER.info('Recreating the DB: done')
    return jsonify("Db recreated")
Esempio n. 35
0
def alembic_app():
    """Flask application for Alembic tests."""
    app = create_app(
        DEBUG=True,
        SQLALCHEMY_DATABASE_URI='postgresql+psycopg2://inspirehep:dbpass123@localhost:5432/inspirehep_alembic',
    )

    with app.app_context():
        db.create_all()
        yield app
        db.drop_all()
        drop_alembic_version_table()
Esempio n. 36
0
def db(base_app):
    """Initialize database."""
    # Init
    if not database_exists(str(_db.engine.url)):
        create_database(str(_db.engine.url))
    _db.create_all()

    yield _db

    # Teardown
    _db.session.remove()
    _db.drop_all()
Esempio n. 37
0
def db(app):
    """Create database for the tests."""
    with app.app_context():
        if not database_exists(str(_db.engine.url)) and \
                app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://':
            create_database(_db.engine.url)
        _db.create_all()

    yield _db

    # Explicitly close DB connection
    _db.session.close()
    _db.drop_all()
Esempio n. 38
0
def test_db(app):
    """Create database for the tests."""
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
    if not database_exists(str(db_.engine.url)):
        create_database(db_.engine.url)
    db_.drop_all()
    db_.create_all()

    yield db_

    # Explicitly close DB connection
    db_.session.close()
    db_.drop_all()
Esempio n. 39
0
def alembic_app():
    """Flask application with no records and module scope."""
    app = create_app(
        SQLALCHEMY_DATABASE_URI='postgresql+psycopg2://inspirehep:dbpass123@localhost:5432/inspirehep_alembic'
    )
    app.config.update({
        'DEBUG': True,
    })

    with app.app_context():
        db.create_all()
        yield app
        db.drop_all()
Esempio n. 40
0
def clear_environment(app):
    with app.app_context():
        db.session.close()
        db.drop_all()
        drop_alembic_version_table()

        alembic = Alembic(app=app)
        alembic.upgrade()
        list(current_search.delete(ignore=[404]))
        list(current_search.create(ignore=[400]))
        current_search.flush_and_refresh('records-hep')

        init_all_storage_paths()
        init_users_and_permissions()
Esempio n. 41
0
def app():
    """Flask application.

    Creates a Flask application with a simple testing configuration,
    then creates an application context and inside of it recreates
    all databases and indices from the fixtures. Finally it yields,
    so that all tests that explicitly use the ``app`` fixture have
    access to an application context.

    See: http://flask.pocoo.org/docs/0.12/appcontext/.
    """
    app = create_app(
        DEBUG=True,
        WTF_CSRF_ENABLED=False,
        CELERY_TASK_ALWAYS_EAGER=True,
        CELERY_RESULT_BACKEND='cache',
        CELERY_CACHE_BACKEND='memory',
        CELERY_TASK_EAGER_PROPAGATES=True,
        SECRET_KEY='secret!',
        RECORD_EDITOR_FILE_UPLOAD_FOLDER='tests/integration/editor/temp',
        TESTING=True,
    )

    with app.app_context():
        # Celery task imports must be local, otherwise their
        # configuration would use the default pickle serializer.
        from inspirehep.modules.migrator.tasks import add_citation_counts, migrate_from_file

        db.drop_all()
        db.create_all()

        _es = app.extensions['invenio-search']
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))

        init_all_storage_paths()
        init_users_and_permissions()
        init_collections()

        migrate_from_file('./inspirehep/demosite/data/demo-records.xml.gz',
                          wait_for_results=True)
        es.indices.refresh(
            'records-hep')  # Makes sure that all HEP records were migrated.

        add_citation_counts()
        es.indices.refresh(
            'records-hep')  # Makes sure that all citation counts were added.

        yield app
Esempio n. 42
0
def clear_environment(app):
    with app.app_context():
        db.session.close()
        db.drop_all()
        drop_alembic_version_table()

        alembic = Alembic(app=app)
        alembic.upgrade()
        _es = app.extensions['invenio-search']
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))
        es.indices.refresh('records-hep')

        init_all_storage_paths()
        init_users_and_permissions()
Esempio n. 43
0
def clear_environment(app):
    with app.app_context():
        db.session.close()
        db.drop_all()
        drop_alembic_version_table()

        alembic = Alembic(app=app)
        alembic.upgrade()
        _es = app.extensions['invenio-search']
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))
        es.indices.refresh('records-hep')

        init_all_storage_paths()
        init_users_and_permissions()
def app(od_licenses_json):
    """Flask application fixture."""
    instance_path = tempfile.mkdtemp()
    app = Flask('testapp', instance_path=instance_path)
    app.config.update(
        JSONSCHEMAS_HOST='localhost',
        SQLALCHEMY_DATABASE_URI=os.environ.get('SQLALCHEMY_DATABASE_URI',
                                               'sqlite://'),
        TESTING=True,
        RECORDS_REST_DEFAULT_READ_PERMISSION_FACTORY=None,
        CELERY_ALWAYS_EAGER=True,
        CELERY_RESULT_BACKEND="cache",
        CELERY_CACHE_BACKEND="memory",
        CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
    )

    app.url_map.converters['pid'] = PIDConverter

    FlaskCeleryExt(app)
    InvenioDB(app)
    InvenioJSONSchemas(app)
    InvenioRecords(app)
    InvenioIndexer(app)
    InvenioPIDStore(app)
    InvenioOpenDefinition(app)

    with app.app_context():
        if str(db.engine.url) != "sqlite://" and \
           not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))

        # MySQL has case-sensitivity issues with its default collation. To fix
        # this, we alter the created database's charset and collation.
        if str(db.engine.url).startswith('mysql'):
            conn = db.engine.connect()
            conn.execute('COMMIT')  # close the current transaction
            conn.execute('ALTER DATABASE invenio '
                         'CHARACTER SET utf8 COLLATE utf8_bin')
            conn.close()
        db.drop_all()
        db.create_all()

    def teardown():
        drop_database(str(db.engine.url))

    yield app

    shutil.rmtree(instance_path)
def app(od_licenses_json):
    """Flask application fixture."""
    instance_path = tempfile.mkdtemp()
    app = Flask('testapp', instance_path=instance_path)
    app.config.update(
        JSONSCHEMAS_HOST='localhost',
        SQLALCHEMY_DATABASE_URI=os.environ.get(
            'SQLALCHEMY_DATABASE_URI', 'sqlite://'),
        TESTING=True,
        RECORDS_REST_DEFAULT_READ_PERMISSION_FACTORY=None,
        CELERY_ALWAYS_EAGER=True,
        CELERY_RESULT_BACKEND="cache",
        CELERY_CACHE_BACKEND="memory",
        CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
    )

    app.url_map.converters['pid'] = PIDConverter

    FlaskCeleryExt(app)
    InvenioDB(app)
    InvenioJSONSchemas(app)
    InvenioRecords(app)
    InvenioIndexer(app)
    InvenioPIDStore(app)
    InvenioOpenDefinition(app)

    with app.app_context():
        if str(db.engine.url) != "sqlite://" and \
           not database_exists(str(db.engine.url)):
            create_database(str(db.engine.url))

        # MySQL has case-sensitivity issues with its default collation. To fix
        # this, we alter the created database's charset and collation.
        if str(db.engine.url).startswith('mysql'):
            conn = db.engine.connect()
            conn.execute('COMMIT')  # close the current transaction
            conn.execute('ALTER DATABASE invenio '
                         'CHARACTER SET utf8 COLLATE utf8_bin')
            conn.close()
        db.drop_all()
        db.create_all()

    def teardown():
        drop_database(str(db.engine.url))

    yield app

    shutil.rmtree(instance_path)
def test_alembic(app):
    """Test alembic recipes."""
    ext = app.extensions['invenio-db']

    if db.engine.name == 'sqlite':
        raise pytest.skip('Upgrades are not supported on SQLite.')

    assert not ext.alembic.compare_metadata()
    db.drop_all()
    ext.alembic.upgrade()

    assert not ext.alembic.compare_metadata()
    ext.alembic.downgrade(target='96e796392533')
    ext.alembic.upgrade()

    assert not ext.alembic.compare_metadata()
def test_alembic(app):
    """Test alembic recipes."""
    ext = app.extensions['invenio-db']

    if db.engine.name == 'sqlite':
        raise pytest.skip('Upgrades are not supported on SQLite.')

    assert not ext.alembic.compare_metadata()
    db.drop_all()
    ext.alembic.upgrade()

    assert not ext.alembic.compare_metadata()
    ext.alembic.downgrade(target='96e796392533')
    ext.alembic.upgrade()

    assert not ext.alembic.compare_metadata()
def test_alembic(app):
    """Test alembic recipes."""
    ext = app.extensions["invenio-db"]

    if db.engine.name == "sqlite":
        raise pytest.skip("Upgrades are not supported on SQLite.")

    assert not ext.alembic.compare_metadata()
    db.drop_all()
    ext.alembic.upgrade()

    assert not ext.alembic.compare_metadata()
    ext.alembic.downgrade(target="e12419831262")
    ext.alembic.upgrade()

    assert not ext.alembic.compare_metadata()
def test_alembic(app):
    """Test alembic recipes."""
    ext = app.extensions["invenio-db"]

    with app.app_context():
        if db.engine.name == "sqlite":
            raise pytest.skip("Upgrades are not supported on SQLite.")

        assert not ext.alembic.compare_metadata()
        db.drop_all()
        ext.alembic.upgrade()

        assert not ext.alembic.compare_metadata()
        ext.alembic.downgrade(target="96e796392533")
        ext.alembic.upgrade()

        assert not ext.alembic.compare_metadata()
Esempio n. 50
0
def database(appctx):
    """Setup database.

    Scope: module

    Normally, tests should use the function-scoped :py:data:`db` fixture
    instead. This fixture takes care of creating the database/tables and
    removing the tables once tests are done.
    """
    if not database_exists(str(db_.engine.url)):
        create_database(str(db_.engine.url))
    db_.create_all()

    yield db_

    db_.session.remove()
    db_.drop_all()
Esempio n. 51
0
def test_init():
    app = Flask('demo')
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
    FlaskCLI(app)
    InvenioDB(app)

    class Demo(db.Model):
        __tablename__ = 'demo'
        pk = sa.Column(sa.Integer, primary_key=True)

    class Demo2(db.Model):
        __tablename__ = 'demo2'
        pk = sa.Column(sa.Integer, primary_key=True)

    with app.app_context():
        db.create_all()
        assert len(db.metadata.tables) == 2
        db.drop_all()
Esempio n. 52
0
def test_init():
    app = Flask('demo')
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
    FlaskCLI(app)
    InvenioDB(app)

    class Demo(db.Model):
        __tablename__ = 'demo'
        pk = sa.Column(sa.Integer, primary_key=True)

    class Demo2(db.Model):
        __tablename__ = 'demo2'
        pk = sa.Column(sa.Integer, primary_key=True)

    with app.app_context():
        db.create_all()
        assert len(db.metadata.tables) == 2
        db.drop_all()
Esempio n. 53
0
def db(app):
    """Create database for the tests."""
    dir_path = os.path.dirname(__file__)
    parent_path = str(Path(dir_path).parent)
    db_path = os.environ.get('SQLALCHEMY_DATABASE_URI',
                             f'sqlite:////{parent_path}/database.db')
    os.environ["INVENIO_SQLALCHEMY_DATABASE_URI"] = db_path
    app.config.update(SQLALCHEMY_DATABASE_URI=db_path)
    if database_exists(str(db_.engine.url)):
        drop_database(db_.engine.url)
    if not database_exists(str(db_.engine.url)):
        create_database(db_.engine.url)
    db_.create_all()
    subprocess.run(["invenio", "taxonomies", "init", "--create-db"])
    yield db_

    # Explicitly close DB connection
    db_.session.close()
    db_.drop_all()
Esempio n. 54
0
def database(appctx):
    """Do Setup database.

    Scope: module

    Normally, tests should use the function-scoped :py:data:`db` fixture
    instead. This fixture takes care of creating the database/tables and
    removing the tables once tests are done.
    """
    from invenio_db import db as db_
    from sqlalchemy_utils.functions import create_database, database_exists
    if not database_exists(str(db_.engine.url)):
        create_database(str(db_.engine.url))
    db_.create_all()

    yield db_

    db_.session.remove()
    db_.drop_all()
Esempio n. 55
0
def clear_environment(app):
    from invenio_db import db as db_
    from sqlalchemy_utils.functions import create_database, database_exists

    with app.app_context():

        db_.session.remove()
        db_.drop_all()
        if not database_exists(str(db_.engine.url)):
            create_database(str(db_.engine.url))
        db_.create_all()

        _es = app.extensions["invenio-search"]
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))
        init_default_storage_path()
        init_records_files_storage_path()
        es.indices.refresh("records-hep")
        es.indices.refresh("records-authors")
Esempio n. 56
0
def test_alembic(clean_app):
    """Test alembic recipes upgrade and downgrade."""
    with clean_app.app_context():
        ext = clean_app.extensions['invenio-db']
        if db.engine.name == 'sqlite':
            raise pytest.skip('Upgrades are not supported on SQLite.')
        db.drop_all()
        remove_alembic_version_table()

        ext.alembic.upgrade()

        # downgrade to root revision which is in invenio-db
        ext.alembic.downgrade(target='96e796392533')

        insp = Inspector.from_engine(db.engine)
        remaining_table = insp.get_table_names()
        assert remaining_table == ['alembic_version']
        remove_alembic_version_table()
        insp = Inspector.from_engine(db.engine)
        assert not insp.get_table_names()
Esempio n. 57
0
def test_alembic(clean_app):
    """Test alembic recipes upgrade and downgrade."""
    with clean_app.app_context():
        ext = clean_app.extensions['invenio-db']
        if db.engine.name == 'sqlite':
            raise pytest.skip('Upgrades are not supported on SQLite.')
        db.drop_all()
        remove_alembic_version_table()

        ext.alembic.upgrade()

        # downgrade to root revision which is in invenio-db
        ext.alembic.downgrade(target='96e796392533')

        insp = Inspector.from_engine(db.engine)
        remaining_table = insp.get_table_names()
        assert remaining_table == ['alembic_version']
        remove_alembic_version_table()
        insp = Inspector.from_engine(db.engine)
        assert not insp.get_table_names()
Esempio n. 58
0
def app():
    """Flask application fixture."""
    app = create_app(
        DEBUG=True,
        WTF_CSRF_ENABLED=False,
        CELERY_ALWAYS_EAGER=True,
        CELERY_RESULT_BACKEND='cache',
        CELERY_CACHE_BACKEND='memory',
        CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
        SECRET_KEY='secret!',
        TESTING=True,
    )

    with app.app_context():
        # Imports must be local, otherwise tasks default to pickle serializer.
        from inspirehep.modules.migrator.tasks import add_citation_counts, migrate
        from inspirehep.modules.fixtures.collections import init_collections
        from inspirehep.modules.fixtures.files import init_all_storage_paths
        from inspirehep.modules.fixtures.users import init_users_and_permissions

        db.drop_all()
        db.create_all()

        _es = app.extensions['invenio-search']
        list(_es.delete(ignore=[404]))
        list(_es.create(ignore=[400]))

        init_all_storage_paths()
        init_users_and_permissions()
        init_collections()

        migrate('./inspirehep/demosite/data/demo-records.xml.gz',
                wait_for_results=True)
        es.indices.refresh(
            'records-hep')  # Makes sure that all HEP records were migrated.

        add_citation_counts()
        es.indices.refresh(
            'records-hep')  # Makes sure that all citation counts were added.

        yield app
Esempio n. 59
0
def test_alembic_revision_cb5153afd839(alembic_app):
    ext = alembic_app.extensions['invenio-db']

    if db.engine.name == 'sqlite':
        raise pytest.skip('Upgrades are not supported on SQLite.')

    db.drop_all()
    drop_alembic_version_table()

    inspector = inspect(db.engine)
    assert 'workflows_record_sources' not in inspector.get_table_names()

    ext.alembic.upgrade(target='cb5153afd839')
    inspector = inspect(db.engine)
    assert 'workflows_record_sources' in inspector.get_table_names()

    ext.alembic.downgrade(target='fddb3cfe7a9c')
    inspector = inspect(db.engine)
    assert 'workflows_record_sources' not in inspector.get_table_names()

    drop_alembic_version_table()
Esempio n. 60
0
def test_init():
    app = Flask('demo')
    app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get(
        'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db'
    )
    FlaskCLI(app)
    InvenioDB(app, entrypoint_name=False)

    class Demo(db.Model):
        __tablename__ = 'demo'
        pk = sa.Column(sa.Integer, primary_key=True)

    class Demo2(db.Model):
        __tablename__ = 'demo2'
        pk = sa.Column(sa.Integer, primary_key=True)

    with app.app_context():
        create_database(db.engine.url)
        db.create_all()
        assert len(db.metadata.tables) == 2
        db.drop_all()
        drop_database(db.engine.url)