mirror of
https://github.com/apache/superset.git
synced 2026-04-08 19:05:46 +00:00
207 lines
7.4 KiB
Python
207 lines
7.4 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.
|
|
"""Tests for examples/utils.py - YAML config loading and content assembly."""
|
|
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import yaml
|
|
|
|
|
|
def _create_example_tree(base_dir: Path) -> Path:
|
|
"""Create a minimal example directory tree under base_dir/superset/examples/.
|
|
|
|
Returns the 'superset' directory (what files("superset") would return).
|
|
"""
|
|
superset_dir = base_dir / "superset"
|
|
examples_dir = superset_dir / "examples"
|
|
|
|
# _shared configs
|
|
shared_dir = examples_dir / "_shared"
|
|
shared_dir.mkdir(parents=True)
|
|
(shared_dir / "database.yaml").write_text(
|
|
"database_name: examples\n"
|
|
"sqlalchemy_uri: __SQLALCHEMY_EXAMPLES_URI__\n"
|
|
"uuid: a2dc77af-e654-49bb-b321-40f6b559a1ee\n"
|
|
"version: '1.0.0'\n"
|
|
)
|
|
(shared_dir / "metadata.yaml").write_text(
|
|
"version: '1.0.0'\ntimestamp: '2020-12-11T22:52:56.534241+00:00'\n"
|
|
)
|
|
|
|
# An example with dataset, dashboard, and chart
|
|
example_dir = examples_dir / "test_example"
|
|
example_dir.mkdir()
|
|
(example_dir / "dataset.yaml").write_text(
|
|
yaml.dump(
|
|
{
|
|
"table_name": "test_table",
|
|
"schema": "main",
|
|
"uuid": "14f48794-ebfa-4f60-a26a-582c49132f1b",
|
|
"database_uuid": "a2dc77af-e654-49bb-b321-40f6b559a1ee",
|
|
"version": "1.0.0",
|
|
}
|
|
)
|
|
)
|
|
(example_dir / "dashboard.yaml").write_text(
|
|
yaml.dump(
|
|
{
|
|
"dashboard_title": "Test Dashboard",
|
|
"uuid": "dddddddd-dddd-dddd-dddd-dddddddddddd",
|
|
"version": "1.0.0",
|
|
}
|
|
)
|
|
)
|
|
charts_dir = example_dir / "charts"
|
|
charts_dir.mkdir()
|
|
(charts_dir / "test_chart.yaml").write_text(
|
|
yaml.dump(
|
|
{
|
|
"slice_name": "Test Chart",
|
|
"uuid": "cccccccc-cccc-cccc-cccc-cccccccccccc",
|
|
"dataset_uuid": "14f48794-ebfa-4f60-a26a-582c49132f1b",
|
|
"version": "1.0.0",
|
|
}
|
|
)
|
|
)
|
|
|
|
return superset_dir
|
|
|
|
|
|
def test_load_contents_builds_correct_import_structure():
|
|
"""load_contents() must produce the key structure ImportExamplesCommand expects.
|
|
|
|
This tests the orchestration entry point: YAML files are discovered from
|
|
the examples directory, the shared database config has its URI placeholder
|
|
replaced, and the result has the correct key prefixes (databases/, datasets/,
|
|
metadata.yaml).
|
|
"""
|
|
from superset.examples.utils import load_contents
|
|
|
|
with TemporaryDirectory() as tmpdir:
|
|
superset_dir = _create_example_tree(Path(tmpdir))
|
|
|
|
test_examples_uri = "sqlite:///path/to/examples.db"
|
|
mock_app = MagicMock()
|
|
mock_app.config = {"SQLALCHEMY_EXAMPLES_URI": test_examples_uri}
|
|
|
|
with patch("superset.examples.utils.files", return_value=superset_dir):
|
|
with patch("flask.current_app", mock_app):
|
|
contents = load_contents()
|
|
|
|
# Verify database config is present with placeholder replaced
|
|
assert "databases/examples.yaml" in contents
|
|
db_content = contents["databases/examples.yaml"]
|
|
assert "__SQLALCHEMY_EXAMPLES_URI__" not in db_content
|
|
assert test_examples_uri in db_content
|
|
|
|
# Verify metadata is present
|
|
assert "metadata.yaml" in contents
|
|
|
|
# Verify dataset is discovered with correct key prefix
|
|
assert "datasets/examples/test_example.yaml" in contents
|
|
|
|
# Verify dashboard is discovered with correct key prefix
|
|
assert "dashboards/test_example.yaml" in contents
|
|
|
|
# Verify chart is discovered with correct key prefix
|
|
assert "charts/test_example/test_chart.yaml" in contents
|
|
|
|
# Verify schema normalization happened (main -> null)
|
|
dataset_content = contents["datasets/examples/test_example.yaml"]
|
|
assert "schema: main" not in dataset_content
|
|
assert "schema: null" in dataset_content
|
|
|
|
|
|
def test_load_contents_replaces_sqlalchemy_examples_uri_placeholder():
|
|
"""The __SQLALCHEMY_EXAMPLES_URI__ placeholder must be replaced with the real URI.
|
|
|
|
If this placeholder is not replaced, the database import will fail with an
|
|
invalid connection string, preventing all examples from loading.
|
|
"""
|
|
from superset.examples.utils import _load_shared_configs
|
|
|
|
with TemporaryDirectory() as tmpdir:
|
|
superset_dir = _create_example_tree(Path(tmpdir))
|
|
examples_root = Path("examples")
|
|
|
|
test_uri = "postgresql://user:pass@host/db"
|
|
mock_app = MagicMock()
|
|
mock_app.config = {"SQLALCHEMY_EXAMPLES_URI": test_uri}
|
|
|
|
with patch("superset.examples.utils.files", return_value=superset_dir):
|
|
with patch("flask.current_app", mock_app):
|
|
contents = _load_shared_configs(examples_root)
|
|
|
|
assert "databases/examples.yaml" in contents
|
|
assert test_uri in contents["databases/examples.yaml"]
|
|
assert "__SQLALCHEMY_EXAMPLES_URI__" not in contents["databases/examples.yaml"]
|
|
|
|
|
|
@patch("superset.examples.utils.ImportExamplesCommand")
|
|
@patch("superset.examples.utils.load_contents")
|
|
def test_load_examples_from_configs_wires_command_correctly(
|
|
mock_load_contents,
|
|
mock_command_cls,
|
|
):
|
|
"""load_examples_from_configs() must construct ImportExamplesCommand
|
|
with overwrite=True and thread force_data through.
|
|
|
|
A wiring regression here would silently skip overwriting existing
|
|
examples or ignore the force_data flag.
|
|
"""
|
|
from superset.examples.utils import load_examples_from_configs
|
|
|
|
mock_load_contents.return_value = {"databases/examples.yaml": "content"}
|
|
mock_command = MagicMock()
|
|
mock_command_cls.return_value = mock_command
|
|
|
|
load_examples_from_configs(force_data=True)
|
|
|
|
mock_load_contents.assert_called_once_with(False)
|
|
mock_command_cls.assert_called_once_with(
|
|
{"databases/examples.yaml": "content"},
|
|
overwrite=True,
|
|
force_data=True,
|
|
)
|
|
mock_command.run.assert_called_once()
|
|
|
|
|
|
@patch("superset.examples.utils.ImportExamplesCommand")
|
|
@patch("superset.examples.utils.load_contents")
|
|
def test_load_examples_from_configs_defaults(
|
|
mock_load_contents,
|
|
mock_command_cls,
|
|
):
|
|
"""Default call should pass force_data=False and load_test_data=False."""
|
|
from superset.examples.utils import load_examples_from_configs
|
|
|
|
mock_load_contents.return_value = {}
|
|
mock_command = MagicMock()
|
|
mock_command_cls.return_value = mock_command
|
|
|
|
load_examples_from_configs()
|
|
|
|
mock_load_contents.assert_called_once_with(False)
|
|
mock_command_cls.assert_called_once_with(
|
|
{},
|
|
overwrite=True,
|
|
force_data=False,
|
|
)
|
|
mock_command.run.assert_called_once()
|