Files
superset2/tests/unit_tests/versioning/test_diff_caps.py
Mike Bridge a899e1db41 feat(versioning): entity-version base infrastructure (gated off, dark launch) (#41176)
Co-authored-by: Mike Bridge <michael.bridge@ext.preset.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 19:57:05 -07:00

219 lines
8.8 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.
"""Output-safety caps on the change-record diff engine.
``version_changes`` is an audit log, not a content store. A single edit must
not be able to write an unbounded value or an unbounded number of rows — a
200 KB ``params``/SQL blob, a 2000-element list edit, or a 1000-key dict
rewrite would otherwise bloat both the table and the activity stream. These
tests pin :func:`cap_records` (applied at persistence time) against exactly
those cases, and confirm ordinary small edits pass through untouched.
"""
from __future__ import annotations
from typing import Any
from superset.utils import json
from superset.versioning.diff import (
cap_records,
ChangeRecord,
diff_slice_params,
MAX_RECORDS_PER_FIELD,
MAX_VALUE_BYTES,
)
def _bytes(value: object) -> int:
return len(json.dumps(value, default=str))
def _max_value_bytes(records: list[ChangeRecord]) -> int:
return max(
(_bytes(r.from_value) + _bytes(r.to_value) for r in records),
default=0,
)
def test_small_edits_pass_through_uncapped() -> None:
"""An ordinary handful of small param edits is returned verbatim — the
caps must not perturb the common case."""
records = diff_slice_params(
{"time_range": "Last week", "viz_type": "table"},
{"time_range": "Last month", "viz_type": "pie"},
)
capped = cap_records(records)
assert len(capped) == len(records) == 2
assert all("__truncated__" not in str(r.to_value) for r in capped)
assert all("__collapsed__" not in str(r.to_value) for r in capped)
def test_oversized_value_is_truncated_to_a_marker() -> None:
"""A value past MAX_VALUE_BYTES is replaced by a bounded marker that records
the original size and a preview, instead of writing the whole blob."""
big = "a" * (MAX_VALUE_BYTES * 4)
capped = cap_records(diff_slice_params({"sql": "x"}, {"sql": big}))
assert len(capped) == 1
marker = capped[0].to_value
assert marker["__truncated__"] is True
assert marker["original_bytes"] >= MAX_VALUE_BYTES * 4
assert len(marker["preview"]) <= 256
assert _bytes(marker) <= MAX_VALUE_BYTES + 600 # marker itself stays small
def test_list_explosion_is_collapsed() -> None:
"""Editing a 2000-element list emits one record per element pre-cap; the cap
collapses the whole field to a single summary record."""
pre = {"adhoc_filters": [{"col": f"c{i}", "val": i} for i in range(2000)]}
post = {"adhoc_filters": [{"col": f"c{i}", "val": i + 1} for i in range(2000)]}
raw = diff_slice_params(pre, post)
assert len(raw) > MAX_RECORDS_PER_FIELD # the explosion exists pre-cap
capped = cap_records(raw)
assert len(capped) == 1
assert capped[0].to_value == {"__collapsed__": len(raw)}
assert capped[0].path == ["params"]
def test_wide_dict_rewrite_is_collapsed() -> None:
"""A 1000-key dict rewrite under one field collapses to a single record."""
pre = {"query_context": {f"k{i}": i for i in range(1000)}}
post = {"query_context": {f"k{i}": i + 1 for i in range(1000)}}
capped = cap_records(diff_slice_params(pre, post))
assert len(capped) == 1
assert capped[0].to_value["__collapsed__"] == 1000
def test_distinct_fields_are_capped_independently() -> None:
"""The record-count cap is per top-level field: a blown-up field collapses
while a sibling small edit on a different field survives intact."""
pre = {
"adhoc_filters": [{"c": i} for i in range(2000)],
"time_range": "Last week",
}
post = {
"adhoc_filters": [{"c": i + 1} for i in range(2000)],
"time_range": "Last month",
}
capped = cap_records(diff_slice_params(pre, post))
# one collapsed record for params (filters live under params) + the
# time_range edit, also under params -> all collapse into the params bucket.
assert all(
_bytes(r.from_value) + _bytes(r.to_value) <= MAX_VALUE_BYTES + 600
for r in capped
)
assert len(capped) <= MAX_RECORDS_PER_FIELD
def test_caps_bound_every_record_from_diff_slice_params() -> None:
"""End-to-end invariant: after capping, no record exceeds the value or
count bounds, for the adversarial inputs the example gallery can't reach."""
pre = {"sql": "x", "adhoc_filters": [{"c": i} for i in range(2000)]}
post = {
"sql": "z" * (MAX_VALUE_BYTES * 10),
"adhoc_filters": [{"c": i + 1} for i in range(2000)],
}
capped = cap_records(diff_slice_params(pre, post))
assert len(capped) <= MAX_RECORDS_PER_FIELD
assert _max_value_bytes(capped) <= MAX_VALUE_BYTES + 600
# Heavy-but-legitimate chart params, representative of the variety the chart
# gallery produces — a wide table, a pivot, a multi-layer geospatial config.
# These exist to prove the caps are tuned NOT to fire on real charts: a sweep
# over the example gallery found every viz type's edit well under both bounds,
# so a normal edit must yield individual, readable records — never a collapse
# or truncation marker. If a future cap-threshold tweak starts degrading real
# charts, these break.
_REALISTIC_PARAMS: dict[str, dict[str, Any]] = {
"wide_table": {
"viz_type": "table",
"query_mode": "aggregate",
"groupby": [f"dim_{i}" for i in range(15)],
"metrics": [
{
"label": f"m_{i}",
"expressionType": "SIMPLE",
"column": {"column_name": f"c_{i}"},
"aggregate": "SUM",
}
for i in range(8)
],
"adhoc_filters": [
{
"clause": "WHERE",
"subject": f"dim_{i}",
"operator": "==",
"comparator": f"v_{i}",
"expressionType": "SIMPLE",
}
for i in range(5)
],
"time_range": "Last quarter",
"row_limit": 1000,
"table_timestamp_format": "smart_date",
},
"pivot": {
"viz_type": "pivot_table_v2",
"groupbyRows": [f"row_{i}" for i in range(6)],
"groupbyColumns": [f"col_{i}" for i in range(4)],
"metrics": [
{
"label": f"agg_{i}",
"expressionType": "SIMPLE",
"column": {"column_name": f"c_{i}"},
"aggregate": "AVG",
}
for i in range(5)
],
"aggregateFunction": "Sum",
"valueFormat": "SMART_NUMBER",
},
"geospatial": {
"viz_type": "deck_multi",
"deck_slices": [{"layer": f"l_{i}", "opacity": 0.8} for i in range(6)],
"viewport": {"longitude": -122.4, "latitude": 37.8, "zoom": 11.2},
"spatial": {"latCol": "lat", "lonCol": "lon", "type": "latlong"},
"color_picker": {"r": 14, "g": 96, "b": 245, "a": 1},
},
}
def test_realistic_chart_edits_are_not_capped() -> None:
"""A normal edit to a heavy-but-legitimate chart of each shape yields
individual records — none collapsed, none truncated — so the caps never
degrade real-world activity entries."""
for name, params in _REALISTIC_PARAMS.items():
edited = {**params, "row_limit": 5000, "time_range": "Last year"}
records = diff_slice_params(params, edited)
capped = cap_records(records)
assert capped == records, f"{name}: caps must not alter a normal edit"
assert capped, f"{name}: a real edit must still produce records"
for r in capped:
assert "__collapsed__" not in str(r.to_value), f"{name}: collapsed"
assert "__truncated__" not in str(r.to_value), f"{name}: truncated"
def test_full_rewrite_of_realistic_charts_stays_under_caps() -> None:
"""Even replacing a heavy chart's params wholesale (every field changes at
once) stays within both bounds for legitimate shapes — the caps headroom
is real, not just exceeded-by-construction in the adversarial tests."""
empty: dict[str, Any] = {}
for name, params in _REALISTIC_PARAMS.items():
capped = cap_records(diff_slice_params(empty, params))
assert len(capped) <= MAX_RECORDS_PER_FIELD, f"{name}: count over cap"
assert _max_value_bytes(capped) <= MAX_VALUE_BYTES, f"{name}: value over cap"