mirror of
https://github.com/apache/superset.git
synced 2026-05-07 17:04:58 +00:00
112 lines
4.0 KiB
Python
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)
|