Files
tick-stock-panel/backend/tests/test_watchlist_groups.py
T
shy3130 c90b83c3e4 feat: v0.2 UI 重构 + 自选分组侧边栏 + 扩展数据时间窗口 + 个股对话框优化
- 侧边栏美化:品牌改名/菜单 active 指示条/收起展开/底部精简
- 数据源+AI 状态卡:名称替换固定文字/档位 tag 条件显示
- 设置页重构:account 合入数据源 tab/数据源置顶/收起展开菜单
- 自选分组侧边栏:二级子菜单/分组跳转同步/清空分组
- 个股对话框:tab 移顶栏/分时关闭/放大全屏/操作按钮归位
- 扩展数据:定时拉取时间窗口(time_window_start/end)
- 版本号: 0.1.88 → 0.2.1
2026-08-10 17:53:14 +08:00

154 lines
5.4 KiB
Python

"""自选分组持久化与 API 契约。"""
from types import SimpleNamespace
from unittest.mock import MagicMock
import polars as pl
import pytest
from fastapi import HTTPException
from app.api import watchlist as watchlist_api
from app.config import settings
from app.services import watchlist
def _request():
repo = MagicMock()
repo.get_name_map.return_value = {}
return SimpleNamespace(app=SimpleNamespace(state=SimpleNamespace(repo=repo)))
def test_historical_watchlist_is_read_as_ungrouped(monkeypatch, tmp_path):
monkeypatch.setattr(settings, "data_dir", tmp_path)
path = tmp_path / "user_data" / "watchlist.parquet"
path.parent.mkdir(parents=True)
pl.DataFrame({
"symbol": ["600000.SH"],
"added_at": ["2026-08-08T10:00:00"],
"note": [""],
}).write_parquet(path)
assert watchlist.list_symbols()[0]["group_id"] is None
def test_group_lifecycle_preserves_watchlist_entries(monkeypatch, tmp_path):
monkeypatch.setattr(settings, "data_dir", tmp_path)
groups, created = watchlist.create_group(" 短线 ", "orange")
assert groups == [{"id": created["id"], "name": "短线", "color": "orange"}]
watchlist.add("600000.SH", group_id=created["id"])
watchlist.add("000001.SZ")
assert watchlist.list_symbols()[1]["group_id"] == created["id"]
renamed = watchlist.rename_group(created["id"], "观察", "fuchsia")
assert renamed[0]["name"] == "观察"
assert renamed[0]["color"] == "fuchsia"
remaining, rows = watchlist.delete_group(created["id"])
assert remaining == []
assert {row["symbol"] for row in rows} == {"600000.SH", "000001.SZ"}
assert all(row["group_id"] is None for row in rows)
def test_group_validation_and_assignment_errors(monkeypatch, tmp_path):
monkeypatch.setattr(settings, "data_dir", tmp_path)
_, created = watchlist.create_group("核心")
watchlist.add("600000.SH")
with pytest.raises(ValueError, match="已存在"):
watchlist.create_group("核心")
with pytest.raises(ValueError, match="颜色"):
watchlist.create_group("无效颜色", "black")
with pytest.raises(ValueError, match="颜色"):
watchlist.rename_group(created["id"], "核心", "black")
with pytest.raises(ValueError, match="不存在"):
watchlist.set_group("600000.SH", "missing")
with pytest.raises(KeyError):
watchlist.set_group("000001.SZ", created["id"])
rows = watchlist.set_group("600000.SH", created["id"])
assert rows[0]["group_id"] == created["id"]
rows = watchlist.set_group("600000.SH", None)
assert rows[0]["group_id"] is None
def test_group_api_contract(monkeypatch, tmp_path):
monkeypatch.setattr(settings, "data_dir", tmp_path)
request = _request()
created = watchlist_api.create_group(
watchlist_api.GroupNameRequest(name="中线", color="teal")
)
group_id = created["group"]["id"]
assert created["group"]["color"] == "teal"
added = watchlist_api.add_one(
watchlist_api.AddRequest(symbol="600000.SH", group_id=group_id),
request,
)
assert added["symbols"][0]["group_id"] == group_id
moved = watchlist_api.assign_group(
"600000.SH",
watchlist_api.GroupAssignRequest(group_id=None),
request,
)
assert moved["symbols"][0]["group_id"] is None
with pytest.raises(HTTPException) as exc_info:
watchlist_api.rename_group("missing", watchlist_api.GroupNameRequest(name="无效"))
assert exc_info.value.status_code == 404
def test_historical_groups_default_to_sky(monkeypatch, tmp_path):
monkeypatch.setattr(settings, "data_dir", tmp_path)
path = tmp_path / "user_data" / "watchlist_groups.json"
path.parent.mkdir(parents=True)
path.write_text(
'[{"id":"legacy","name":"旧分组"},'
'{"id":"invalid","name":"未知颜色","color":"black"}]',
encoding="utf-8",
)
assert watchlist.list_groups() == [
{"id": "legacy", "name": "旧分组", "color": "sky"},
{"id": "invalid", "name": "未知颜色", "color": "sky"},
]
def test_clear_group_moves_members_to_ungrouped(monkeypatch, tmp_path):
"""清空分组:成员变未分组,分组定义保留。"""
monkeypatch.setattr(settings, "data_dir", tmp_path)
_, group = watchlist.create_group("芯片")
watchlist.add("600000.SH", group_id=group["id"])
watchlist.add("000001.SZ", group_id=group["id"])
watchlist.add("300750.SZ") # 不在任何分组
rows = watchlist.clear_group(group["id"])
# 3 只都还在,group_id 全部为 None
assert len(rows) == 3
assert all(r["group_id"] is None for r in rows)
# 分组定义仍在
assert any(g["id"] == group["id"] for g in watchlist.list_groups())
# 清空不存在的分组 → KeyError
with pytest.raises(KeyError):
watchlist.clear_group("missing")
def test_clear_group_api(monkeypatch, tmp_path):
monkeypatch.setattr(settings, "data_dir", tmp_path)
request = _request()
created = watchlist_api.create_group(
watchlist_api.GroupNameRequest(name="中线", color="teal")
)
group_id = created["group"]["id"]
watchlist.add("600000.SH", group_id=group_id)
watchlist.add("000001.SZ", group_id=group_id)
result = watchlist_api.clear_group(group_id, request)
assert all(s["group_id"] is None for s in result["symbols"])
# 不存在的分组 → 404
with pytest.raises(HTTPException) as exc:
watchlist_api.clear_group("missing", request)
assert exc.value.status_code == 404