| # 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. |
| from unittest.mock import call, patch |
| |
| from flask_appbuilder.security.sqla.models import User |
| |
| from superset.common.query_object import QueryObject |
| from superset.connectors.sqla.models import SqlaTable |
| from superset.models.core import Database |
| from superset.utils.core import override_user |
| |
| |
| def cache_impersonation_flag_side_effect(feature=None): |
| return feature == "CACHE_IMPERSONATION" |
| |
| |
| def cache_query_by_user_flag_side_effect(feature=None): |
| return feature == "CACHE_QUERY_BY_USER" |
| |
| |
| def test_default_query_object_to_dict(): |
| """ |
| Simple test to check default QueryObject values |
| """ |
| query_object = QueryObject(row_limit=1) |
| assert query_object.to_dict() == { |
| "apply_fetch_values_predicate": False, |
| "columns": [], |
| "extras": {}, |
| "filter": [], |
| "from_dttm": None, |
| "granularity": None, |
| "group_others_when_limit_reached": False, |
| "inner_from_dttm": None, |
| "inner_to_dttm": None, |
| "is_rowcount": False, |
| "is_timeseries": False, |
| "metrics": None, |
| "order_desc": True, |
| "orderby": [], |
| "row_limit": 1, |
| "row_offset": 0, |
| "series_columns": [], |
| "series_limit": 0, |
| "series_limit_metric": None, |
| "time_shift": None, |
| "to_dttm": None, |
| } |
| |
| |
| def test_cache_key_consistent_for_query_object(): |
| """ |
| When the same query is object is used, the |
| cache key will be the same |
| """ |
| query_object = QueryObject(row_limit=1) |
| cache_key = query_object.cache_key() |
| assert query_object.cache_key() == cache_key |
| |
| |
| def test_cache_key_changes_for_new_query_object_different_params(): |
| """ |
| When a new query object is created with different params, |
| the cache key will be different |
| """ |
| query_object1 = QueryObject(row_limit=1) |
| cache_key1 = query_object1.cache_key() |
| query_object2 = QueryObject(row_limit=2) |
| assert query_object2.cache_key() != cache_key1 |
| |
| |
| def test_cache_key_changes_for_new_query_object_same_params(): |
| """ |
| When a new query object is created with the same params, |
| the cache key will be the same |
| """ |
| query_object1 = QueryObject(row_limit=1) |
| cache_key1 = query_object1.cache_key() |
| query_object2 = QueryObject(row_limit=1) |
| assert query_object2.cache_key() == cache_key1 |
| |
| |
| @patch("superset.common.query_object.feature_flag_manager") |
| def test_cache_key_cache_query_by_user_on_no_datasource(feature_flag_mock): |
| """ |
| When CACHE_QUERY_BY_USER flag is on and there is no datasource, |
| cache key will be the same |
| """ |
| |
| def feature_flag_side_effect(feature=None): |
| if feature == "CACHE_QUERY_BY_USER": |
| return True |
| |
| feature_flag_mock.is_feature_enabled.side_effect = feature_flag_side_effect |
| query_object = QueryObject(row_limit=1) |
| cache_key = query_object.cache_key() |
| assert query_object.cache_key() == cache_key |
| |
| |
| @patch("superset.common.query_object.feature_flag_manager") |
| @patch("superset.common.query_object.logger") |
| def test_cache_key_cache_query_by_user_on_no_user(logger_mock, feature_flag_mock): |
| """ |
| When CACHE_QUERY_BY_USER flag is on and there is no user, |
| cache key will be the same |
| """ |
| |
| datasource = SqlaTable( |
| table_name="test_table", |
| columns=[], |
| metrics=[], |
| main_dttm_col=None, |
| database=Database(database_name="my_database", sqlalchemy_uri="sqlite://"), |
| ) |
| |
| feature_flag_mock.is_feature_enabled.side_effect = ( |
| cache_query_by_user_flag_side_effect |
| ) |
| query_object = QueryObject(row_limit=1, datasource=datasource) |
| cache_key = query_object.cache_key() |
| assert query_object.cache_key() == cache_key |
| logger_mock.debug.assert_not_called() |
| |
| |
| @patch("superset.common.query_object.feature_flag_manager") |
| @patch("superset.common.query_object.logger") |
| def test_cache_key_cache_query_by_user_on_with_user(logger_mock, feature_flag_mock): |
| """ |
| When the same user is requesting a cache key with CACHE_QUERY_BY_USER |
| flag on, the key will be the same |
| """ |
| |
| datasource = SqlaTable( |
| table_name="test_table", |
| columns=[], |
| metrics=[], |
| main_dttm_col=None, |
| database=Database(database_name="my_database", sqlalchemy_uri="sqlite://"), |
| ) |
| |
| feature_flag_mock.is_feature_enabled.side_effect = ( |
| cache_query_by_user_flag_side_effect |
| ) |
| query_object = QueryObject(row_limit=1, datasource=datasource) |
| |
| with override_user(User(username="test_user")): |
| cache_key1 = query_object.cache_key() |
| assert query_object.cache_key() == cache_key1 |
| |
| logger_mock.debug.assert_called_with( |
| "Adding impersonation key to QueryObject cache dict: %s", "test_user" |
| ) |
| |
| |
| @patch("superset.common.query_object.feature_flag_manager") |
| @patch("superset.common.query_object.logger") |
| def test_cache_key_cache_query_by_user_on_with_different_user( |
| logger_mock, feature_flag_mock |
| ): |
| """ |
| When two different users are requesting a cache key with CACHE_QUERY_BY_USER |
| flag on, the key will be different |
| """ |
| |
| datasource = SqlaTable( |
| table_name="test_table", |
| columns=[], |
| metrics=[], |
| main_dttm_col=None, |
| database=Database(database_name="my_database", sqlalchemy_uri="sqlite://"), |
| ) |
| |
| feature_flag_mock.is_feature_enabled.side_effect = ( |
| cache_query_by_user_flag_side_effect |
| ) |
| query_object = QueryObject(row_limit=1, datasource=datasource) |
| |
| with override_user(User(username="test_user1")): |
| cache_key1 = query_object.cache_key() |
| |
| with override_user(User(username="test_user2")): |
| cache_key2 = query_object.cache_key() |
| |
| assert cache_key1 != cache_key2 |
| |
| logger_mock.debug.assert_has_calls( |
| [ |
| call( |
| "Adding impersonation key to QueryObject cache dict: %s", "test_user1" |
| ), |
| call( |
| "Adding impersonation key to QueryObject cache dict: %s", "test_user2" |
| ), |
| ] |
| ) |
| |
| |
| @patch("superset.common.query_object.feature_flag_manager") |
| @patch("superset.common.query_object.logger") |
| def test_cache_key_cache_impersonation_on_no_user(logger_mock, feature_flag_mock): |
| """ |
| When CACHE_IMPERSONATION flag is on and there is no user, |
| cache key will be the same |
| """ |
| |
| datasource = SqlaTable( |
| table_name="test_table", |
| columns=[], |
| metrics=[], |
| main_dttm_col=None, |
| database=Database(database_name="my_database", sqlalchemy_uri="sqlite://"), |
| ) |
| |
| feature_flag_mock.is_feature_enabled.side_effect = ( |
| cache_impersonation_flag_side_effect |
| ) |
| query_object = QueryObject(row_limit=1, datasource=datasource) |
| cache_key = query_object.cache_key() |
| assert query_object.cache_key() == cache_key |
| logger_mock.debug.assert_not_called() |
| |
| |
| @patch("superset.common.query_object.feature_flag_manager") |
| @patch("superset.common.query_object.logger") |
| def test_cache_key_cache_impersonation_on_with_user(logger_mock, feature_flag_mock): |
| """ |
| When the same user is requesting a cache key with CACHE_IMPERSONATION |
| flag on, the key will be the same |
| """ |
| |
| datasource = SqlaTable( |
| table_name="test_table", |
| columns=[], |
| metrics=[], |
| main_dttm_col=None, |
| database=Database(database_name="my_database", sqlalchemy_uri="sqlite://"), |
| ) |
| |
| feature_flag_mock.is_feature_enabled.side_effect = ( |
| cache_impersonation_flag_side_effect |
| ) |
| query_object = QueryObject(row_limit=1, datasource=datasource) |
| |
| with override_user(User(username="test_user")): |
| cache_key1 = query_object.cache_key() |
| assert query_object.cache_key() == cache_key1 |
| |
| logger_mock.debug.assert_not_called() |
| |
| |
| @patch("superset.common.query_object.feature_flag_manager") |
| @patch("superset.common.query_object.logger") |
| def test_cache_key_cache_impersonation_on_with_different_user( |
| logger_mock, feature_flag_mock |
| ): |
| """ |
| When two different users are requesting a cache key with CACHE_IMPERSONATION |
| flag on, but the cache_impersonation is not enabled on the database, |
| the keys will be the same |
| """ |
| |
| datasource = SqlaTable( |
| table_name="test_table", |
| columns=[], |
| metrics=[], |
| main_dttm_col=None, |
| database=Database(database_name="my_database", sqlalchemy_uri="sqlite://"), |
| ) |
| |
| feature_flag_mock.is_feature_enabled.side_effect = ( |
| cache_impersonation_flag_side_effect |
| ) |
| query_object = QueryObject(row_limit=1, datasource=datasource) |
| |
| with override_user(User(username="test_user1")): |
| cache_key1 = query_object.cache_key() |
| |
| with override_user(User(username="test_user2")): |
| cache_key2 = query_object.cache_key() |
| |
| assert cache_key1 == cache_key2 |
| |
| logger_mock.debug.assert_not_called() |
| |
| |
| @patch("superset.common.query_object.feature_flag_manager") |
| @patch("superset.common.query_object.logger") |
| def test_cache_key_cache_impersonation_on_with_different_user_and_db_impersonation( |
| logger_mock, feature_flag_mock |
| ): |
| """ |
| When two different users are requesting a cache key with CACHE_IMPERSONATION |
| flag on, and cache_impersonation is enabled on the database, |
| the keys will be different |
| """ |
| |
| datasource = SqlaTable( |
| table_name="test_table", |
| columns=[], |
| metrics=[], |
| main_dttm_col=None, |
| database=Database( |
| database_name="my_database", |
| sqlalchemy_uri="sqlite://", |
| impersonate_user=True, |
| ), |
| ) |
| |
| feature_flag_mock.is_feature_enabled.side_effect = ( |
| cache_impersonation_flag_side_effect |
| ) |
| query_object = QueryObject(row_limit=1, datasource=datasource) |
| |
| with override_user(User(username="test_user1")): |
| cache_key1 = query_object.cache_key() |
| |
| with override_user(User(username="test_user2")): |
| cache_key2 = query_object.cache_key() |
| |
| assert cache_key1 != cache_key2 |
| |
| logger_mock.debug.assert_has_calls( |
| [ |
| call( |
| "Adding impersonation key to QueryObject cache dict: %s", "test_user1" |
| ), |
| call( |
| "Adding impersonation key to QueryObject cache dict: %s", "test_user2" |
| ), |
| ] |
| ) |