feat(crm): add whitelist datetime list sort helper.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
b731c728a7
commit
e6af68a2c3
2 files changed
+79
No files matched your search
@@ -0,0 +1,27 @@
|
||||
"""CRM 列表日期列排序:白名单 prop → SQLAlchemy order 表达式。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping, Optional
|
||||
|
||||
|
||||
def resolve_datetime_order_by(
|
||||
sort_field: Optional[str],
|
||||
sort_order: Optional[str],
|
||||
whitelist: Mapping[str, Any],
|
||||
default,
|
||||
):
|
||||
"""
|
||||
sort_field: 前端 camelCase prop(如 createTime)
|
||||
sort_order: asc | desc(大小写不敏感)
|
||||
whitelist: prop → Column / InstrumentedAttribute
|
||||
非法或缺参 → 返回 default(通常 create_time.desc())
|
||||
MySQL 对 NULL:ASC 前、DESC 后,与产品决策一致,无需额外 IS NULL。
|
||||
"""
|
||||
field = (sort_field or "").strip()
|
||||
order = (sort_order or "").strip().lower()
|
||||
if not field or order not in ("asc", "desc"):
|
||||
return default
|
||||
col = whitelist.get(field)
|
||||
if col is None:
|
||||
return default
|
||||
return col.asc() if order == "asc" else col.desc()
|
||||
@@ -0,0 +1,52 @@
|
||||
# backend/tests/test_crm_list_sort.py
|
||||
from sqlalchemy import Column, DateTime, MetaData, String, Table
|
||||
|
||||
from app.services.crm.list_sort import resolve_datetime_order_by
|
||||
|
||||
|
||||
def _fake_columns():
|
||||
md = MetaData()
|
||||
t = Table(
|
||||
"t",
|
||||
md,
|
||||
Column("id", String(64)),
|
||||
Column("create_time", DateTime),
|
||||
Column("deal_time", DateTime),
|
||||
)
|
||||
return {
|
||||
"createTime": t.c.create_time,
|
||||
"dealTime": t.c.deal_time,
|
||||
}, t.c.create_time.desc()
|
||||
|
||||
|
||||
def test_valid_asc():
|
||||
wl, default = _fake_columns()
|
||||
expr = resolve_datetime_order_by("createTime", "asc", wl, default)
|
||||
assert "ASC" in str(expr).upper() or getattr(expr, "modifier", None) is not None
|
||||
# 更稳:比较 clause 指向白名单列
|
||||
assert expr.element is wl["createTime"] or list(expr.element.base_columns)[0] is list(wl["createTime"].base_columns)[0]
|
||||
|
||||
|
||||
def test_valid_desc():
|
||||
wl, default = _fake_columns()
|
||||
expr = resolve_datetime_order_by("dealTime", "desc", wl, default)
|
||||
assert expr.element.key == "deal_time" or "deal_time" in str(expr)
|
||||
|
||||
|
||||
def test_invalid_field_falls_back():
|
||||
wl, default = _fake_columns()
|
||||
expr = resolve_datetime_order_by("hack", "asc", wl, default)
|
||||
assert expr is default
|
||||
|
||||
|
||||
def test_invalid_order_falls_back():
|
||||
wl, default = _fake_columns()
|
||||
expr = resolve_datetime_order_by("createTime", "up", wl, default)
|
||||
assert expr is default
|
||||
|
||||
|
||||
def test_missing_falls_back():
|
||||
wl, default = _fake_columns()
|
||||
assert resolve_datetime_order_by(None, None, wl, default) is default
|
||||
assert resolve_datetime_order_by("createTime", None, wl, default) is default
|
||||
assert resolve_datetime_order_by(None, "asc", wl, default) is default
|
||||
Reference in new issue
Block a user