Files
superset2/tests/integration_tests/dashboards/test_update_emoji.py
2026-04-29 10:08:50 -03:00

112 lines
4.0 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.
"""Tests that emoji characters in position_json are persisted correctly via PUT."""
from superset import db
from superset.models.dashboard import Dashboard
from superset.utils import json
from tests.integration_tests.base_tests import SupersetTestCase
from tests.integration_tests.constants import ADMIN_USERNAME
# position_json payload containing a 4-byte emoji in a MARKDOWN component,
# matching the real-world payload that triggered the truncation bug
POSITION_JSON_WITH_EMOJI = json.dumps(
{
"DASHBOARD_VERSION_KEY": "v2",
"ROOT_ID": {"type": "ROOT", "id": "ROOT_ID", "children": ["GRID_ID"]},
"GRID_ID": {
"type": "GRID",
"id": "GRID_ID",
"children": ["ROW-test"],
"parents": ["ROOT_ID"],
},
"ROW-test": {
"type": "ROW",
"id": "ROW-test",
"children": ["MARKDOWN-test"],
"parents": ["ROOT_ID", "GRID_ID"],
"meta": {"background": "BACKGROUND_TRANSPARENT"},
},
"MARKDOWN-test": {
"type": "MARKDOWN",
"id": "MARKDOWN-test",
"children": [],
"parents": ["ROOT_ID", "GRID_ID", "ROW-test"],
"meta": {"code": "📈 See Tab\n\ntest\ntest2", "height": 50, "width": 4},
},
},
)
class TestDashboardUpdateEmoji(SupersetTestCase):
"""Verify that emoji in position_json survive a dashboard PUT round-trip."""
def setUp(self) -> None:
super().setUp()
admin = self.get_user("admin")
self.dashboard = self.insert_dashboard(
"emoji test dashboard", "emoji-test-slug", [admin.id]
)
def tearDown(self) -> None:
db.session.delete(self.dashboard)
db.session.commit()
super().tearDown()
def test_position_json_emoji_survives_put(self) -> None:
"""Emoji in position_json must be stored and retrievable after PUT."""
self.login(ADMIN_USERNAME)
uri = f"api/v1/dashboard/{self.dashboard.id}"
rv = self.client.put(
uri,
json={"position_json": POSITION_JSON_WITH_EMOJI},
)
assert rv.status_code == 200, rv.json
db.session.expire(self.dashboard)
saved = db.session.get(Dashboard, self.dashboard.id)
assert saved is not None
parsed = json.loads(saved.position_json)
code = parsed["MARKDOWN-test"]["meta"]["code"]
# The emoji and the text after it must not be truncated
assert code == "📈 See Tab\n\ntest\ntest2"
def test_position_json_emoji_is_ascii_safe_in_db(self) -> None:
"""position_json stored in DB must be ASCII-safe (emoji escaped as \\uXXXX)
so it is safe for MySQL utf8 (3-byte) charset columns."""
self.login(ADMIN_USERNAME)
uri = f"api/v1/dashboard/{self.dashboard.id}"
rv = self.client.put(
uri,
json={"position_json": POSITION_JSON_WITH_EMOJI},
)
assert rv.status_code == 200, rv.json
db.session.expire(self.dashboard)
saved = db.session.get(Dashboard, self.dashboard.id)
assert saved is not None
# Raw 4-byte emoji must not appear in the stored string
assert "📈" not in saved.position_json
assert all(ord(c) < 128 for c in saved.position_json)