recon-driven sql composer; pick → compose → execute; llm out of structural sql
This commit is contained in:
122
tests/test_pick_shape.py
Normal file
122
tests/test_pick_shape.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""Pick / Filter / OrderBy validation — round-trips and rejection cases."""
|
||||
|
||||
import pytest
|
||||
|
||||
from api.composer.types import Filter, OrderBy, Pick, PickValidationError
|
||||
|
||||
|
||||
# ── Filter ──────────────────────────────────────────────────
|
||||
|
||||
def test_filter_equals_roundtrip():
|
||||
f = Filter.from_dict({"column": "status", "equals": "B"})
|
||||
assert f.kind() == "equals"
|
||||
assert f.to_dict() == {"column": "status", "equals": "B"}
|
||||
|
||||
|
||||
def test_filter_in_values_roundtrip():
|
||||
f = Filter.from_dict({"column": "status", "in_values": ["A", "B"]})
|
||||
assert f.kind() == "in_values"
|
||||
assert f.to_dict() == {"column": "status", "in_values": ["A", "B"]}
|
||||
|
||||
|
||||
def test_filter_between_normalised_to_tuple():
|
||||
f = Filter.from_dict({"column": "amount", "between": [1000, 5000]})
|
||||
assert f.kind() == "between"
|
||||
assert f.between == (1000, 5000)
|
||||
# round-trip emits a list, not a tuple
|
||||
assert f.to_dict() == {"column": "amount", "between": [1000, 5000]}
|
||||
|
||||
|
||||
def test_filter_date_range_roundtrip():
|
||||
f = Filter.from_dict({"column": "date", "date_range": "1996"})
|
||||
assert f.kind() == "date_range"
|
||||
assert f.to_dict() == {"column": "date", "date_range": "1996"}
|
||||
|
||||
|
||||
def test_filter_missing_value_raises():
|
||||
with pytest.raises(PickValidationError, match="no value field"):
|
||||
Filter(column="x").kind()
|
||||
|
||||
|
||||
def test_filter_multiple_value_fields_raises():
|
||||
f = Filter(column="x", equals="A", in_values=["A", "B"])
|
||||
with pytest.raises(PickValidationError, match="multiple value fields"):
|
||||
f.kind()
|
||||
|
||||
|
||||
def test_filter_missing_column_raises():
|
||||
with pytest.raises(PickValidationError, match="missing 'column'"):
|
||||
Filter.from_dict({"equals": "B"})
|
||||
|
||||
|
||||
def test_filter_between_wrong_arity_raises():
|
||||
with pytest.raises(PickValidationError, match="2-element"):
|
||||
Filter.from_dict({"column": "x", "between": [1, 2, 3]})
|
||||
|
||||
|
||||
# ── OrderBy ─────────────────────────────────────────────────
|
||||
|
||||
def test_order_by_metric():
|
||||
ob = OrderBy.from_dict({"by": "metric", "direction": "desc"})
|
||||
assert ob.by == "metric"
|
||||
assert ob.direction == "desc"
|
||||
assert ob.to_dict() == {"by": "metric", "direction": "desc"}
|
||||
|
||||
|
||||
def test_order_by_dimension_requires_dimension():
|
||||
with pytest.raises(PickValidationError, match="requires order_by.dimension"):
|
||||
OrderBy.from_dict({"by": "dimension"})
|
||||
|
||||
|
||||
def test_order_by_invalid_direction_raises():
|
||||
with pytest.raises(PickValidationError, match="direction"):
|
||||
OrderBy.from_dict({"by": "metric", "direction": "sideways"})
|
||||
|
||||
|
||||
# ── Pick ────────────────────────────────────────────────────
|
||||
|
||||
def test_pick_minimal():
|
||||
p = Pick.from_dict({"kind": "aggregate", "metric": "loan_volume"})
|
||||
assert p.metric == "loan_volume"
|
||||
assert p.group_by == []
|
||||
assert p.where == []
|
||||
assert p.order_by is None
|
||||
assert p.limit is None
|
||||
|
||||
|
||||
def test_pick_full_roundtrip():
|
||||
raw = {
|
||||
"kind": "aggregate",
|
||||
"metric": "loan_default_rate",
|
||||
"group_by": ["A2"],
|
||||
"where": [{"column": "date", "date_range": "1996"}],
|
||||
"order_by": {"by": "metric", "direction": "desc"},
|
||||
"limit": 10,
|
||||
}
|
||||
p = Pick.from_dict(raw)
|
||||
assert p.to_dict() == raw
|
||||
|
||||
|
||||
def test_pick_rejects_non_aggregate_kind():
|
||||
with pytest.raises(PickValidationError, match="aggregate"):
|
||||
Pick.from_dict({"kind": "ratio", "metric": "x"})
|
||||
|
||||
|
||||
def test_pick_missing_metric_raises():
|
||||
with pytest.raises(PickValidationError, match="metric"):
|
||||
Pick.from_dict({"kind": "aggregate"})
|
||||
|
||||
|
||||
def test_pick_group_by_must_be_list_of_strings():
|
||||
with pytest.raises(PickValidationError, match="group_by"):
|
||||
Pick.from_dict({"kind": "aggregate", "metric": "x", "group_by": [1, 2]})
|
||||
|
||||
|
||||
def test_pick_where_must_be_list():
|
||||
with pytest.raises(PickValidationError, match="where must be a list"):
|
||||
Pick.from_dict({"kind": "aggregate", "metric": "x", "where": {"foo": "bar"}})
|
||||
|
||||
|
||||
def test_pick_limit_must_be_int():
|
||||
with pytest.raises(PickValidationError, match="limit"):
|
||||
Pick.from_dict({"kind": "aggregate", "metric": "x", "limit": "ten"})
|
||||
Reference in New Issue
Block a user