P0 修复: - runtimeStore: 添加缺失的 lastDayHistory 字段 - Gateway/RuntimeService: 状态同步改为内存优先,消除 glob 竞态 - App.jsx: 从 3075 行重构到 ~500 行,提取 8 个独立文件 P1 修复: - CORS: 4 个服务改为从环境变量读取允许 origins - MarketStore: 改为模块级单例模式 - Domain 层: 删除 trading thin wrapper,保留 news 真实逻辑 - 测试: 补齐 77 个 gateway/runtime 测试 新增文件: - backend/tests/test_gateway.py (43 tests) - frontend/src/hooks/useWebSocketHandler.js - frontend/src/hooks/useStockRequestCallbacks.js - frontend/src/hooks/useAgentCallbacks.js - frontend/src/hooks/useRuntimeCallbacks.js - frontend/src/hooks/useWatchlistCallbacks.js - frontend/src/components/TickerBar.jsx - frontend/src/components/HeaderRight.jsx - frontend/src/components/ChartTabs.jsx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
217 lines
6.8 KiB
Python
217 lines
6.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Tests for the extracted trading service app surface."""
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from backend.apps.trading_service import create_app
|
|
from shared.schema import CompanyNews, FinancialMetrics, InsiderTrade, LineItem, Price
|
|
|
|
|
|
def test_trading_service_routes_are_exposed():
|
|
app = create_app()
|
|
|
|
paths = {route.path for route in app.routes}
|
|
|
|
assert "/health" in paths
|
|
assert "/api/prices" in paths
|
|
assert "/api/financials" in paths
|
|
assert "/api/news" in paths
|
|
assert "/api/insider-trades" in paths
|
|
assert "/api/market/status" in paths
|
|
assert "/api/market-cap" in paths
|
|
assert "/api/line-items" in paths
|
|
|
|
|
|
def test_trading_service_prices_endpoint(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"backend.apps.trading_service.get_prices",
|
|
lambda ticker, start_date, end_date: [
|
|
Price(
|
|
open=1.0,
|
|
close=2.0,
|
|
high=2.5,
|
|
low=0.5,
|
|
volume=100,
|
|
time="2026-03-20",
|
|
)
|
|
],
|
|
)
|
|
|
|
with TestClient(create_app()) as client:
|
|
response = client.get(
|
|
"/api/prices",
|
|
params={
|
|
"ticker": "AAPL",
|
|
"start_date": "2026-03-01",
|
|
"end_date": "2026-03-20",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["ticker"] == "AAPL"
|
|
assert response.json()["prices"][0]["close"] == 2.0
|
|
|
|
|
|
def test_trading_service_financials_endpoint(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"backend.apps.trading_service.get_financial_metrics",
|
|
lambda ticker, end_date, period, limit: [
|
|
FinancialMetrics(
|
|
ticker=ticker,
|
|
report_period=end_date,
|
|
period=period,
|
|
currency="USD",
|
|
market_cap=123.0,
|
|
enterprise_value=None,
|
|
price_to_earnings_ratio=None,
|
|
price_to_book_ratio=None,
|
|
price_to_sales_ratio=None,
|
|
enterprise_value_to_ebitda_ratio=None,
|
|
enterprise_value_to_revenue_ratio=None,
|
|
free_cash_flow_yield=None,
|
|
peg_ratio=None,
|
|
gross_margin=None,
|
|
operating_margin=None,
|
|
net_margin=None,
|
|
return_on_equity=None,
|
|
return_on_assets=None,
|
|
return_on_invested_capital=None,
|
|
asset_turnover=None,
|
|
inventory_turnover=None,
|
|
receivables_turnover=None,
|
|
days_sales_outstanding=None,
|
|
operating_cycle=None,
|
|
working_capital_turnover=None,
|
|
current_ratio=None,
|
|
quick_ratio=None,
|
|
cash_ratio=None,
|
|
operating_cash_flow_ratio=None,
|
|
debt_to_equity=None,
|
|
debt_to_assets=None,
|
|
interest_coverage=None,
|
|
revenue_growth=None,
|
|
earnings_growth=None,
|
|
book_value_growth=None,
|
|
earnings_per_share_growth=None,
|
|
free_cash_flow_growth=None,
|
|
operating_income_growth=None,
|
|
ebitda_growth=None,
|
|
payout_ratio=None,
|
|
earnings_per_share=None,
|
|
book_value_per_share=None,
|
|
free_cash_flow_per_share=None,
|
|
)
|
|
],
|
|
)
|
|
|
|
with TestClient(create_app()) as client:
|
|
response = client.get(
|
|
"/api/financials",
|
|
params={"ticker": "AAPL", "end_date": "2026-03-20"},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["financial_metrics"][0]["ticker"] == "AAPL"
|
|
|
|
|
|
def test_trading_service_news_and_insider_endpoints(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"backend.apps.trading_service.get_company_news",
|
|
lambda ticker, end_date, start_date=None, limit=1000: [
|
|
CompanyNews(
|
|
ticker=ticker,
|
|
title="News title",
|
|
source="polygon",
|
|
url="https://example.com/news",
|
|
date=end_date,
|
|
)
|
|
],
|
|
)
|
|
monkeypatch.setattr(
|
|
"backend.apps.trading_service.get_insider_trades",
|
|
lambda ticker, end_date, start_date=None, limit=1000: [
|
|
InsiderTrade(ticker=ticker, filing_date=end_date)
|
|
],
|
|
)
|
|
|
|
with TestClient(create_app()) as client:
|
|
news_response = client.get(
|
|
"/api/news",
|
|
params={"ticker": "AAPL", "end_date": "2026-03-20"},
|
|
)
|
|
insider_response = client.get(
|
|
"/api/insider-trades",
|
|
params={"ticker": "AAPL", "end_date": "2026-03-20"},
|
|
)
|
|
|
|
assert news_response.status_code == 200
|
|
assert news_response.json()["news"][0]["title"] == "News title"
|
|
assert insider_response.status_code == 200
|
|
assert insider_response.json()["insider_trades"][0]["ticker"] == "AAPL"
|
|
|
|
|
|
def test_trading_service_market_status_endpoint(monkeypatch):
|
|
class _FakeMarketService:
|
|
def get_market_status(self):
|
|
return {"status": "open", "status_text": "Open"}
|
|
|
|
monkeypatch.setattr(
|
|
"backend.apps.trading_service.MarketService",
|
|
lambda tickers: _FakeMarketService(),
|
|
)
|
|
|
|
with TestClient(create_app()) as client:
|
|
response = client.get("/api/market/status")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == {"status": "open", "status_text": "Open"}
|
|
|
|
|
|
def test_trading_service_market_cap_endpoint(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"backend.apps.trading_service.get_market_cap",
|
|
lambda ticker, end_date: 3.5e12,
|
|
)
|
|
|
|
with TestClient(create_app()) as client:
|
|
response = client.get(
|
|
"/api/market-cap",
|
|
params={"ticker": "AAPL", "end_date": "2026-03-20"},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == {
|
|
"ticker": "AAPL",
|
|
"end_date": "2026-03-20",
|
|
"market_cap": 3.5e12,
|
|
}
|
|
|
|
|
|
def test_trading_service_line_items_endpoint(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"backend.apps.trading_service.search_line_items",
|
|
lambda ticker, line_items, end_date, period, limit: [
|
|
LineItem(
|
|
ticker=ticker,
|
|
report_period=end_date,
|
|
period=period,
|
|
currency="USD",
|
|
free_cash_flow=123.0,
|
|
)
|
|
],
|
|
)
|
|
|
|
with TestClient(create_app()) as client:
|
|
response = client.get(
|
|
"/api/line-items",
|
|
params=[
|
|
("ticker", "AAPL"),
|
|
("line_items", "free_cash_flow"),
|
|
("end_date", "2026-03-20"),
|
|
],
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["search_results"][0]["ticker"] == "AAPL"
|
|
assert response.json()["search_results"][0]["free_cash_flow"] == 123.0
|