This is an automated email from the ASF dual-hosted git repository.

beto pushed a commit to branch default-db-schema-dropdown
in repository https://gitbox.apache.org/repos/asf/superset.git

commit b377ce564b97adfe13e662078bcc9129faf06f6a
Author: Beto Dealmeida <[email protected]>
AuthorDate: Thu Dec 18 18:11:08 2025 -0500

    test(api): add tests for default catalog/schema in API responses
    
    Add and update tests for the catalogs and schemas endpoints to verify:
    - Default catalog/schema is returned when accessible
    - Default is null when not in user's accessible list
    - Default is null when retrieval fails (error handling)
    - Default works correctly with upload_allowed filter
    - Default is null when not in upload-allowed schemas list
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-Authored-By: Claude Opus 4.5 <[email protected]>
---
 tests/unit_tests/databases/api_test.py | 344 +++++++++++++++++++++++++--------
 1 file changed, 265 insertions(+), 79 deletions(-)

diff --git a/tests/unit_tests/databases/api_test.py 
b/tests/unit_tests/databases/api_test.py
index 0785d762e5..a0f035630d 100644
--- a/tests/unit_tests/databases/api_test.py
+++ b/tests/unit_tests/databases/api_test.py
@@ -225,93 +225,96 @@ def test_database_connection(
     mocker.patch("superset.utils.log.DBEventLogger.log")
 
     response = client.get("/api/v1/database/1/connection")
-    assert response.json == {
-        "id": 1,
-        "result": {
-            "allow_ctas": False,
-            "allow_cvas": False,
-            "allow_dml": False,
-            "allow_file_upload": False,
-            "allow_run_async": False,
-            "backend": "gsheets",
-            "cache_timeout": None,
-            "configuration_method": "sqlalchemy_form",
-            "database_name": "my_database",
-            "driver": "gsheets",
-            "engine_information": {
-                "disable_ssh_tunneling": True,
-                "supports_dynamic_catalog": False,
-                "supports_file_upload": True,
-                "supports_oauth2": True,
-            },
-            "expose_in_sqllab": True,
-            "extra": '{\n    "metadata_params": {},\n    "engine_params": 
{},\n    "metadata_cache_timeout": {},\n    "schemas_allowed_for_file_upload": 
[]\n}\n',  # noqa: E501
-            "force_ctas_schema": None,
+    assert (
+        response.json
+        == {
             "id": 1,
-            "impersonate_user": False,
-            "is_managed_externally": False,
-            "masked_encrypted_extra": json.dumps(
-                {
+            "result": {
+                "allow_ctas": False,
+                "allow_cvas": False,
+                "allow_dml": False,
+                "allow_file_upload": False,
+                "allow_run_async": False,
+                "backend": "gsheets",
+                "cache_timeout": None,
+                "configuration_method": "sqlalchemy_form",
+                "database_name": "my_database",
+                "driver": "gsheets",
+                "engine_information": {
+                    "disable_ssh_tunneling": True,
+                    "supports_dynamic_catalog": False,
+                    "supports_file_upload": True,
+                    "supports_oauth2": True,
+                },
+                "expose_in_sqllab": True,
+                "extra": '{\n    "metadata_params": {},\n    "engine_params": 
{},\n    "metadata_cache_timeout": {},\n    "schemas_allowed_for_file_upload": 
[]\n}\n',  # noqa: E501
+                "force_ctas_schema": None,
+                "id": 1,
+                "impersonate_user": False,
+                "is_managed_externally": False,
+                "masked_encrypted_extra": json.dumps(
+                    {
+                        "service_account_info": {
+                            "type": "service_account",
+                            "project_id": "black-sanctum-314419",
+                            "private_key_id": 
"259b0d419a8f840056158763ff54d8b08f7b8173",  # noqa: E501
+                            "private_key": "XXXXXXXXXX",
+                            "client_email": 
"google-spreadsheets-demo-se...@black-sanctum-314419.iam.gserviceaccount.com",  
# noqa: E501
+                            "client_id": "114567578578109757129",
+                            "auth_uri": 
"https://accounts.google.com/o/oauth2/auth";,
+                            "token_uri": "https://oauth2.googleapis.com/token";,
+                            "auth_provider_x509_cert_url": 
"https://www.googleapis.com/oauth2/v1/certs";,
+                            "client_x509_cert_url": 
"https://www.googleapis.com/robot/v1/metadata/x509/google-spreadsheets-demo-servi%40black-sanctum-314419.iam.gserviceaccount.com";,
+                        }
+                    }
+                ),
+                "parameters": {
                     "service_account_info": {
-                        "type": "service_account",
-                        "project_id": "black-sanctum-314419",
-                        "private_key_id": 
"259b0d419a8f840056158763ff54d8b08f7b8173",
-                        "private_key": "XXXXXXXXXX",
+                        "auth_provider_x509_cert_url": 
"https://www.googleapis.com/oauth2/v1/certs";,
+                        "auth_uri": 
"https://accounts.google.com/o/oauth2/auth";,
                         "client_email": 
"google-spreadsheets-demo-se...@black-sanctum-314419.iam.gserviceaccount.com",  
# noqa: E501
                         "client_id": "114567578578109757129",
-                        "auth_uri": 
"https://accounts.google.com/o/oauth2/auth";,
-                        "token_uri": "https://oauth2.googleapis.com/token";,
-                        "auth_provider_x509_cert_url": 
"https://www.googleapis.com/oauth2/v1/certs";,
                         "client_x509_cert_url": 
"https://www.googleapis.com/robot/v1/metadata/x509/google-spreadsheets-demo-servi%40black-sanctum-314419.iam.gserviceaccount.com";,
+                        "private_key": "XXXXXXXXXX",
+                        "private_key_id": 
"259b0d419a8f840056158763ff54d8b08f7b8173",
+                        "project_id": "black-sanctum-314419",
+                        "token_uri": "https://oauth2.googleapis.com/token";,
+                        "type": "service_account",
                     }
-                }
-            ),
-            "parameters": {
-                "service_account_info": {
-                    "auth_provider_x509_cert_url": 
"https://www.googleapis.com/oauth2/v1/certs";,
-                    "auth_uri": "https://accounts.google.com/o/oauth2/auth";,
-                    "client_email": 
"google-spreadsheets-demo-se...@black-sanctum-314419.iam.gserviceaccount.com",  
# noqa: E501
-                    "client_id": "114567578578109757129",
-                    "client_x509_cert_url": 
"https://www.googleapis.com/robot/v1/metadata/x509/google-spreadsheets-demo-servi%40black-sanctum-314419.iam.gserviceaccount.com";,
-                    "private_key": "XXXXXXXXXX",
-                    "private_key_id": 
"259b0d419a8f840056158763ff54d8b08f7b8173",
-                    "project_id": "black-sanctum-314419",
-                    "token_uri": "https://oauth2.googleapis.com/token";,
-                    "type": "service_account",
-                }
-            },
-            "parameters_schema": {
-                "properties": {
-                    "catalog": {"type": "object"},
-                    "oauth2_client_info": {
-                        "default": {
-                            "authorization_request_uri": 
"https://accounts.google.com/o/oauth2/v2/auth";,
-                            "scope": (
-                                
"https://www.googleapis.com/auth/drive.readonly "
-                                "https://www.googleapis.com/auth/spreadsheets "
-                                "https://spreadsheets.google.com/feeds";
-                            ),
-                            "token_request_uri": 
"https://oauth2.googleapis.com/token";,
+                },
+                "parameters_schema": {
+                    "properties": {
+                        "catalog": {"type": "object"},
+                        "oauth2_client_info": {
+                            "default": {
+                                "authorization_request_uri": 
"https://accounts.google.com/o/oauth2/v2/auth";,
+                                "scope": (
+                                    
"https://www.googleapis.com/auth/drive.readonly "
+                                    
"https://www.googleapis.com/auth/spreadsheets "
+                                    "https://spreadsheets.google.com/feeds";
+                                ),
+                                "token_request_uri": 
"https://oauth2.googleapis.com/token";,
+                            },
+                            "description": "OAuth2 client information",
+                            "nullable": True,
+                            "type": "string",
+                            "x-encrypted-extra": True,
+                        },
+                        "service_account_info": {
+                            "description": "Contents of GSheets JSON 
credentials.",
+                            "type": "string",
+                            "x-encrypted-extra": True,
                         },
-                        "description": "OAuth2 client information",
-                        "nullable": True,
-                        "type": "string",
-                        "x-encrypted-extra": True,
-                    },
-                    "service_account_info": {
-                        "description": "Contents of GSheets JSON credentials.",
-                        "type": "string",
-                        "x-encrypted-extra": True,
                     },
+                    "type": "object",
                 },
-                "type": "object",
+                "server_cert": None,
+                "sqlalchemy_uri": "gsheets://",
+                "ssh_tunnel": None,
+                "uuid": "02feae18-2dd6-4bb4-a9c0-49e9d4f29d58",
             },
-            "server_cert": None,
-            "sqlalchemy_uri": "gsheets://",
-            "ssh_tunnel": None,
-            "uuid": "02feae18-2dd6-4bb4-a9c0-49e9d4f29d58",
-        },
-    }
+        }
+    )
 
     response = client.get("/api/v1/database/1")
     assert response.json == {
@@ -2104,6 +2107,7 @@ def test_catalogs(
     """
     database = mocker.MagicMock()
     database.get_all_catalog_names.return_value = {"db1", "db2"}
+    database.get_default_catalog.return_value = "db2"
     DatabaseDAO = mocker.patch("superset.databases.api.DatabaseDAO")  # noqa: 
N806
     DatabaseDAO.find_by_id.return_value = database
 
@@ -2115,7 +2119,7 @@ def test_catalogs(
 
     response = client.get("/api/v1/database/1/catalogs/")
     assert response.status_code == 200
-    assert response.json == {"result": ["db2"]}
+    assert response.json == {"result": ["db2"], "default": "db2"}
     database.get_all_catalog_names.assert_called_with(
         cache=database.catalog_cache_enabled,
         cache_timeout=database.catalog_cache_timeout,
@@ -2187,6 +2191,7 @@ def test_schemas(
 
     database = mocker.MagicMock()
     database.get_all_schema_names.return_value = {"schema1", "schema2"}
+    database.get_default_schema.return_value = "schema2"
     datamodel = mocker.patch.object(DatabaseRestApi, "datamodel")
     datamodel.get.return_value = database
 
@@ -2198,7 +2203,7 @@ def test_schemas(
 
     response = client.get("/api/v1/database/1/schemas/")
     assert response.status_code == 200
-    assert response.json == {"result": ["schema2"]}
+    assert response.json == {"result": ["schema2"], "default": "schema2"}
     database.get_all_schema_names.assert_called_with(
         catalog=None,
         cache=database.schema_cache_enabled,
@@ -2274,3 +2279,184 @@ def test_schemas_with_oauth2(
             }
         ]
     }
+
+
+def test_catalogs_default_not_accessible(
+    mocker: MockerFixture,
+    client: Any,
+    full_api_access: None,
+) -> None:
+    """
+    Test that `default` is null when the default catalog is not accessible to 
the user.
+    """
+    database = mocker.MagicMock()
+    database.get_all_catalog_names.return_value = {"db1", "db2"}
+    database.get_default_catalog.return_value = "db1"  # default is db1
+    DatabaseDAO = mocker.patch("superset.databases.api.DatabaseDAO")  # noqa: 
N806
+    DatabaseDAO.find_by_id.return_value = database
+
+    security_manager = mocker.patch(
+        "superset.databases.api.security_manager",
+        new=mocker.MagicMock(),
+    )
+    # User only has access to db2, not the default db1
+    security_manager.get_catalogs_accessible_by_user.return_value = {"db2"}
+
+    response = client.get("/api/v1/database/1/catalogs/")
+    assert response.status_code == 200
+    assert response.json == {"result": ["db2"], "default": None}
+
+
+def test_catalogs_default_retrieval_fails(
+    mocker: MockerFixture,
+    client: Any,
+    full_api_access: None,
+) -> None:
+    """
+    Test that the endpoint still works when get_default_catalog fails.
+    """
+    database = mocker.MagicMock()
+    database.get_all_catalog_names.return_value = {"db1", "db2"}
+    database.get_default_catalog.side_effect = Exception("Connection failed")
+    DatabaseDAO = mocker.patch("superset.databases.api.DatabaseDAO")  # noqa: 
N806
+    DatabaseDAO.find_by_id.return_value = database
+
+    security_manager = mocker.patch(
+        "superset.databases.api.security_manager",
+        new=mocker.MagicMock(),
+    )
+    security_manager.get_catalogs_accessible_by_user.return_value = {"db1", 
"db2"}
+
+    response = client.get("/api/v1/database/1/catalogs/")
+    assert response.status_code == 200
+    # Result should still be returned, default is null due to error
+    assert set(response.json["result"]) == {"db1", "db2"}
+    assert response.json["default"] is None
+
+
+def test_schemas_default_not_accessible(
+    mocker: MockerFixture,
+    client: Any,
+    full_api_access: None,
+) -> None:
+    """
+    Test that `default` is null when the default schema is not accessible to 
the user.
+    """
+    from superset.databases.api import DatabaseRestApi
+
+    database = mocker.MagicMock()
+    database.get_all_schema_names.return_value = {"public", "private"}
+    database.get_default_schema.return_value = "public"  # default is public
+    datamodel = mocker.patch.object(DatabaseRestApi, "datamodel")
+    datamodel.get.return_value = database
+
+    security_manager = mocker.patch(
+        "superset.databases.api.security_manager",
+        new=mocker.MagicMock(),
+    )
+    # User only has access to private, not the default public
+    security_manager.get_schemas_accessible_by_user.return_value = {"private"}
+
+    response = client.get("/api/v1/database/1/schemas/")
+    assert response.status_code == 200
+    assert response.json == {"result": ["private"], "default": None}
+
+
+def test_schemas_default_retrieval_fails(
+    mocker: MockerFixture,
+    client: Any,
+    full_api_access: None,
+) -> None:
+    """
+    Test that the endpoint still works when get_default_schema fails.
+    """
+    from superset.databases.api import DatabaseRestApi
+
+    database = mocker.MagicMock()
+    database.get_all_schema_names.return_value = {"public", "private"}
+    database.get_default_schema.side_effect = Exception("Connection failed")
+    datamodel = mocker.patch.object(DatabaseRestApi, "datamodel")
+    datamodel.get.return_value = database
+
+    security_manager = mocker.patch(
+        "superset.databases.api.security_manager",
+        new=mocker.MagicMock(),
+    )
+    security_manager.get_schemas_accessible_by_user.return_value = {"public", 
"private"}
+
+    response = client.get("/api/v1/database/1/schemas/")
+    assert response.status_code == 200
+    # Result should still be returned, default is null due to error
+    assert set(response.json["result"]) == {"public", "private"}
+    assert response.json["default"] is None
+
+
+def test_schemas_default_with_upload_allowed(
+    mocker: MockerFixture,
+    client: Any,
+    full_api_access: None,
+) -> None:
+    """
+    Test that default schema is returned correctly with upload_allowed filter.
+    """
+    from superset.databases.api import DatabaseRestApi
+
+    database = mocker.MagicMock()
+    database.get_all_schema_names.return_value = {"public", "uploads", 
"private"}
+    database.get_default_schema.return_value = "public"
+    database.allow_file_upload = True
+    database.get_schema_access_for_file_upload.return_value = ["uploads", 
"public"]
+    datamodel = mocker.patch.object(DatabaseRestApi, "datamodel")
+    datamodel.get.return_value = database
+
+    security_manager = mocker.patch(
+        "superset.databases.api.security_manager",
+        new=mocker.MagicMock(),
+    )
+    security_manager.get_schemas_accessible_by_user.return_value = {
+        "public",
+        "uploads",
+        "private",
+    }
+
+    response = client.get("/api/v1/database/1/schemas/?q=(upload_allowed:!t)")
+    assert response.status_code == 200
+    # Only upload-allowed schemas should be returned
+    assert set(response.json["result"]) == {"public", "uploads"}
+    # Default should be public since it's in the allowed list
+    assert response.json["default"] == "public"
+
+
+def test_schemas_default_not_in_upload_allowed(
+    mocker: MockerFixture,
+    client: Any,
+    full_api_access: None,
+) -> None:
+    """
+    Test that default schema is null when not in upload_allowed schemas.
+    """
+    from superset.databases.api import DatabaseRestApi
+
+    database = mocker.MagicMock()
+    database.get_all_schema_names.return_value = {"public", "uploads", 
"private"}
+    database.get_default_schema.return_value = "private"  # default not in 
allowed list
+    database.allow_file_upload = True
+    database.get_schema_access_for_file_upload.return_value = ["uploads", 
"public"]
+    datamodel = mocker.patch.object(DatabaseRestApi, "datamodel")
+    datamodel.get.return_value = database
+
+    security_manager = mocker.patch(
+        "superset.databases.api.security_manager",
+        new=mocker.MagicMock(),
+    )
+    security_manager.get_schemas_accessible_by_user.return_value = {
+        "public",
+        "uploads",
+        "private",
+    }
+
+    response = client.get("/api/v1/database/1/schemas/?q=(upload_allowed:!t)")
+    assert response.status_code == 200
+    assert set(response.json["result"]) == {"public", "uploads"}
+    # Default should be null since "private" is not in allowed list
+    assert response.json["default"] is None

Reply via email to