mirror of
https://github.com/apache/superset.git
synced 2026-05-21 15:55:10 +00:00
Add more tests
This commit is contained in:
@@ -15,7 +15,8 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import date, datetime, time
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pandas as pd
|
||||
@@ -40,6 +41,7 @@ from superset_core.semantic_layers.types import (
|
||||
from superset_core.semantic_layers.view import SemanticViewFeature
|
||||
|
||||
from superset.semantic_layers.mapper import (
|
||||
_coerce_scalar_filter_value,
|
||||
_convert_query_object_filter,
|
||||
_convert_time_grain,
|
||||
_get_filters_from_extras,
|
||||
@@ -2826,3 +2828,160 @@ def test_get_group_limit_filters_no_granularity(
|
||||
|
||||
# Should return None - no granularity means no time filters added
|
||||
assert result is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _coerce_scalar_filter_value: per-dtype branches
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _dim(dtype: pa.DataType, name: str = "d") -> Dimension:
|
||||
return Dimension(name, name, dtype, name, name.capitalize())
|
||||
|
||||
|
||||
def test_coerce_none_returns_none() -> None:
|
||||
assert _coerce_scalar_filter_value(None, _dim(pa.int64())) is None
|
||||
|
||||
|
||||
def test_coerce_unsupported_dtype_passes_through() -> None:
|
||||
# utf8 (and any dtype not branched in the function) returns the value as-is.
|
||||
assert _coerce_scalar_filter_value("abc", _dim(pa.utf8())) == "abc"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"raw,expected",
|
||||
[
|
||||
(True, True),
|
||||
(False, False),
|
||||
("true", True),
|
||||
("T", True),
|
||||
(" 1 ", True),
|
||||
("yes", True),
|
||||
("Y", True),
|
||||
("on", True),
|
||||
("false", False),
|
||||
("F", False),
|
||||
("0", False),
|
||||
("no", False),
|
||||
("N", False),
|
||||
("off", False),
|
||||
],
|
||||
)
|
||||
def test_coerce_boolean(raw: Any, expected: bool) -> None:
|
||||
assert _coerce_scalar_filter_value(raw, _dim(pa.bool_())) is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("raw", ["maybe", 1, 0.5])
|
||||
def test_coerce_boolean_invalid_raises(raw: Any) -> None:
|
||||
with pytest.raises(ValueError, match="Invalid boolean value"):
|
||||
_coerce_scalar_filter_value(raw, _dim(pa.bool_()))
|
||||
|
||||
|
||||
def test_coerce_integer_passthrough() -> None:
|
||||
assert _coerce_scalar_filter_value(42, _dim(pa.int64())) == 42
|
||||
|
||||
|
||||
def test_coerce_integer_rejects_bool() -> None:
|
||||
# bool is a subclass of int; we explicitly reject it.
|
||||
with pytest.raises(ValueError, match="Invalid integer value"):
|
||||
_coerce_scalar_filter_value(True, _dim(pa.int64()))
|
||||
|
||||
|
||||
def test_coerce_integer_rejects_other_types() -> None:
|
||||
with pytest.raises(ValueError, match="Invalid integer value"):
|
||||
_coerce_scalar_filter_value(1.5, _dim(pa.int64()))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dtype",
|
||||
[pa.float64(), pa.decimal128(10, 2)],
|
||||
)
|
||||
def test_coerce_floating_or_decimal(dtype: pa.DataType) -> None:
|
||||
assert _coerce_scalar_filter_value(1, _dim(dtype)) == 1.0
|
||||
assert _coerce_scalar_filter_value(1.5, _dim(dtype)) == 1.5
|
||||
assert _coerce_scalar_filter_value(" 2.5 ", _dim(dtype)) == 2.5
|
||||
|
||||
|
||||
def test_coerce_floating_rejects_bool() -> None:
|
||||
with pytest.raises(ValueError, match="Invalid numeric value"):
|
||||
_coerce_scalar_filter_value(True, _dim(pa.float64()))
|
||||
|
||||
|
||||
def test_coerce_floating_invalid_string_raises() -> None:
|
||||
with pytest.raises(ValueError, match="Invalid numeric value"):
|
||||
_coerce_scalar_filter_value("not-a-number", _dim(pa.float64()))
|
||||
|
||||
|
||||
def test_coerce_floating_rejects_other_types() -> None:
|
||||
with pytest.raises(ValueError, match="Invalid numeric value"):
|
||||
_coerce_scalar_filter_value([1.0], _dim(pa.float64()))
|
||||
|
||||
|
||||
def test_coerce_date_from_datetime() -> None:
|
||||
out = _coerce_scalar_filter_value(datetime(2025, 1, 2, 12, 0), _dim(pa.date32()))
|
||||
assert out == date(2025, 1, 2)
|
||||
|
||||
|
||||
def test_coerce_date_passthrough() -> None:
|
||||
out = _coerce_scalar_filter_value(date(2025, 1, 2), _dim(pa.date32()))
|
||||
assert out == date(2025, 1, 2)
|
||||
|
||||
|
||||
def test_coerce_date_from_iso_string() -> None:
|
||||
out = _coerce_scalar_filter_value(" 2025-01-02 ", _dim(pa.date32()))
|
||||
assert out == date(2025, 1, 2)
|
||||
|
||||
|
||||
def test_coerce_date_invalid_string_raises() -> None:
|
||||
with pytest.raises(ValueError, match="Invalid date value"):
|
||||
_coerce_scalar_filter_value("not-a-date", _dim(pa.date32()))
|
||||
|
||||
|
||||
def test_coerce_date_rejects_other_types() -> None:
|
||||
with pytest.raises(ValueError, match="Invalid date value"):
|
||||
_coerce_scalar_filter_value(20250102, _dim(pa.date32()))
|
||||
|
||||
|
||||
def test_coerce_timestamp_from_datetime_passthrough() -> None:
|
||||
dt = datetime(2025, 1, 2, 3, 4, 5)
|
||||
assert _coerce_scalar_filter_value(dt, _dim(pa.timestamp("us"))) is dt
|
||||
|
||||
|
||||
def test_coerce_timestamp_from_date() -> None:
|
||||
out = _coerce_scalar_filter_value(date(2025, 1, 2), _dim(pa.timestamp("us")))
|
||||
assert out == datetime(2025, 1, 2, 0, 0)
|
||||
|
||||
|
||||
def test_coerce_timestamp_from_iso_string_with_z() -> None:
|
||||
out = _coerce_scalar_filter_value("2025-01-02T03:04:05Z", _dim(pa.timestamp("us")))
|
||||
assert out == datetime.fromisoformat("2025-01-02T03:04:05+00:00")
|
||||
|
||||
|
||||
def test_coerce_timestamp_invalid_string_raises() -> None:
|
||||
with pytest.raises(ValueError, match="Invalid timestamp value"):
|
||||
_coerce_scalar_filter_value("not-a-ts", _dim(pa.timestamp("us")))
|
||||
|
||||
|
||||
def test_coerce_timestamp_rejects_other_types() -> None:
|
||||
with pytest.raises(ValueError, match="Invalid timestamp value"):
|
||||
_coerce_scalar_filter_value(1234567890, _dim(pa.timestamp("us")))
|
||||
|
||||
|
||||
def test_coerce_time_passthrough() -> None:
|
||||
out = _coerce_scalar_filter_value(time(3, 4, 5), _dim(pa.time64("us")))
|
||||
assert out == time(3, 4, 5)
|
||||
|
||||
|
||||
def test_coerce_time_from_iso_string() -> None:
|
||||
out = _coerce_scalar_filter_value(" 03:04:05 ", _dim(pa.time64("us")))
|
||||
assert out == time(3, 4, 5)
|
||||
|
||||
|
||||
def test_coerce_time_invalid_string_raises() -> None:
|
||||
with pytest.raises(ValueError, match="Invalid time value"):
|
||||
_coerce_scalar_filter_value("not-a-time", _dim(pa.time64("us")))
|
||||
|
||||
|
||||
def test_coerce_time_rejects_other_types() -> None:
|
||||
with pytest.raises(ValueError, match="Invalid time value"):
|
||||
_coerce_scalar_filter_value(123, _dim(pa.time64("us")))
|
||||
|
||||
Reference in New Issue
Block a user