blob: 01fde0967c93177e4e342946699b23742f4a9b91 [file] [log] [blame]
# 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"
)