fix: chart import validation (#26993)

This commit is contained in:
Daniel Vaz Gaspar
2024-02-06 12:14:02 +00:00
committed by Michael S. Molina
parent e772915bb8
commit c029475f60
13 changed files with 404 additions and 146 deletions

View File

@@ -17,104 +17,130 @@
# pylint: disable=unused-argument, import-outside-toplevel, unused-import, invalid-name
import copy
from collections.abc import Generator
import pytest
from flask_appbuilder.security.sqla.models import Role, User
from pytest_mock import MockFixture
from sqlalchemy.orm.session import Session
from superset import security_manager
from superset.charts.commands.importers.v1.utils import import_chart
from superset.commands.exceptions import ImportFailedError
from superset.connectors.sqla.models import Database, SqlaTable
from superset.models.slice import Slice
from superset.utils.core import override_user
from tests.integration_tests.fixtures.importexport import chart_config
def test_import_chart(mocker: MockFixture, session: Session) -> None:
@pytest.fixture
def session_with_data(session: Session) -> Generator[Session, None, None]:
engine = session.get_bind()
SqlaTable.metadata.create_all(engine) # pylint: disable=no-member
dataset = SqlaTable(
table_name="test_table",
metrics=[],
main_dttm_col=None,
database=Database(database_name="my_database", sqlalchemy_uri="sqlite://"),
)
session.add(dataset)
session.flush()
slice = Slice(
id=1,
datasource_id=dataset.id,
datasource_type="table",
datasource_name="tmp_perm_table",
slice_name="slice_name",
uuid=chart_config["uuid"],
)
session.add(slice)
session.flush()
yield session
session.rollback()
@pytest.fixture
def session_with_schema(session: Session) -> Generator[Session, None, None]:
from superset.connectors.sqla.models import SqlaTable
engine = session.get_bind()
SqlaTable.metadata.create_all(engine) # pylint: disable=no-member
yield session
def test_import_chart(mocker: MockFixture, session_with_schema: Session) -> None:
"""
Test importing a chart.
"""
from superset import security_manager
from superset.charts.commands.importers.v1.utils import import_chart
from superset.connectors.sqla.models import SqlaTable
from superset.models.core import Database
from superset.models.slice import Slice
from tests.integration_tests.fixtures.importexport import chart_config
mocker.patch.object(security_manager, "can_access", return_value=True)
engine = session.get_bind()
Slice.metadata.create_all(engine) # pylint: disable=no-member
config = copy.deepcopy(chart_config)
config["datasource_id"] = 1
config["datasource_type"] = "table"
chart = import_chart(session, config)
chart = import_chart(session_with_schema, config)
assert chart.slice_name == "Deck Path"
assert chart.viz_type == "deck_path"
assert chart.is_managed_externally is False
assert chart.external_url is None
# Assert that the can write to chart was checked
security_manager.can_access.assert_called_once_with("can_write", "Chart")
def test_import_chart_managed_externally(mocker: MockFixture, session: Session) -> None:
def test_import_chart_managed_externally(
mocker: MockFixture, session_with_schema: Session
) -> None:
"""
Test importing a chart that is managed externally.
"""
from superset import security_manager
from superset.charts.commands.importers.v1.utils import import_chart
from superset.connectors.sqla.models import SqlaTable
from superset.models.core import Database
from superset.models.slice import Slice
from tests.integration_tests.fixtures.importexport import chart_config
mocker.patch.object(security_manager, "can_access", return_value=True)
engine = session.get_bind()
Slice.metadata.create_all(engine) # pylint: disable=no-member
config = copy.deepcopy(chart_config)
config["datasource_id"] = 1
config["datasource_type"] = "table"
config["is_managed_externally"] = True
config["external_url"] = "https://example.org/my_chart"
chart = import_chart(session, config)
chart = import_chart(session_with_schema, config)
assert chart.is_managed_externally is True
assert chart.external_url == "https://example.org/my_chart"
# Assert that the can write to chart was checked
security_manager.can_access.assert_called_once_with("can_write", "Chart")
def test_import_chart_without_permission(
mocker: MockFixture,
session: Session,
session_with_schema: Session,
) -> None:
"""
Test importing a chart when a user doesn't have permissions to create.
"""
from superset import security_manager
from superset.charts.commands.importers.v1.utils import import_chart
from superset.connectors.sqla.models import SqlaTable
from superset.models.core import Database
from superset.models.slice import Slice
from tests.integration_tests.fixtures.importexport import chart_config
mocker.patch.object(security_manager, "can_access", return_value=False)
engine = session.get_bind()
Slice.metadata.create_all(engine) # pylint: disable=no-member
config = copy.deepcopy(chart_config)
config["datasource_id"] = 1
config["datasource_type"] = "table"
with pytest.raises(ImportFailedError) as excinfo:
import_chart(session, config)
import_chart(session_with_schema, config)
assert (
str(excinfo.value)
== "Chart doesn't exist and user doesn't have permission to create charts"
)
# Assert that the can write to chart was checked
security_manager.can_access.assert_called_once_with("can_write", "Chart")
def test_filter_chart_annotations(mocker: MockFixture, session: Session) -> None:
def test_filter_chart_annotations(session: Session) -> None:
"""
Test importing a chart.
"""
from superset import security_manager
from superset.charts.commands.importers.v1.utils import filter_chart_annotations
from tests.integration_tests.fixtures.importexport import (
chart_config_with_mixed_annotations,
@@ -127,3 +153,67 @@ def test_filter_chart_annotations(mocker: MockFixture, session: Session) -> None
assert len(annotation_layers) == 1
assert all([al["annotationType"] == "FORMULA" for al in annotation_layers])
def test_import_existing_chart_without_permission(
mocker: MockFixture,
session_with_data: Session,
) -> None:
"""
Test importing a chart when a user doesn't have permissions to modify.
"""
mocker.patch.object(security_manager, "can_access", return_value=True)
mocker.patch.object(security_manager, "can_access_chart", return_value=False)
slice = (
session_with_data.query(Slice)
.filter(Slice.uuid == chart_config["uuid"])
.one_or_none()
)
with override_user("admin"):
with pytest.raises(ImportFailedError) as excinfo:
import_chart(session_with_data, chart_config, overwrite=True)
assert (
str(excinfo.value)
== "A chart already exists and user doesn't have permissions to overwrite it"
)
# Assert that the can write to chart was checked
security_manager.can_access.assert_called_once_with("can_write", "Chart")
security_manager.can_access_chart.assert_called_once_with(slice)
def test_import_existing_chart_with_permission(
mocker: MockFixture,
session_with_data: Session,
) -> None:
"""
Test importing a chart that exists when a user has access permission to that chart.
"""
mocker.patch.object(security_manager, "can_access", return_value=True)
mocker.patch.object(security_manager, "can_access_chart", return_value=True)
admin = User(
first_name="Alice",
last_name="Doe",
email="adoe@example.org",
username="admin",
roles=[Role(name="Admin")],
)
config = copy.deepcopy(chart_config)
config["datasource_id"] = 1
config["datasource_type"] = "table"
slice = (
session_with_data.query(Slice)
.filter(Slice.uuid == config["uuid"])
.one_or_none()
)
with override_user(admin):
import_chart(session_with_data, config, overwrite=True)
# Assert that the can write to chart was checked
security_manager.can_access.assert_called_once_with("can_write", "Chart")
security_manager.can_access_chart.assert_called_once_with(slice)