From 40ed2f56a8dc9e791a26afb87f33aa7916f7f26e Mon Sep 17 00:00:00 2001 From: Amin Ghadersohi Date: Wed, 20 May 2026 23:01:36 +0000 Subject: [PATCH] refactor(mcp): add is_system fields to ThemeInfo and get_schema tests for new model types - Add is_system, is_system_default, is_system_dark to ThemeInfo and serialize_theme_object so schema discovery columns are fully serializable - Add get_schema tests covering css_template and theme model types: default column assertions, search/sort columns, and request validation Co-Authored-By: Claude Sonnet 4.6 --- superset/mcp_service/theme/schemas.py | 10 ++ .../system/tool/test_get_schema.py | 129 ++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/superset/mcp_service/theme/schemas.py b/superset/mcp_service/theme/schemas.py index e92e01afbc4..eb24d1bc394 100644 --- a/superset/mcp_service/theme/schemas.py +++ b/superset/mcp_service/theme/schemas.py @@ -73,6 +73,13 @@ class ThemeInfo(BaseModel): None, description="Theme JSON configuration data", ) + is_system: bool | None = Field(None, description="Whether this is a system theme") + is_system_default: bool | None = Field( + None, description="Whether this is the default system theme" + ) + is_system_dark: bool | None = Field( + None, description="Whether this is the dark system theme" + ) changed_on: str | datetime | None = Field( None, description="Last modification timestamp" ) @@ -244,6 +251,9 @@ def serialize_theme_object(obj: Any) -> ThemeInfo | None: uuid=str(getattr(obj, "uuid", "")) if getattr(obj, "uuid", None) else None, theme_name=getattr(obj, "theme_name", None), json_data=getattr(obj, "json_data", None), + is_system=getattr(obj, "is_system", None), + is_system_default=getattr(obj, "is_system_default", None), + is_system_dark=getattr(obj, "is_system_dark", None), changed_on=getattr(obj, "changed_on", None), created_on=getattr(obj, "created_on", None), ) diff --git a/tests/unit_tests/mcp_service/system/tool/test_get_schema.py b/tests/unit_tests/mcp_service/system/tool/test_get_schema.py index 1df8dc2ca48..07bad0ca4db 100644 --- a/tests/unit_tests/mcp_service/system/tool/test_get_schema.py +++ b/tests/unit_tests/mcp_service/system/tool/test_get_schema.py @@ -30,6 +30,9 @@ from superset.mcp_service.common.schema_discovery import ( CHART_DEFAULT_COLUMNS, CHART_SEARCH_COLUMNS, CHART_SORTABLE_COLUMNS, + CSS_TEMPLATE_DEFAULT_COLUMNS, + CSS_TEMPLATE_SEARCH_COLUMNS, + CSS_TEMPLATE_SORTABLE_COLUMNS, DASHBOARD_DEFAULT_COLUMNS, DASHBOARD_SEARCH_COLUMNS, DASHBOARD_SORTABLE_COLUMNS, @@ -38,6 +41,9 @@ from superset.mcp_service.common.schema_discovery import ( DATASET_SORTABLE_COLUMNS, GetSchemaRequest, ModelSchemaInfo, + THEME_DEFAULT_COLUMNS, + THEME_SEARCH_COLUMNS, + THEME_SORTABLE_COLUMNS, ) from superset.utils import json @@ -527,3 +533,126 @@ class TestSchemaDiscoveryConstants: assert "slice_name" in CHART_SEARCH_COLUMNS assert "table_name" in DATASET_SEARCH_COLUMNS assert "dashboard_title" in DASHBOARD_SEARCH_COLUMNS + + def test_css_template_default_columns(self): + """Test CSS template default columns include id and uuid but not css.""" + assert "id" in CSS_TEMPLATE_DEFAULT_COLUMNS + assert "uuid" in CSS_TEMPLATE_DEFAULT_COLUMNS + assert "template_name" in CSS_TEMPLATE_DEFAULT_COLUMNS + assert "css" not in CSS_TEMPLATE_DEFAULT_COLUMNS + + def test_css_template_sortable_columns(self): + """Test CSS template sortable columns are defined correctly.""" + assert "id" in CSS_TEMPLATE_SORTABLE_COLUMNS + assert "template_name" in CSS_TEMPLATE_SORTABLE_COLUMNS + assert "changed_on" in CSS_TEMPLATE_SORTABLE_COLUMNS + assert "created_on" in CSS_TEMPLATE_SORTABLE_COLUMNS + + def test_theme_default_columns(self): + """Test theme default columns include uuid.""" + assert "id" in THEME_DEFAULT_COLUMNS + assert "uuid" in THEME_DEFAULT_COLUMNS + assert "theme_name" in THEME_DEFAULT_COLUMNS + assert "json_data" not in THEME_DEFAULT_COLUMNS + + def test_theme_sortable_columns(self): + """Test theme sortable columns are defined correctly.""" + assert "id" in THEME_SORTABLE_COLUMNS + assert "theme_name" in THEME_SORTABLE_COLUMNS + assert "changed_on" in THEME_SORTABLE_COLUMNS + assert "created_on" in THEME_SORTABLE_COLUMNS + + def test_css_template_search_columns_defined(self): + """Test CSS template search columns are defined.""" + assert "template_name" in CSS_TEMPLATE_SEARCH_COLUMNS + + def test_theme_search_columns_defined(self): + """Test theme search columns are defined.""" + assert "theme_name" in THEME_SEARCH_COLUMNS + + +class TestGetSchemaCssTemplateAndTheme: + """Test get_schema tool for css_template and theme model types.""" + + @patch("superset.daos.css.CssTemplateDAO.get_filterable_columns_and_operators") + @pytest.mark.asyncio + async def test_get_schema_css_template(self, mock_filters, mcp_server): + """Test get_schema for css_template model type.""" + mock_filters.return_value = { + "template_name": ["eq", "sw", "ilike"], + } + + async with Client(mcp_server) as client: + result = await client.call_tool( + "get_schema", {"request": {"model_type": "css_template"}} + ) + + assert result.content is not None + data = json.loads(result.content[0].text) + + assert "schema_info" in data + info = data["schema_info"] + assert info["model_type"] == "css_template" + assert "select_columns" in info + assert "filter_columns" in info + assert "sortable_columns" in info + assert "default_select" in info + assert "search_columns" in info + + # uuid and template_name are in defaults; css is not + assert "id" in info["default_select"] + assert "uuid" in info["default_select"] + assert "template_name" in info["default_select"] + assert "css" not in info["default_select"] + + # template_name is searchable + assert "template_name" in info["search_columns"] + + # sortable columns + assert "template_name" in info["sortable_columns"] + assert "changed_on" in info["sortable_columns"] + + @patch("superset.daos.theme.ThemeDAO.get_filterable_columns_and_operators") + @pytest.mark.asyncio + async def test_get_schema_theme(self, mock_filters, mcp_server): + """Test get_schema for theme model type.""" + mock_filters.return_value = { + "theme_name": ["eq", "sw", "ilike"], + } + + async with Client(mcp_server) as client: + result = await client.call_tool( + "get_schema", {"request": {"model_type": "theme"}} + ) + + assert result.content is not None + data = json.loads(result.content[0].text) + + assert "schema_info" in data + info = data["schema_info"] + assert info["model_type"] == "theme" + assert "select_columns" in info + assert "filter_columns" in info + + # uuid and theme_name are in defaults; json_data is not + assert "id" in info["default_select"] + assert "uuid" in info["default_select"] + assert "theme_name" in info["default_select"] + assert "json_data" not in info["default_select"] + + # theme_name is searchable + assert "theme_name" in info["search_columns"] + + # sortable columns + assert "theme_name" in info["sortable_columns"] + assert "changed_on" in info["sortable_columns"] + + def test_css_template_model_type_accepted(self): + """css_template is a valid GetSchemaRequest model_type.""" + request = GetSchemaRequest(model_type="css_template") + assert request.model_type == "css_template" + + def test_theme_model_type_accepted(self): + """theme is a valid GetSchemaRequest model_type.""" + request = GetSchemaRequest(model_type="theme") + assert request.model_type == "theme"