# 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" )