Files
superset2/tests/unit_tests/dashboards/api_test.py
T
Claude Code ae1838814f chore(tags): stop auto-generating type:/editor:/favorited_by: tags
villebro noted on #43390 that these system-generated tags appear to be
unused. Confirmed: every tags list and filter in the frontend explicitly
excludes non-custom tags (ChartList, DashboardList, SavedQueryList, the
chart PropertiesModal, the dashboard Header), so nothing ever surfaced
them to a user. What remained was pure write-side overhead: 13
SQLAlchemy event listeners across 5 models firing on every chart/
dashboard/query/dataset save and every favorite/unfavorite, plus a whole
performance-optimization mixin (CustomTagsOptimizationMixin,
DASHBOARD_LIST_CUSTOM_TAGS_ONLY) that existed purely to strip the
resulting noise back out of dashboard-list responses.

This removes the generation:
- superset/tags/models.py: drop ObjectUpdater's editor:/type: generation
  (after_insert/after_update) and FavStarUpdater's favorited_by:
  generation entirely. Keeps after_delete (tagged_object cleanup applies
  to every tag, custom included, and nothing else removes those rows
  since tagged_object.object_id has no FK - see its column comment).
- superset/tags/core.py: only registers the delete-cleanup listeners now.
- superset/common/tags.py + the `sync_tags` CLI command: removed (the
  backfill path for the generation this removes).
- superset/views/custom_tags_api_mixin.py, DASHBOARD_LIST_CUSTOM_TAGS_ONLY,
  Dashboard.custom_tags, and the schema/API plumbing built around them:
  removed - nothing left to optimize away once implicit tags stop
  accumulating.

Kept for backward compatibility, since MCP's list_tags/get_tag_info tools
document these tag types and upgraded deployments may already have rows
of these types: the TagType enum values, the custom_tag API filter,
and bulk-delete protection for non-custom tags. Docstrings updated to
say these are legacy/no longer generated rather than actively implicit.

Also fixes a real, currently-broken import in superset/daos/tag.py
(current_user_can_modify_object doesn't live in
superset.commands.tag.utils, only in superset.commands.utils) that
otherwise blocks every test in this area from running at all. Filed and
fixed separately as #43467; this commit will collapse away on rebase
once that merges.

Follow-up to #43390.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 13:44:55 -07:00

138 lines
4.9 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
import pytest
from superset.dashboards.schemas import DashboardGetResponseSchema
@pytest.fixture
def mock_dashboard() -> MagicMock:
dash = MagicMock()
# Real Dashboard objects are not subscriptable. Without this, marshmallow's
# get_value reads ``dash["changed_on"]`` (which a MagicMock happily returns
# as another mock) instead of the attribute, and marshmallow 4's DateTime
# field then fails serializing the mock via datetime.isoformat().
dash.__getitem__.side_effect = TypeError
dash.id = 1
dash.slug = "test-slug"
dash.url = "/superset/dashboard/test-slug/"
dash.dashboard_title = "Test Dashboard"
dash.thumbnail_url = "http://example.com/thumb.png"
dash.published = True
dash.css = ""
dash.theme = None
dash.json_metadata = "{}"
dash.position_json = "{}"
dash.certified_by = None
dash.certification_details = None
dash.changed_by_name = "admin"
dash.changed_by = MagicMock(id=1, first_name="admin", last_name="user")
dash.changed_on = None
dash.changed_on_humanized = "2 days ago"
dash.created_by = MagicMock(id=1, first_name="admin", last_name="user")
dash.created_on_humanized = "5 days ago"
dash.charts = []
dash.editors = []
dash.viewers = []
dash.tags = []
dash.is_managed_externally = False
dash.uuid = None
return dash
def test_schema_column_selection_excludes_thumbnail(
mock_dashboard: MagicMock,
) -> None:
schema = DashboardGetResponseSchema(only=["id", "dashboard_title"])
result = schema.dump(mock_dashboard)
assert "id" in result
assert "dashboard_title" in result
assert "thumbnail_url" not in result
assert "slug" not in result
def test_schema_column_selection_with_data_key(
mock_dashboard: MagicMock,
) -> None:
"""Fields with data_key should work when using the internal field name."""
schema = DashboardGetResponseSchema(only=["id", "changed_on_humanized"])
result = schema.dump(mock_dashboard)
assert "id" in result
assert "changed_on_delta_humanized" in result
assert "dashboard_title" not in result
def test_schema_full_response_includes_thumbnail(
mock_dashboard: MagicMock,
) -> None:
schema = DashboardGetResponseSchema()
result = schema.dump(mock_dashboard)
assert "thumbnail_url" in result
assert "id" in result
assert "dashboard_title" in result
def test_data_key_mapping_logic() -> None:
"""The key_to_name mapping used in the API correctly maps data_key to field name."""
schema = DashboardGetResponseSchema()
key_to_name = {
field.data_key or name: name for name, field in schema.fields.items()
}
# changed_on_delta_humanized is the data_key for changed_on_humanized
assert key_to_name["changed_on_delta_humanized"] == "changed_on_humanized"
assert key_to_name["created_on_delta_humanized"] == "created_on_humanized"
# fields without data_key map to themselves
assert key_to_name["id"] == "id"
assert key_to_name["thumbnail_url"] == "thumbnail_url"
def test_schema_strips_sensitive_fields_for_guest_user(
mock_dashboard: MagicMock,
) -> None:
"""Guest users should not see editors, viewers, or changed_by."""
schema = DashboardGetResponseSchema()
with patch("superset.dashboards.schemas.security_manager") as mock_sm:
mock_sm.is_guest_user = MagicMock(return_value=True)
result = schema.dump(mock_dashboard)
assert "editors" not in result
assert "viewers" not in result
assert "changed_by_name" not in result
assert "changed_by" not in result
assert "id" in result
assert "dashboard_title" in result
def test_schema_includes_all_fields_for_regular_user(
mock_dashboard: MagicMock,
) -> None:
"""Regular users should see editors, viewers, and changed_by."""
schema = DashboardGetResponseSchema()
with patch("superset.dashboards.schemas.security_manager") as mock_sm:
mock_sm.is_guest_user = MagicMock(return_value=False)
result = schema.dump(mock_dashboard)
assert "editors" in result
assert "viewers" in result
assert "changed_by_name" in result
assert "changed_by" in result