| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # pylint: disable=redefined-outer-name, import-outside-toplevel |
| import functools |
| import importlib |
| import os |
| import unittest.mock |
| from collections.abc import Iterator |
| from typing import Any, Callable, Union |
| from unittest.mock import patch |
| |
| import pytest |
| from _pytest.fixtures import SubRequest |
| from pytest_mock import MockerFixture |
| from sqlalchemy import create_engine |
| from sqlalchemy.orm import sessionmaker |
| from sqlalchemy.orm.session import Session |
| |
| from superset import security_manager |
| from superset.app import SupersetApp |
| from superset.common.chart_data import ChartDataResultType |
| from superset.common.query_object_factory import QueryObjectFactory |
| from superset.extensions import appbuilder, feature_flag_manager |
| from superset.initialization import SupersetAppInitializer |
| |
| |
| @pytest.fixture |
| def get_session(mocker: MockerFixture) -> Callable[[], Session]: |
| """ |
| Create an in-memory SQLite db.session.to test models. |
| """ |
| engine = create_engine("sqlite://") |
| |
| def get_session(): |
| Session_ = sessionmaker(bind=engine) # pylint: disable=invalid-name # noqa: N806 |
| in_memory_session = Session_() |
| |
| # flask calls db.session.remove() |
| in_memory_session.remove = lambda: None |
| |
| # patch session |
| get_session = mocker.patch( |
| "superset.security.SupersetSecurityManager.session", |
| ) |
| get_session.return_value = in_memory_session |
| # FAB calls get_session.get_bind() to get a handler to the engine |
| get_session.get_bind.return_value = engine |
| # Allow for queries on security manager |
| get_session.query = in_memory_session.query |
| |
| mocker.patch("superset.db.session", in_memory_session) |
| return in_memory_session |
| |
| return get_session |
| |
| |
| @pytest.fixture |
| def session(get_session) -> Iterator[Session]: |
| return get_session() |
| |
| |
| @pytest.fixture(scope="module") |
| def app(request: SubRequest) -> Iterator[SupersetApp]: |
| """ |
| A fixture that generates a Superset app. |
| """ |
| app = SupersetApp(__name__) |
| |
| app.config.from_object("superset.config") |
| app.config["SQLALCHEMY_DATABASE_URI"] = ( |
| os.environ.get("SUPERSET__SQLALCHEMY_DATABASE_URI") or "sqlite://" |
| ) |
| app.config["WTF_CSRF_ENABLED"] = False |
| app.config["PREVENT_UNSAFE_DB_CONNECTIONS"] = False |
| app.config["TESTING"] = True |
| app.config["RATELIMIT_ENABLED"] = False |
| app.config["CACHE_CONFIG"] = {} |
| app.config["DATA_CACHE_CONFIG"] = {} |
| app.config["SERVER_NAME"] = "example.com" |
| app.config["APPLICATION_ROOT"] = "/" |
| app.config["PREFERRED_URL_SCHEME="] = "http" |
| |
| # loop over extra configs passed in by tests |
| # and update the app config |
| # to override the default configs use: |
| # |
| # @pytest.mark.parametrize( |
| # "app", |
| # [{"SOME_CONFIG": "SOME_VALUE"}], |
| # indirect=True, |
| # ) |
| # def test_some_test(app_context: None) -> None: |
| if request and hasattr(request, "param"): |
| for key, val in request.param.items(): |
| app.config[key] = val |
| |
| # ``superset.extensions.appbuilder`` is a singleton, and won't rebuild the |
| # routes when this fixture is called multiple times; we need to clear the |
| # registered views to ensure the initialization can happen more than once. |
| appbuilder.baseviews = [] |
| |
| app_initializer = SupersetAppInitializer(app) |
| app_initializer.init_app() |
| |
| # reload base views to ensure error handlers are applied to the app |
| with app.app_context(): |
| import superset.views.base |
| |
| importlib.reload(superset.views.base) |
| |
| return app |
| |
| |
| @pytest.fixture |
| def client(app: SupersetApp) -> Any: |
| with app.test_client() as client: |
| yield client |
| |
| |
| @pytest.fixture(autouse=True) |
| def app_context(app: SupersetApp) -> Iterator[None]: |
| """ |
| A fixture that yields and application context. |
| """ |
| with app.app_context(): |
| yield |
| |
| |
| @pytest.fixture |
| def full_api_access(mocker: MockerFixture) -> Union[Iterator[None], None]: |
| """ |
| Allow full access to the API. |
| |
| TODO (betodealmeida): we should replace this with user-fixtures, eg, ``admin`` or |
| ``gamma``, so that we have granular access to the APIs. |
| """ |
| mocker.patch( |
| "flask_appbuilder.security.decorators.verify_jwt_in_request", |
| return_value=True, |
| ) |
| mocker.patch.object(security_manager, "is_item_public", return_value=True) |
| mocker.patch.object(security_manager, "has_access", return_value=True) |
| mocker.patch.object(security_manager, "can_access_all_databases", return_value=True) |
| |
| return None |
| |
| |
| @pytest.fixture |
| def dummy_query_object(request, app_context): |
| query_obj_marker = request.node.get_closest_marker("query_object") |
| result_type_marker = request.node.get_closest_marker("result_type") |
| |
| if query_obj_marker is None: |
| query_object = {} |
| else: |
| query_object = query_obj_marker.args[0] |
| |
| if result_type_marker is None: |
| result_type = ChartDataResultType.FULL |
| else: |
| result_type = result_type_marker.args[0] |
| |
| return QueryObjectFactory( |
| app_configurations={ |
| "ROW_LIMIT": 100, |
| }, |
| _datasource_dao=unittest.mock.Mock(), |
| ).create(parent_result_type=result_type, **query_object) |
| |
| |
| def with_feature_flags(**mock_feature_flags): |
| """ |
| Use this decorator to mock feature flags in tests.integration_tests. |
| |
| Usage: |
| |
| class TestYourFeature(SupersetTestCase): |
| |
| @with_feature_flags(YOUR_FEATURE=True) |
| def test_your_feature_enabled(self): |
| self.assertEqual(is_feature_enabled("YOUR_FEATURE"), True) |
| |
| @with_feature_flags(YOUR_FEATURE=False) |
| def test_your_feature_disabled(self): |
| self.assertEqual(is_feature_enabled("YOUR_FEATURE"), False) |
| """ |
| |
| def mock_get_feature_flags(): |
| feature_flags = feature_flag_manager._feature_flags or {} |
| return {**feature_flags, **mock_feature_flags} |
| |
| def decorate(test_fn): |
| def wrapper(*args, **kwargs): |
| with patch.object( |
| feature_flag_manager, |
| "get_feature_flags", |
| side_effect=mock_get_feature_flags, |
| ): |
| test_fn(*args, **kwargs) |
| |
| return functools.update_wrapper(wrapper, test_fn) |
| |
| return decorate |