Files
superset2/tests/unit_tests/commands/dashboard/export_example_test.py

324 lines
11 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 __future__ import annotations
from unittest.mock import MagicMock, patch
from uuid import uuid4
import pytest
import yaml
from superset.commands.dashboard.exceptions import DashboardNotFoundError
from superset.commands.dashboard.export_example import (
_make_bytes_generator,
_make_yaml_generator,
export_chart,
export_dataset_yaml,
ExportExampleCommand,
sanitize_filename,
)
def test_sanitize_filename_basic():
"""Test basic filename sanitization."""
assert sanitize_filename("my_dashboard") == "my_dashboard"
assert sanitize_filename("My Dashboard") == "My_Dashboard"
assert sanitize_filename("test-name") == "test-name"
def test_sanitize_filename_special_chars():
"""Test sanitization of special characters."""
assert sanitize_filename("test/name") == "test_name"
assert sanitize_filename("test:name") == "test_name"
assert sanitize_filename("test<>name") == "test_name"
def test_sanitize_filename_collapses_underscores():
"""Test that multiple underscores are collapsed."""
assert sanitize_filename("test___name") == "test_name"
assert sanitize_filename("a b c") == "a_b_c"
def test_make_yaml_generator():
"""Test YAML generator function."""
config = {"key": "value", "number": 42}
generator = _make_yaml_generator(config)
result = generator()
assert isinstance(result, bytes)
parsed = yaml.safe_load(result.decode("utf-8"))
assert parsed == config
def test_make_bytes_generator():
"""Test bytes generator function."""
data = b"test binary data"
generator = _make_bytes_generator(data)
result = generator()
assert result == data
def test_export_dataset_yaml():
"""Test dataset YAML export."""
# Create mock dataset
mock_dataset = MagicMock()
mock_dataset.table_name = "test_table"
mock_dataset.main_dttm_col = "created_at"
mock_dataset.description = "Test description"
mock_dataset.default_endpoint = None
mock_dataset.offset = 0
mock_dataset.cache_timeout = None
mock_dataset.catalog = None
mock_dataset.sql = None
mock_dataset.template_params = None
mock_dataset.filter_select_enabled = True
mock_dataset.fetch_values_predicate = None
mock_dataset.extra = None
mock_dataset.normalize_columns = False
mock_dataset.always_filter_main_dttm = False
mock_dataset.uuid = uuid4()
mock_dataset.metrics = []
mock_dataset.columns = []
result = export_dataset_yaml(mock_dataset)
assert result["table_name"] == "test_table"
assert result["main_dttm_col"] == "created_at"
assert result["description"] == "Test description"
assert result["uuid"] == str(mock_dataset.uuid)
assert result["version"] == "1.0.0"
# Schema should be None (use target database default)
assert result["schema"] is None
def test_export_dataset_yaml_with_metrics():
"""Test dataset YAML export includes metrics."""
mock_metric = MagicMock()
mock_metric.metric_name = "count"
mock_metric.verbose_name = "Count"
mock_metric.metric_type = "count"
mock_metric.expression = "COUNT(*)"
mock_metric.description = "Row count"
mock_metric.d3format = None
mock_metric.currency = None
mock_metric.extra = None
mock_metric.warning_text = None
mock_dataset = MagicMock()
mock_dataset.table_name = "test_table"
mock_dataset.main_dttm_col = None
mock_dataset.description = None
mock_dataset.default_endpoint = None
mock_dataset.offset = 0
mock_dataset.cache_timeout = None
mock_dataset.catalog = None
mock_dataset.sql = None
mock_dataset.template_params = None
mock_dataset.filter_select_enabled = True
mock_dataset.fetch_values_predicate = None
mock_dataset.extra = None
mock_dataset.normalize_columns = False
mock_dataset.always_filter_main_dttm = False
mock_dataset.uuid = uuid4()
mock_dataset.metrics = [mock_metric]
mock_dataset.columns = []
result = export_dataset_yaml(mock_dataset)
assert len(result["metrics"]) == 1
assert result["metrics"][0]["metric_name"] == "count"
assert result["metrics"][0]["expression"] == "COUNT(*)"
def test_export_chart():
"""Test chart YAML export."""
mock_chart = MagicMock()
mock_chart.slice_name = "Test Chart"
mock_chart.description = "A test chart"
mock_chart.certified_by = None
mock_chart.certification_details = None
mock_chart.viz_type = "table"
mock_chart.params_dict = {"groupby": ["col1"]}
mock_chart.cache_timeout = None
mock_chart.uuid = uuid4()
dataset_uuid = str(uuid4())
result = export_chart(mock_chart, dataset_uuid)
assert result["slice_name"] == "Test Chart"
assert result["description"] == "A test chart"
assert result["viz_type"] == "table"
assert result["params"] == {"groupby": ["col1"]}
assert result["uuid"] == str(mock_chart.uuid)
assert result["dataset_uuid"] == dataset_uuid
assert result["version"] == "1.0.0"
# query_context should be None (contains stale IDs)
assert result["query_context"] is None
def test_export_example_command_not_found():
"""Test ExportExampleCommand raises error for non-existent dashboard."""
with patch("superset.commands.dashboard.export_example.DashboardDAO") as mock_dao:
mock_dao.find_by_id.return_value = None
command = ExportExampleCommand(dashboard_id=9999)
with pytest.raises(DashboardNotFoundError):
list(command.run())
def test_export_example_command_single_dataset():
"""Test ExportExampleCommand with single dataset dashboard."""
# Create mock objects
mock_chart = MagicMock()
mock_chart.id = 1
mock_chart.uuid = uuid4()
mock_chart.slice_name = "Test Chart"
mock_chart.description = None
mock_chart.certified_by = None
mock_chart.certification_details = None
mock_chart.viz_type = "table"
mock_chart.params_dict = {}
mock_chart.cache_timeout = None
mock_dataset = MagicMock()
mock_dataset.id = 1
mock_dataset.uuid = uuid4()
mock_dataset.table_name = "test_table"
mock_dataset.main_dttm_col = None
mock_dataset.description = None
mock_dataset.default_endpoint = None
mock_dataset.offset = 0
mock_dataset.cache_timeout = None
mock_dataset.catalog = None
mock_dataset.schema = None
mock_dataset.sql = None
mock_dataset.template_params = None
mock_dataset.filter_select_enabled = True
mock_dataset.fetch_values_predicate = None
mock_dataset.extra = None
mock_dataset.normalize_columns = False
mock_dataset.always_filter_main_dttm = False
mock_dataset.metrics = []
mock_dataset.columns = []
mock_dataset.database = MagicMock()
mock_chart.datasource = mock_dataset
mock_dashboard = MagicMock()
mock_dashboard.id = 1
mock_dashboard.uuid = uuid4()
mock_dashboard.dashboard_title = "Test Dashboard"
mock_dashboard.description = None
mock_dashboard.css = None
mock_dashboard.slug = "test-dashboard"
mock_dashboard.certified_by = None
mock_dashboard.certification_details = None
mock_dashboard.published = True
mock_dashboard.position = {}
mock_dashboard.json_metadata = "{}"
mock_dashboard.slices = [mock_chart]
with (
patch("superset.commands.dashboard.export_example.DashboardDAO") as mock_dao,
patch(
"superset.commands.dashboard.export_example.export_dataset_data"
) as mock_export_data,
):
mock_dao.find_by_id.return_value = mock_dashboard
mock_export_data.return_value = b"parquet data"
command = ExportExampleCommand(dashboard_id=1, export_data=True)
files = dict(command.run())
# Should have single dataset structure
assert "dataset.yaml" in files
assert "data.parquet" in files
assert "dashboard.yaml" in files
assert any(f.startswith("charts/") for f in files)
# Verify content generators work
dataset_content = files["dataset.yaml"]()
assert isinstance(dataset_content, bytes)
dataset_yaml = yaml.safe_load(dataset_content.decode("utf-8"))
assert dataset_yaml["table_name"] == "test_table"
def test_export_example_command_no_data():
"""Test ExportExampleCommand with export_data=False."""
mock_chart = MagicMock()
mock_chart.id = 1
mock_chart.uuid = uuid4()
mock_chart.slice_name = "Test Chart"
mock_chart.description = None
mock_chart.certified_by = None
mock_chart.certification_details = None
mock_chart.viz_type = "table"
mock_chart.params_dict = {}
mock_chart.cache_timeout = None
mock_dataset = MagicMock()
mock_dataset.id = 1
mock_dataset.uuid = uuid4()
mock_dataset.table_name = "test_table"
mock_dataset.main_dttm_col = None
mock_dataset.description = None
mock_dataset.default_endpoint = None
mock_dataset.offset = 0
mock_dataset.cache_timeout = None
mock_dataset.catalog = None
mock_dataset.schema = None
mock_dataset.sql = None
mock_dataset.template_params = None
mock_dataset.filter_select_enabled = True
mock_dataset.fetch_values_predicate = None
mock_dataset.extra = None
mock_dataset.normalize_columns = False
mock_dataset.always_filter_main_dttm = False
mock_dataset.metrics = []
mock_dataset.columns = []
mock_chart.datasource = mock_dataset
mock_dashboard = MagicMock()
mock_dashboard.id = 1
mock_dashboard.uuid = uuid4()
mock_dashboard.dashboard_title = "Test Dashboard"
mock_dashboard.description = None
mock_dashboard.css = None
mock_dashboard.slug = "test-dashboard"
mock_dashboard.certified_by = None
mock_dashboard.certification_details = None
mock_dashboard.published = True
mock_dashboard.position = {}
mock_dashboard.json_metadata = "{}"
mock_dashboard.slices = [mock_chart]
with patch("superset.commands.dashboard.export_example.DashboardDAO") as mock_dao:
mock_dao.find_by_id.return_value = mock_dashboard
command = ExportExampleCommand(dashboard_id=1, export_data=False)
files = dict(command.run())
# Should have dataset.yaml but no data.parquet
assert "dataset.yaml" in files
assert "data.parquet" not in files
assert "dashboard.yaml" in files