alphax/tests/test_chat_assistant.py
2026-05-21 09:58:52 +08:00

149 lines
6.1 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

from datetime import datetime, timedelta
import pandas as pd
from fastapi.testclient import TestClient
from app.db import auth_db, chat_assistant_db
from app.services import chat_assistant
from app.web import web_server
def _login_user(email: str = "chat-user@example.com", password: str = "StrongPass123", monkeypatch=None) -> str:
if monkeypatch is not None:
monkeypatch.setattr(auth_db, "is_smtp_configured", lambda: False)
reg = auth_db.register_user(email, password)
auth_db.verify_email(email, reg["verification_code"])
user = auth_db.get_user_by_email(email)
auth_db.claim_free_trial(user["id"])
return auth_db.login_user(email, password)["token"]
def _fake_ohlcv(rows=180, start=100.0):
now = datetime(2026, 5, 1, 12, 0, 0)
data = []
price = start
for i in range(rows):
ts = now + timedelta(minutes=i * 15)
open_p = price
close_p = price * (1 + 0.001)
high = max(open_p, close_p) * 1.004
low = min(open_p, close_p) * 0.996
vol = 1000 + i * 3
data.append([ts, open_p, high, low, close_p, vol])
price = close_p
return pd.DataFrame(data, columns=["timestamp", "open", "high", "low", "close", "volume"])
def test_chat_page_and_bootstrap_require_subscription():
token = _login_user("chat-page@example.com")
client = TestClient(web_server.app)
client.cookies.set("altcoin_session", token)
page = client.get("/chat")
boot = client.get("/api/chat/bootstrap")
assert page.status_code == 200
assert "Crypto 研究助手" in page.text
assert boot.status_code == 200
assert "suggested_prompts" in boot.json()
def test_single_coin_chat_fetches_multi_timeframe_technicals(monkeypatch):
token = _login_user("chat-coin@example.com", monkeypatch=monkeypatch)
seen = []
def fake_fetch(symbol, timeframe, limit=160):
seen.append((symbol, timeframe))
return _fake_ohlcv(rows=180, start=100.0)
monkeypatch.setattr(chat_assistant, "fetch_binance_klines", fake_fetch)
monkeypatch.setattr(chat_assistant, "_call_chat_llm", lambda message, context, history=None: {"status": "skipped", "error": "disabled"})
client = TestClient(web_server.app)
client.cookies.set("altcoin_session", token)
resp = client.post("/api/chat/send", json={"message": "分析 SUI/USDT 现在的技术面"})
assert resp.status_code == 200
data = resp.json()
assert data["intent"] == "coin_analysis"
assert data["assistant_message"]["content"]["answer_style"] == "technical"
assert data["symbol"] == "SUI/USDT"
assert {tf for _, tf in seen} >= {"15m", "1h", "4h", "1d"}
tfs = data["assistant_message"]["context"]["technicals"]["timeframes"]
assert tfs["15m"]["available"] is True
assert tfs["1h"]["pa"]["zone_count"] >= 0
def test_non_crypto_question_is_rejected(monkeypatch):
token = _login_user("chat-scope@example.com", monkeypatch=monkeypatch)
monkeypatch.setattr(chat_assistant, "_call_chat_llm", lambda message, context, history=None: {"status": "skipped", "error": "disabled"})
client = TestClient(web_server.app)
client.cookies.set("altcoin_session", token)
resp = client.post("/api/chat/send", json={"message": "帮我写一份旅游攻略"})
assert resp.status_code == 200
data = resp.json()
assert data["intent"] == "unsupported"
assert "只能回答加密货币" in data["answer"]["summary"]
def test_chat_rejects_paper_trading_questions(monkeypatch):
token = _login_user("chat-restricted@example.com", monkeypatch=monkeypatch)
monkeypatch.setattr(chat_assistant, "_call_chat_llm", lambda message, context, history=None: {"status": "skipped", "error": "disabled"})
client = TestClient(web_server.app)
client.cookies.set("altcoin_session", token)
resp = client.post("/api/chat/send", json={"message": "帮我看一下策略交易开仓和收益"})
assert resp.status_code == 200
data = resp.json()
assert data["intent"] == "restricted"
assert "内部策略交易数据不可在智能问答中直接访问" in data["answer"]["summary"]
assert data["answer"]["evidence"] == []
def test_chat_user_memory_tracks_last_symbol(monkeypatch):
token = _login_user("chat-memory@example.com", monkeypatch=monkeypatch)
user = auth_db.get_user_by_email("chat-memory@example.com")
monkeypatch.setattr(chat_assistant, "fetch_binance_klines", lambda symbol, timeframe, limit=160: _fake_ohlcv())
monkeypatch.setattr(chat_assistant, "_call_chat_llm", lambda message, context, history=None: {"status": "skipped", "error": "disabled"})
client = TestClient(web_server.app)
client.cookies.set("altcoin_session", token)
client.post("/api/chat/send", json={"message": "分析 LINK/USDT 技术面"})
resp = client.post("/api/chat/send", json={"message": "那它现在追高风险大吗?"})
prefs = chat_assistant_db.get_user_preferences(user["id"])
assert prefs["last_symbol"] == "LINK/USDT"
assert resp.json()["symbol"] == "LINK/USDT"
def test_chat_repairs_llm_mojibake_output(monkeypatch):
token = _login_user("chat-mojibake@example.com", monkeypatch=monkeypatch)
monkeypatch.setattr(chat_assistant, "fetch_binance_klines", lambda symbol, timeframe, limit=160: _fake_ohlcv())
monkeypatch.setattr(
chat_assistant,
"_call_chat_llm",
lambda message, context, history=None: {
"status": "success",
"model": "mock",
"content": {
"summary": "BTC/USDT 当前技术面偏弱",
"answer": "结论:BTC 短线偏弱",
"evidence": ["证据:1h 低于 MA20"],
},
},
)
client = TestClient(web_server.app)
client.cookies.set("altcoin_session", token)
resp = client.post("/api/chat/send", json={"message": "分析 BTC/USDT 技术面"})
assert resp.status_code == 200
data = resp.json()
content = data["assistant_message"]["content"]
assert "当前技术面偏弱" in content["summary"]
assert "结论BTC 短线偏弱" in content["answer"]
assert "证据1h 低于 MA20" in content["evidence"][0]