| # 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. |
| """ |
| Behavioral tests for ``viz.BaseViz.cache_key`` covering per-user cache-key |
| inclusion. |
| """ |
| |
| from typing import Any |
| from unittest.mock import patch |
| |
| from flask_appbuilder.security.sqla.models import User |
| |
| from superset import viz |
| from superset.connectors.sqla.models import SqlaTable |
| from superset.models.core import Database |
| from superset.utils.core import override_user |
| |
| QUERY_OBJ: dict[str, Any] = {"row_limit": 100, "from_dttm": None, "to_dttm": None} |
| |
| |
| def _viz_for(database: Database) -> viz.BaseViz: |
| datasource = SqlaTable( |
| table_name="t", |
| columns=[], |
| metrics=[], |
| main_dttm_col=None, |
| database=database, |
| ) |
| return viz.BaseViz(datasource=datasource, form_data={"viz_type": "table"}) |
| |
| |
| def test_no_per_user_opt_in_keys_match_across_users(): |
| """ |
| Without any per-user caching opt-in, two different users on the same |
| database/query must produce the *same* cache key (regression guard — we |
| must not accidentally make every cache key per-user). |
| """ |
| database = Database(database_name="d", sqlalchemy_uri="sqlite://") |
| obj = _viz_for(database) |
| |
| with override_user(User(username="alice")): |
| key_a = obj.cache_key(QUERY_OBJ) |
| with override_user(User(username="bob")): |
| key_b = obj.cache_key(QUERY_OBJ) |
| |
| assert key_a == key_b |
| |
| |
| def test_per_user_caching_in_extra_yields_distinct_keys_per_user(): |
| """ |
| With ``per_user_caching: true`` set on the database, two different users |
| must produce *different* cache keys for the same query. |
| """ |
| database = Database( |
| database_name="d", |
| sqlalchemy_uri="sqlite://", |
| extra='{"per_user_caching": true}', |
| ) |
| obj = _viz_for(database) |
| |
| with override_user(User(username="alice")): |
| key_a = obj.cache_key(QUERY_OBJ) |
| with override_user(User(username="bob")): |
| key_b = obj.cache_key(QUERY_OBJ) |
| |
| assert key_a != key_b |
| |
| |
| def test_same_user_same_query_idempotent(): |
| database = Database( |
| database_name="d", |
| sqlalchemy_uri="sqlite://", |
| extra='{"per_user_caching": true}', |
| ) |
| obj = _viz_for(database) |
| |
| with override_user(User(username="alice")): |
| assert obj.cache_key(QUERY_OBJ) == obj.cache_key(QUERY_OBJ) |
| |
| |
| @patch("superset.utils.cache_keys.feature_flag_manager") |
| def test_cache_query_by_user_flag_yields_distinct_keys(feature_flag_mock): |
| """ |
| Global ``CACHE_QUERY_BY_USER`` flag also reaches the legacy viz path. |
| """ |
| feature_flag_mock.is_feature_enabled.side_effect = ( |
| lambda feature=None: feature == "CACHE_QUERY_BY_USER" |
| ) |
| database = Database(database_name="d", sqlalchemy_uri="sqlite://") |
| obj = _viz_for(database) |
| |
| with override_user(User(username="alice")): |
| key_a = obj.cache_key(QUERY_OBJ) |
| with override_user(User(username="bob")): |
| key_b = obj.cache_key(QUERY_OBJ) |
| |
| assert key_a != key_b |