60 lines
1.8 KiB
Python
60 lines
1.8 KiB
Python
"""C-04 亏损阈值提醒服务单测。"""
|
|||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from decimal import Decimal
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from app.service import threshold_service as ts
|
||
|
|
|
||
|
|
|
||
|
|
def test_parse_loss_threshold_pct():
|
||
|
|
assert ts.parse_loss_threshold_pct("亏10%就提醒我") == Decimal("10")
|
||
|
|
assert ts.parse_loss_threshold_pct("亏损 12.5% 通知我") == Decimal("12.5")
|
||
|
|
assert ts.parse_loss_threshold_pct("没有数字") is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_portfolio_pnl_pct_weighted():
|
||
|
|
rows = [
|
||
|
|
{"market_value": 60000, "pnl_pct": -12.0},
|
||
|
|
{"market_value": 40000, "pnl_pct": -8.0},
|
||
|
|
]
|
||
|
|
pnl = ts.portfolio_pnl_pct(rows)
|
||
|
|
assert pnl is not None
|
||
|
|
assert round(pnl, 2) == -10.4
|
||
|
|
|
||
|
|
|
||
|
|
def test_build_threshold_alert_when_breached(monkeypatch):
|
||
|
|
logs: list[dict] = []
|
||
|
|
|
||
|
|
class FakeRepo:
|
||
|
|
def list_enabled(self, cid):
|
||
|
|
return [{
|
||
|
|
"id": 7,
|
||
|
|
"scope_type": "portfolio",
|
||
|
|
"loss_threshold_pct": Decimal("10"),
|
||
|
|
}]
|
||
|
|
|
||
|
|
def insert_notify_log(self, **kwargs):
|
||
|
|
logs.append(kwargs)
|
||
|
|
|
||
|
|
monkeypatch.setattr(ts, "ThresholdRepository", FakeRepo)
|
||
|
|
holdings = [{"market_value": 100000, "pnl_pct": -15.0}]
|
||
|
|
alert = ts.build_threshold_alert("CUST-1", holdings, trace_id="t1")
|
||
|
|
assert alert and "阈值提醒" in alert
|
||
|
|
assert logs and logs[0]["customer_id"] == "CUST-1"
|
||
|
|
|
||
|
|
|
||
|
|
def test_build_threshold_alert_skips_when_within_threshold(monkeypatch):
|
||
|
|
class FakeRepo:
|
||
|
|
def list_enabled(self, cid):
|
||
|
|
return [{"id": 1, "scope_type": "portfolio", "loss_threshold_pct": Decimal("20")}]
|
||
|
|
|
||
|
|
def insert_notify_log(self, **kwargs):
|
||
|
|
raise AssertionError("should not notify")
|
||
|
|
|
||
|
|
monkeypatch.setattr(ts, "ThresholdRepository", FakeRepo)
|
||
|
|
holdings = [{"market_value": 100000, "pnl_pct": -5.0}]
|
||
|
|
assert ts.build_threshold_alert("CUST-1", holdings) is None
|