feat(crm): add whitelist datetime list sort helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-09-08 21:37:35 +08:00
1 parent b731c728a7
commit e6af68a2c3
2 files changed
+79

No files matched your search

+27
View File
@@ -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()
+52
View File
@@ -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