mirror of
https://github.com/apache/superset.git
synced 2026-04-07 18:35:15 +00:00
190 lines
7.7 KiB
Python
190 lines
7.7 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 unittest.mock import MagicMock, patch
|
|
|
|
from sqlalchemy.exc import OperationalError
|
|
|
|
from superset.app import SupersetApp
|
|
from superset.initialization import SupersetAppInitializer
|
|
|
|
|
|
class TestSupersetApp:
|
|
@patch("superset.app.logger")
|
|
def test_sync_config_to_db_skips_when_no_tables(self, mock_logger):
|
|
"""Test that sync is skipped when database is not up-to-date."""
|
|
# Setup
|
|
app = SupersetApp(__name__)
|
|
app.config = {"SQLALCHEMY_DATABASE_URI": "postgresql://user:pass@host:5432/db"}
|
|
|
|
# Mock _is_database_up_to_date to return False
|
|
with patch.object(app, "_is_database_up_to_date", return_value=False):
|
|
# Execute
|
|
app.sync_config_to_db()
|
|
|
|
# Assert
|
|
mock_logger.info.assert_called_once_with(
|
|
"Pending database migrations: run 'superset db upgrade'"
|
|
)
|
|
|
|
@patch("superset.extensions.db")
|
|
@patch("superset.app.logger")
|
|
def test_sync_config_to_db_handles_operational_error(self, mock_logger, mock_db):
|
|
"""Test that OperationalError during migration check is handled gracefully."""
|
|
# Setup
|
|
app = SupersetApp(__name__)
|
|
app.config = {"SQLALCHEMY_DATABASE_URI": "postgresql://user:pass@host:5432/db"}
|
|
error_msg = "Cannot connect to database"
|
|
|
|
# Mock db.engine.connect to raise an OperationalError
|
|
mock_db.engine.connect.side_effect = OperationalError(error_msg, None, None)
|
|
|
|
# Execute
|
|
app.sync_config_to_db()
|
|
|
|
# Assert - _is_database_up_to_date should catch the error and return False
|
|
# which causes the info log about pending migrations
|
|
mock_logger.info.assert_called_once_with(
|
|
"Pending database migrations: run 'superset db upgrade'"
|
|
)
|
|
|
|
@patch("superset.extensions.feature_flag_manager")
|
|
@patch("superset.app.logger")
|
|
@patch("superset.commands.theme.seed.SeedSystemThemesCommand")
|
|
def test_sync_config_to_db_initializes_when_tables_exist(
|
|
self,
|
|
mock_seed_themes_command,
|
|
mock_logger,
|
|
mock_feature_flag_manager,
|
|
):
|
|
"""Test that features are initialized when database is up-to-date."""
|
|
# Setup
|
|
app = SupersetApp(__name__)
|
|
app.config = {"SQLALCHEMY_DATABASE_URI": "postgresql://user:pass@host:5432/db"}
|
|
mock_feature_flag_manager.is_feature_enabled.return_value = True
|
|
mock_seed_themes = MagicMock()
|
|
mock_seed_themes_command.return_value = mock_seed_themes
|
|
|
|
# Mock _is_database_up_to_date to return True
|
|
with (
|
|
patch.object(app, "_is_database_up_to_date", return_value=True),
|
|
patch(
|
|
"superset.tags.core.register_sqla_event_listeners"
|
|
) as mock_register_listeners,
|
|
):
|
|
# Execute
|
|
app.sync_config_to_db()
|
|
|
|
# Assert
|
|
mock_feature_flag_manager.is_feature_enabled.assert_called_with(
|
|
"TAGGING_SYSTEM"
|
|
)
|
|
mock_register_listeners.assert_called_once()
|
|
# Should seed themes
|
|
mock_seed_themes_command.assert_called_once()
|
|
mock_seed_themes.run.assert_called_once()
|
|
# Should log successful completion
|
|
mock_logger.info.assert_any_call("Syncing configuration to database...")
|
|
mock_logger.info.assert_any_call(
|
|
"Configuration sync to database completed successfully"
|
|
)
|
|
|
|
|
|
class TestSupersetAppInitializer:
|
|
@patch("superset.initialization.logger")
|
|
def test_init_app_in_ctx_calls_sync_config_to_db(self, mock_logger):
|
|
"""Test that initialization calls app.sync_config_to_db()."""
|
|
# Setup
|
|
mock_app = MagicMock()
|
|
mock_app.config = {
|
|
"SQLALCHEMY_DATABASE_URI": "postgresql://user:pass@host:5432/db",
|
|
"FLASK_APP_MUTATOR": None,
|
|
}
|
|
app_initializer = SupersetAppInitializer(mock_app)
|
|
|
|
# Execute init_app_in_ctx which calls sync_config_to_db
|
|
with (
|
|
patch.object(app_initializer, "configure_fab"),
|
|
patch.object(app_initializer, "configure_url_map_converters"),
|
|
patch.object(app_initializer, "configure_data_sources"),
|
|
patch.object(app_initializer, "configure_auth_provider"),
|
|
patch.object(app_initializer, "configure_async_queries"),
|
|
patch.object(app_initializer, "configure_ssh_manager"),
|
|
patch.object(app_initializer, "configure_stats_manager"),
|
|
patch.object(app_initializer, "init_views"),
|
|
):
|
|
app_initializer.init_app_in_ctx()
|
|
|
|
# Assert that sync_config_to_db was called on the app
|
|
mock_app.sync_config_to_db.assert_called_once()
|
|
|
|
def test_database_uri_lazy_property(self):
|
|
"""Test database_uri property uses lazy initialization with smart caching."""
|
|
# Setup
|
|
mock_app = MagicMock()
|
|
test_uri = "postgresql://user:pass@host:5432/testdb"
|
|
mock_app.config = {"SQLALCHEMY_DATABASE_URI": test_uri}
|
|
app_initializer = SupersetAppInitializer(mock_app)
|
|
|
|
# Ensure cache is None initially
|
|
assert app_initializer._db_uri_cache is None
|
|
|
|
# First access should set the cache (valid URI)
|
|
uri = app_initializer.database_uri
|
|
assert uri == test_uri
|
|
assert app_initializer._db_uri_cache is not None
|
|
assert app_initializer._db_uri_cache == test_uri
|
|
|
|
# Second access should use cache (not call config.get again)
|
|
# Change the config to verify cache is being used
|
|
mock_app.config["SQLALCHEMY_DATABASE_URI"] = "different_uri"
|
|
uri2 = app_initializer.database_uri
|
|
assert (
|
|
uri2 == test_uri
|
|
) # Should still return cached value (not "different_uri")
|
|
|
|
def test_database_uri_doesnt_cache_fallback_values(self):
|
|
"""Test that fallback values like 'nouser' are not cached."""
|
|
# Setup
|
|
mock_app = MagicMock()
|
|
|
|
# Initially return the fallback nouser URI
|
|
config_dict = {
|
|
"SQLALCHEMY_DATABASE_URI": "postgresql://nouser:nopassword@nohost:5432/nodb"
|
|
}
|
|
mock_app.config = config_dict
|
|
app_initializer = SupersetAppInitializer(mock_app)
|
|
|
|
# First access returns fallback but shouldn't cache it
|
|
uri1 = app_initializer.database_uri
|
|
assert uri1 == "postgresql://nouser:nopassword@nohost:5432/nodb"
|
|
assert app_initializer._db_uri_cache is None # Should NOT be cached
|
|
|
|
# Now config is properly loaded - update the same dict
|
|
config_dict["SQLALCHEMY_DATABASE_URI"] = (
|
|
"postgresql://realuser:realpass@realhost:5432/realdb"
|
|
)
|
|
|
|
# Second access should get the new value since fallback wasn't cached
|
|
uri2 = app_initializer.database_uri
|
|
assert uri2 == "postgresql://realuser:realpass@realhost:5432/realdb"
|
|
assert app_initializer._db_uri_cache is not None # Now it should be cached
|
|
assert (
|
|
app_initializer._db_uri_cache
|
|
== "postgresql://realuser:realpass@realhost:5432/realdb"
|
|
)
|