Files
superset2/tests/unit_tests/tasks/test_cache.py
2026-07-07 17:08:40 -07:00

401 lines
15 KiB
Python

# 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 typing import Any, Optional
from unittest import mock
def _fake_app(config: Optional[dict[str, Any]] = None) -> mock.MagicMock:
"""Build a stand-in for ``current_app`` with a controllable config dict."""
base: dict[str, Any] = {"WEBDRIVER_TYPE": "chrome"}
if config:
base.update(config)
app: mock.MagicMock = mock.MagicMock()
app.config = base
return app
def test_cache_warmup_unknown_strategy(app_context: None) -> None:
"""An unknown strategy name returns an explanatory message and warms nothing."""
from superset.tasks.cache import cache_warmup
with mock.patch("superset.tasks.cache.WebDriverSelenium") as mock_wd:
result: dict[str, list[str]] | str = cache_warmup("does_not_exist")
assert result == "No strategy does_not_exist found!"
mock_wd.assert_not_called()
def test_cache_warmup_missing_config(app_context: None) -> None:
"""Without SUPERSET_CACHE_WARMUP_USER the task fails fast before any WebDriver."""
from superset.tasks.cache import cache_warmup
with (
mock.patch(
"superset.tasks.cache.current_app",
_fake_app({"SUPERSET_CACHE_WARMUP_USER": None}),
),
mock.patch("superset.tasks.cache.WebDriverSelenium") as mock_wd,
):
result: dict[str, list[str]] | str = cache_warmup("dummy")
assert isinstance(result, str)
assert "SUPERSET_CACHE_WARMUP_USER is not configured" in result
mock_wd.assert_not_called()
def test_cache_warmup_user_not_found(app_context: None) -> None:
"""A configured-but-missing warmup user fails fast before any WebDriver."""
from superset.tasks.cache import cache_warmup
with (
mock.patch(
"superset.tasks.cache.current_app",
_fake_app({"SUPERSET_CACHE_WARMUP_USER": "bot"}),
),
mock.patch("superset.tasks.cache.security_manager") as mock_sm,
mock.patch("superset.tasks.cache.WebDriverSelenium") as mock_wd,
):
mock_sm.find_user = mock.MagicMock(return_value=None)
result: dict[str, list[str]] | str = cache_warmup("dummy")
assert isinstance(result, str)
assert "not found" in result
mock_sm.find_user.assert_called_once_with(username="bot")
mock_wd.assert_not_called()
def test_cache_warmup_happy_path(app_context: None) -> None:
"""Each URL is screenshotted as the configured user and the driver is destroyed."""
from superset.tasks.cache import cache_warmup
urls: list[str] = ["http://localhost/dash/1", "http://localhost/dash/2"]
user: mock.MagicMock = mock.MagicMock()
with (
mock.patch(
"superset.tasks.cache.current_app",
_fake_app({"SUPERSET_CACHE_WARMUP_USER": "bot"}),
),
mock.patch("superset.tasks.cache.security_manager") as mock_sm,
mock.patch("superset.tasks.cache.WebDriverSelenium") as mock_wd,
mock.patch("superset.tasks.cache.DummyStrategy.get_urls", return_value=urls),
):
mock_sm.find_user = mock.MagicMock(return_value=user)
driver: mock.MagicMock = mock_wd.return_value
result: dict[str, list[str]] | str = cache_warmup("dummy")
assert result == {"success": urls, "errors": []}
mock_wd.assert_called_once_with("chrome", user=user)
assert driver.get_screenshot.call_count == len(urls)
driver.destroy.assert_called_once_with()
def test_cache_warmup_collects_errors_and_destroys(app_context: None) -> None:
"""A failing URL is recorded as an error and cleanup still runs in finally."""
from superset.tasks.cache import cache_warmup
urls: list[str] = ["http://localhost/dash/ok", "http://localhost/dash/boom"]
user: mock.MagicMock = mock.MagicMock()
def side_effect(url: str, _element: str) -> None:
if url.endswith("boom"):
raise Exception("screenshot failed")
with (
mock.patch(
"superset.tasks.cache.current_app",
_fake_app({"SUPERSET_CACHE_WARMUP_USER": "bot"}),
),
mock.patch("superset.tasks.cache.security_manager") as mock_sm,
mock.patch("superset.tasks.cache.WebDriverSelenium") as mock_wd,
mock.patch("superset.tasks.cache.DummyStrategy.get_urls", return_value=urls),
):
mock_sm.find_user = mock.MagicMock(return_value=user)
driver: mock.MagicMock = mock_wd.return_value
driver.get_screenshot.side_effect = side_effect
result: dict[str, list[str]] | str = cache_warmup("dummy")
assert result == {
"success": ["http://localhost/dash/ok"],
"errors": ["http://localhost/dash/boom"],
}
driver.destroy.assert_called_once_with()
def test_native_filter_options_strategy_returns_tasks_for_eligible_filters() -> None:
"""Eligible native filters are converted into cache warm-up tasks."""
from superset.tasks.cache import NativeFilterOptionsStrategy
dashboard: mock.MagicMock = mock.MagicMock()
dashboard.id = 10
filter_configs: list[dict[str, Any]] = [{"id": "filter-1"}, {"id": "filter-2"}]
form_data: list[dict[str, Any]] = [
{"groupby": ["country"]},
{"groupby": ["state"]},
]
query_contexts: list[mock.MagicMock] = [mock.MagicMock(), mock.MagicMock()]
with (
mock.patch(
"superset.tasks.cache.DashboardDAO.find_by_id",
return_value=dashboard,
),
mock.patch(
"superset.tasks.cache.get_eligible_native_filters",
return_value=filter_configs,
),
mock.patch(
"superset.tasks.cache.build_native_filter_option_form_data",
side_effect=form_data,
) as mock_build_form_data,
mock.patch(
"superset.tasks.cache.build_native_filter_option_query_context",
side_effect=query_contexts,
) as mock_build_query_context,
):
tasks: list[Any] = NativeFilterOptionsStrategy(dashboard_ids=[10]).get_tasks()
assert len(tasks) == 2
assert [task.query_context for task in tasks] == query_contexts
assert [task.dashboard_id for task in tasks] == [10, 10]
assert [task.native_filter_id for task in tasks] == ["filter-1", "filter-2"]
assert mock_build_form_data.call_count == 2
assert mock_build_query_context.call_count == 2
def test_native_filter_options_strategy_skips_missing_dashboard() -> None:
"""Missing dashboards are skipped without raising."""
from superset.tasks.cache import NativeFilterOptionsStrategy
with (
mock.patch(
"superset.tasks.cache.DashboardDAO.find_by_id",
return_value=None,
),
mock.patch("superset.tasks.cache.get_eligible_native_filters") as mock_filters,
):
tasks: list[Any] = NativeFilterOptionsStrategy(dashboard_ids=[10]).get_tasks()
assert tasks == []
mock_filters.assert_not_called()
def test_native_filter_options_strategy_skips_when_form_data_is_none() -> None:
"""Filters without form data are skipped without raising."""
from superset.tasks.cache import NativeFilterOptionsStrategy
dashboard: mock.MagicMock = mock.MagicMock()
dashboard.id = 10
filter_configs: list[dict[str, Any]] = [{"id": "filter-1"}, {"id": "filter-2"}]
query_context: mock.MagicMock = mock.MagicMock()
with (
mock.patch(
"superset.tasks.cache.DashboardDAO.find_by_id",
return_value=dashboard,
),
mock.patch(
"superset.tasks.cache.get_eligible_native_filters",
return_value=filter_configs,
),
mock.patch(
"superset.tasks.cache.build_native_filter_option_form_data",
side_effect=[None, {"groupby": ["country"]}],
),
mock.patch(
"superset.tasks.cache.build_native_filter_option_query_context",
return_value=query_context,
) as mock_build_query_context,
):
tasks: list[Any] = NativeFilterOptionsStrategy(dashboard_ids=[10]).get_tasks()
assert len(tasks) == 1
assert tasks[0].query_context == query_context
mock_build_query_context.assert_called_once_with({"groupby": ["country"]})
def test_native_filter_options_strategy_skips_when_query_context_is_none() -> None:
"""Filters without a query context are skipped without raising."""
from superset.tasks.cache import NativeFilterOptionsStrategy
dashboard: mock.MagicMock = mock.MagicMock()
dashboard.id = 10
with (
mock.patch(
"superset.tasks.cache.DashboardDAO.find_by_id",
return_value=dashboard,
),
mock.patch(
"superset.tasks.cache.get_eligible_native_filters",
return_value=[{"id": "filter-1"}],
),
mock.patch(
"superset.tasks.cache.build_native_filter_option_form_data",
return_value={"groupby": ["country"]},
),
mock.patch(
"superset.tasks.cache.build_native_filter_option_query_context",
return_value=None,
),
):
tasks: list[Any] = NativeFilterOptionsStrategy(dashboard_ids=[10]).get_tasks()
assert tasks == []
def test_native_filter_options_strategy_uses_real_filter_helpers() -> None:
"""Eligible filter config is handled by real helper functions."""
from superset.tasks.cache import NativeFilterOptionsStrategy
from superset.utils import json
dashboard: mock.MagicMock = mock.MagicMock()
dashboard.id = 10
dashboard.json_metadata = json.dumps(
{
"native_filter_configuration": [
"bad-entry",
{
"id": "filter-1",
"filterType": "filter_select",
"type": "NATIVE_FILTER",
"targets": [{"datasetId": 7, "column": {"name": "country"}}],
"adhoc_filters": [
{
"expressionType": "SIMPLE",
"subject": "region",
"operator": "==",
"comparator": "EMEA",
}
],
},
{
"id": "range-filter",
"filterType": "filter_range",
"type": "NATIVE_FILTER",
"targets": [{"datasetId": 7, "column": {"name": "value"}}],
},
]
}
)
query_context: mock.MagicMock = mock.MagicMock()
with (
mock.patch(
"superset.tasks.cache.DashboardDAO.find_by_id",
return_value=dashboard,
),
mock.patch(
"superset.tasks.cache.build_native_filter_option_query_context",
return_value=query_context,
) as mock_build_query_context,
):
tasks: list[Any] = NativeFilterOptionsStrategy(dashboard_ids=[10]).get_tasks()
assert len(tasks) == 1
assert tasks[0].dashboard_id == 10
assert tasks[0].native_filter_id == "filter-1"
assert tasks[0].query_context == query_context
mock_build_query_context.assert_called_once()
form_data: dict[str, Any] = mock_build_query_context.call_args.args[0]
assert form_data["datasource"] == "7__table"
assert form_data["groupby"] == ["country"]
assert form_data["adhoc_filters"] == [
{
"expressionType": "SIMPLE",
"subject": "region",
"operator": "==",
"comparator": "EMEA",
}
]
def test_native_filter_options_strategy_skips_filter_helper_exceptions() -> None:
"""A failing filter config is skipped while remaining filters are processed."""
from superset.tasks.cache import NativeFilterOptionsStrategy
dashboard: mock.MagicMock = mock.MagicMock()
dashboard.id = 10
filter_configs: list[dict[str, Any]] = [{"missing_id": True}, {"id": "filter-2"}]
query_context: mock.MagicMock = mock.MagicMock()
with (
mock.patch(
"superset.tasks.cache.DashboardDAO.find_by_id",
return_value=dashboard,
),
mock.patch(
"superset.tasks.cache.get_eligible_native_filters",
return_value=filter_configs,
),
mock.patch(
"superset.tasks.cache.build_native_filter_option_form_data",
side_effect=[RuntimeError("boom"), {"groupby": ["country"]}],
),
mock.patch(
"superset.tasks.cache.build_native_filter_option_query_context",
return_value=query_context,
),
):
tasks: list[Any] = NativeFilterOptionsStrategy(dashboard_ids=[10]).get_tasks()
assert len(tasks) == 1
assert tasks[0].native_filter_id == "filter-2"
assert tasks[0].query_context == query_context
def test_native_filter_options_strategy_skips_dashboard_lookup_exceptions() -> None:
"""A dashboard-level failure is caught and does not stop other dashboard ids."""
from superset.tasks.cache import NativeFilterOptionsStrategy
dashboard: mock.MagicMock = mock.MagicMock()
dashboard.id = 11
query_context: mock.MagicMock = mock.MagicMock()
with (
mock.patch(
"superset.tasks.cache.DashboardDAO.find_by_id",
side_effect=[RuntimeError("boom"), dashboard],
),
mock.patch(
"superset.tasks.cache.get_eligible_native_filters",
return_value=[{"id": "filter-1"}],
),
mock.patch(
"superset.tasks.cache.build_native_filter_option_form_data",
return_value={"groupby": ["country"]},
),
mock.patch(
"superset.tasks.cache.build_native_filter_option_query_context",
return_value=query_context,
),
):
tasks: list[Any] = NativeFilterOptionsStrategy(
dashboard_ids=[10, 11]
).get_tasks()
assert len(tasks) == 1
assert tasks[0].dashboard_id == 11
assert tasks[0].native_filter_id == "filter-1"
def test_native_filter_options_strategy_registered() -> None:
"""The native filter options strategy is registered by name."""
from superset.tasks.cache import NativeFilterOptionsStrategy, strategy_registry
assert strategy_registry["native_filter_options"] is NativeFilterOptionsStrategy