Files
group_xinghuo_jinrong/tests/test_trade_flow_service.py
T
zhanghongyu_0626 647c07062e feat(trade): Enhance trade processing and context handling in customer service
- Introduced `pending_trade` handling in the chat API to manage trade requests more effectively.
- Updated the `submit_trade_api` to allow advisors to access customer trades based on assigned roles.
- Added new methods in `GatewayRepository` for managing core holdings during trade subscriptions and redemptions.
- Implemented context-aware trade dialogue management in the customer service layer to improve user experience during multi-turn interactions.
- Enhanced the tool service to support trade actions and suitability checks, ensuring accurate processing of user requests.

This update significantly improves the trade interaction flow, providing a more robust and user-friendly experience for customers engaging in trading activities.
2026-09-13 17:45:04 +08:00

177 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import pytest
from app.repository.core_ro import CoreReadOnlyRepository
from app.service.trade_action_service import (
build_trade_draft,
looks_like_trade_execute,
should_use_suitability_instead_of_trade,
subscribe_suitability_block_text,
)
from app.service.trade_flow_service import (
clear_trade_flow,
process_trade_turn,
trade_dialogue_should_continue,
)
from app.tool.core_ro_tool import extract_r_levels, query_suitability
def test_reply_one_continues_trade_from_context_without_redis(monkeypatch):
class BrokenRedis:
def get(self, _key):
return None
def setex(self, *_a, **_k):
raise OSError("redis down")
def delete(self, *_a):
return 0
monkeypatch.setattr("app.service.trade_flow_service._redis", lambda: BrokenRedis())
ctx = (
"assistant: 请选择要申购的产品(仅列出与您风评匹配、可购买的产品):\n"
"1. 稳健债基 A(PROD-110022)\n"
"2. 现金宝货币(PROD-000001)\n"
"请回复序号(如 1);若都不对,请直接输入产品全称或代码。"
)
assert trade_dialogue_should_continue("sess-x", "1", ctx) is True
draft = process_trade_turn("sess-x", "CUST-9527", "1", ctx)
assert draft.get("ok") is False
hint = draft.get("hint") or ""
assert "暂时无法回答" not in hint
assert "稳健债基" in hint or "金额" in hint or "申购" in hint
def test_reply_one_continues_from_purchasable_catalog_footer(monkeypatch):
"""选购清单用「请回复上方序号」,须识别为交易菜单续轮(非 fallback)。"""
class BrokenRedis:
def get(self, _key):
return None
def setex(self, *_a, **_k):
raise OSError("redis down")
def delete(self, *_a):
return 0
monkeypatch.setattr("app.service.trade_flow_service._redis", lambda: BrokenRedis())
ctx = (
"assistant: 按您当前风评C3(平衡型),在售产品清单如下:\n"
"【R1】\n"
"1. 现金宝货币(PROD-000001)— 可直接申购\n"
"【R2】\n"
"2. 稳健债基(PROD-110022)— 可直接申购\n"
"\n请回复上方序号(如 2)选择要申购的产品。"
)
assert trade_dialogue_should_continue("sess-catalog", "1", ctx) is True
draft = process_trade_turn("sess-catalog", "CUST-9527", "1", ctx)
assert draft.get("ok") is False
hint = draft.get("hint") or ""
assert "暂时无法回答" not in hint
assert "稳健债基" in hint or "金额" in hint or "申购" in hint
def test_want_buy_one_asks_amount_not_one_yuan(monkeypatch):
class BrokenRedis:
def get(self, _key):
return None
def setex(self, *_a, **_k):
raise OSError("redis down")
def delete(self, *_a):
return 0
monkeypatch.setattr("app.service.trade_flow_service._redis", lambda: BrokenRedis())
ctx = (
"assistant: 在售产品清单如下:\n"
"1. 现金宝货币(PROD-000001)— 可直接申购\n"
"请回复上方序号(如 2)选择要申购的产品。"
)
draft = process_trade_turn("sess-loose", "CUST-9527", "我想买1", ctx)
assert draft.get("ok") is False
hint = draft.get("hint") or ""
assert "金额" in hint
assert draft.get("payload", {}).get("amount") != 1.0
def test_r4_catalog_with_subscribe_not_trade_execute():
msg = "有哪些r4产品我想申购"
assert should_use_suitability_instead_of_trade(msg) is True
assert looks_like_trade_execute(msg) is False
def test_medical_subscribe_runs_suitability_not_product_menu():
ro = CoreReadOnlyRepository()
draft = process_trade_turn(
"sess-med-1",
"CUST-9527",
"我想申购《医药健康精选》",
"",
ro=ro,
)
assert draft.get("ok") is False
hint = draft.get("hint") or ""
assert "请选择要申购的产品" not in hint
assert "金额" not in hint or "不支持在此直接" in hint
assert "理财师" in hint or "代理人" in hint or "转人工" in hint
def test_extract_r_levels_multi():
assert extract_r_levels("有哪些 r4 和 r5 产品") == ["R4", "R5"]
def test_query_suitability_lists_r4_r5_products():
ro = CoreReadOnlyRepository()
res = query_suitability("CUST-9527", user_message="有哪些R4和R5产品?", repo=ro)
assert res["ok"] is True
text = res["fact_text"]
assert "【R4】" in text
assert "【R5】" in text
assert "PROD-" in text
def test_subscribe_two_turn_style_combined():
ro = CoreReadOnlyRepository()
ctx = "用户:我要申购"
draft = process_trade_turn("sess-test-1", "CUST-9527", "稳健债基 A,申购1万元", ctx, ro=ro)
if not draft.get("ok"):
pytest.skip("product seed mismatch")
assert draft["payload"]["trade_type"] == "subscribe"
assert draft["payload"]["amount"] == 10000.0
def test_build_subscribe_with_chinese_product_name():
ro = CoreReadOnlyRepository()
draft = build_trade_draft("稳健债基 A,申购1万元", "CUST-9527", ro=ro)
if not draft.get("ok"):
pytest.skip("product seed mismatch")
assert draft["payload"]["trade_type"] == "subscribe"
def test_trade_flow_menu_pick(monkeypatch):
class FakeRedis:
def __init__(self):
self.store = {}
def get(self, key):
return self.store.get(key)
def setex(self, key, _ttl, val):
self.store[key] = val
def delete(self, key):
self.store.pop(key, None)
fake = FakeRedis()
monkeypatch.setattr("app.service.trade_flow_service._redis", lambda: fake)
sid = "sess-menu-1"
clear_trade_flow(sid)
ro = CoreReadOnlyRepository()
r1 = process_trade_turn(sid, "CUST-9527", "我要申购", "", ro=ro)
assert r1.get("ok") is False
assert "请选择" in r1.get("hint", "") or "金额" in r1.get("hint", "") or "产品" in r1.get("hint", "")
clear_trade_flow(sid)