后端: - 拆分出 agent_service, runtime_service, trading_service, news_service - Gateway 模块化拆分 (gateway_*.py) - 添加 domains/ 领域层 - 新增 control_client, runtime_client - 更新 start-dev.sh 支持 split 服务模式 前端: - 完善 API 服务层 (newsApi, tradingApi) - 更新 vite.config.js - Explain 组件优化 测试: - 添加多个服务 app 测试 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
232 lines
7.5 KiB
Python
232 lines
7.5 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.domains.trading.get_prices_payload",
|
|
lambda ticker, start_date, end_date: {
|
|
"ticker": ticker,
|
|
"prices": [
|
|
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.domains.trading.get_financials_payload",
|
|
lambda ticker, end_date, period, limit: {
|
|
"financial_metrics": [
|
|
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.domains.trading.get_news_payload",
|
|
lambda ticker, end_date, start_date=None, limit=1000: {
|
|
"news": [
|
|
CompanyNews(
|
|
ticker=ticker,
|
|
title="News title",
|
|
source="polygon",
|
|
url="https://example.com/news",
|
|
date=end_date,
|
|
)
|
|
]
|
|
},
|
|
)
|
|
monkeypatch.setattr(
|
|
"backend.domains.trading.get_insider_trades_payload",
|
|
lambda ticker, end_date, start_date=None, limit=1000: {
|
|
"insider_trades": [
|
|
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.domains.trading.get_market_status_payload",
|
|
lambda: _FakeMarketService().get_market_status(),
|
|
)
|
|
|
|
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.domains.trading.get_market_cap_payload",
|
|
lambda ticker, end_date: {
|
|
"ticker": ticker,
|
|
"end_date": end_date,
|
|
"market_cap": 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.domains.trading.get_line_items_payload",
|
|
lambda ticker, line_items, end_date, period, limit: {
|
|
"search_results": [
|
|
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
|