feat(api): routes 完整(login + JWT 依赖 + optimize/factor + WS route)

This commit is contained in:
2026-07-06 19:04:26 +08:00
parent f5a69b312a
commit efaac41c06
3 changed files with 267 additions and 64 deletions
+151 -34
View File
@@ -1,7 +1,7 @@
"""
FastAPI routes tests using TestClient
"""
from unittest.mock import Mock, patch
from unittest.mock import Mock, patch, AsyncMock
import pytest
from fastapi.testclient import TestClient
@@ -10,17 +10,21 @@ def test_submit_cta_backtest():
"""Test POST /api/v1/backtest/cta returns task_id"""
# Create app with temporary DB
from sanguo_api.app import create_app
from sanguo_api.auth import set_jwt_config, create_token
import tempfile
import os
set_jwt_config(secret="test", expire_minutes=60)
with tempfile.TemporaryDirectory() as tmp:
db_path = os.path.join(tmp, "test.db")
app = create_app(db_path=db_path, file_dir=None)
token = create_token("admin")
# Mock get_orchestrator to return mock orchestrator
with patch("sanguo_api.routes.get_orchestrator") as mock_get_orch:
mock_orch = Mock()
mock_orch.submit_cta.return_value = "cta_test_123"
mock_orch.submit_cta = AsyncMock(return_value="cta_test_123")
mock_get_orch.return_value = mock_orch
client = TestClient(app)
@@ -32,7 +36,8 @@ def test_submit_cta_backtest():
"params": {"fast": 5, "slow": 20},
"start": "2024-01-01",
"end": "2024-12-31"
}
},
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
@@ -44,21 +49,26 @@ def test_submit_cta_backtest():
def test_get_task_status():
"""Test GET /api/v1/task/{task_id} returns status"""
from sanguo_api.app import create_app
from sanguo_api.auth import set_jwt_config, create_token
from sanguo_orchestrator.task import TaskState
import tempfile
import os
set_jwt_config(secret="test", expire_minutes=60)
with tempfile.TemporaryDirectory() as tmp:
db_path = os.path.join(tmp, "test.db")
app = create_app(db_path=db_path, file_dir=None)
token = create_token("admin")
with patch("sanguo_api.routes.get_orchestrator") as mock_get_orch:
mock_orch = Mock()
mock_orch.get_status.return_value = TaskState.DONE
mock_orch.pool.get_stage.return_value = "完成"
mock_get_orch.return_value = mock_orch
client = TestClient(app)
response = client.get("/api/v1/task/cta_test_123")
response = client.get("/api/v1/task/cta_test_123", headers={"Authorization": f"Bearer {token}"})
assert response.status_code == 200
data = response.json()
@@ -70,12 +80,16 @@ def test_get_task_status():
def test_get_task_status_not_found():
"""Test GET /api/v1/task/{task_id} returns 404 for unknown task"""
from sanguo_api.app import create_app
from sanguo_api.auth import set_jwt_config, create_token
import tempfile
import os
set_jwt_config(secret="test", expire_minutes=60)
with tempfile.TemporaryDirectory() as tmp:
db_path = os.path.join(tmp, "test.db")
app = create_app(db_path=db_path, file_dir=None)
token = create_token("admin")
with patch("sanguo_api.routes.get_orchestrator") as mock_get_orch:
mock_orch = Mock()
@@ -83,7 +97,7 @@ def test_get_task_status_not_found():
mock_get_orch.return_value = mock_orch
client = TestClient(app)
response = client.get("/api/v1/task/unknown_task")
response = client.get("/api/v1/task/unknown_task", headers={"Authorization": f"Bearer {token}"})
assert response.status_code == 404
@@ -91,12 +105,16 @@ def test_get_task_status_not_found():
def test_invalid_params_returns_422():
"""Test POST /api/v1/backtest/cta with missing fields returns 422"""
from sanguo_api.app import create_app
from sanguo_api.auth import set_jwt_config, create_token
import tempfile
import os
set_jwt_config(secret="test", expire_minutes=60)
with tempfile.TemporaryDirectory() as tmp:
db_path = os.path.join(tmp, "test.db")
app = create_app(db_path=db_path, file_dir=None)
token = create_token("admin")
client = TestClient(app)
# Missing required field: strategy
@@ -107,7 +125,8 @@ def test_invalid_params_returns_422():
"params": {"fast": 5, "slow": 20},
"start": "2024-01-01",
"end": "2024-12-31"
}
},
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 422
@@ -116,13 +135,17 @@ def test_invalid_params_returns_422():
def test_get_task_result():
"""Test GET /api/v1/task/{task_id}/result returns statistics"""
from sanguo_api.app import create_app
from sanguo_api.auth import set_jwt_config, create_token
from sanguo_orchestrator.task import TaskState
import tempfile
import os
set_jwt_config(secret="test", expire_minutes=60)
with tempfile.TemporaryDirectory() as tmp:
db_path = os.path.join(tmp, "test.db")
app = create_app(db_path=db_path, file_dir=None)
token = create_token("admin")
with patch("sanguo_api.routes.get_orchestrator") as mock_get_orch:
mock_orch = Mock()
@@ -132,7 +155,7 @@ def test_get_task_result():
mock_get_orch.return_value = mock_orch
client = TestClient(app)
response = client.get("/api/v1/task/cta_test_123/result")
response = client.get("/api/v1/task/cta_test_123/result", headers={"Authorization": f"Bearer {token}"})
assert response.status_code == 200
data = response.json()
@@ -145,12 +168,16 @@ def test_get_task_result():
def test_get_task_result_not_found():
"""Test GET /api/v1/task/{task_id}/result returns 404 when result not ready"""
from sanguo_api.app import create_app
from sanguo_api.auth import set_jwt_config, create_token
import tempfile
import os
set_jwt_config(secret="test", expire_minutes=60)
with tempfile.TemporaryDirectory() as tmp:
db_path = os.path.join(tmp, "test.db")
app = create_app(db_path=db_path, file_dir=None)
token = create_token("admin")
with patch("sanguo_api.routes.get_orchestrator") as mock_get_orch:
mock_orch = Mock()
@@ -158,62 +185,152 @@ def test_get_task_result_not_found():
mock_get_orch.return_value = mock_orch
client = TestClient(app)
response = client.get("/api/v1/task/unknown_task/result")
response = client.get("/api/v1/task/unknown_task/result", headers={"Authorization": f"Bearer {token}"})
assert response.status_code == 404
def test_submit_optimize_returns_pending():
"""Test POST /api/v1/backtest/optimize returns pending placeholder"""
"""Test POST /api/v1/backtest/optimize calls orchestrator submit_optimize"""
from sanguo_api.app import create_app
from sanguo_api.auth import set_jwt_config, create_token
from unittest.mock import AsyncMock
import tempfile
import os
set_jwt_config(secret="test", expire_minutes=60)
with tempfile.TemporaryDirectory() as tmp:
db_path = os.path.join(tmp, "test.db")
app = create_app(db_path=db_path, file_dir=None)
token = create_token("admin")
client = TestClient(app)
response = client.post(
"/api/v1/backtest/optimize",
json={
"symbol": "600000SH",
"strategy": "DoubleSMA",
"grid": {"fast": [5, 10], "slow": [20, 30]},
"start": "2024-01-01",
"end": "2024-12-31",
"max_workers": 2
}
)
with patch("sanguo_api.routes.get_orchestrator") as mock_get_orch:
mock_orch = Mock()
mock_orch.submit_optimize = AsyncMock(return_value="opt_test_123")
mock_get_orch.return_value = mock_orch
client = TestClient(app)
response = client.post(
"/api/v1/backtest/optimize",
json={
"symbol": "600000SH",
"strategy": "DoubleSMA",
"grid": {"fast": [5, 10], "slow": [20, 30]},
"start": "2024-01-01",
"end": "2024-12-31",
"max_workers": 2
},
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
data = response.json()
assert "task_id" in data
assert data["task_id"] == "pending_impl"
assert data["task_id"] == "opt_test_123"
def test_submit_factor_returns_pending():
"""Test POST /api/v1/factor/analyze returns pending placeholder"""
"""Test POST /api/v1/factor/analyze calls orchestrator submit_factor"""
from sanguo_api.app import create_app
from sanguo_api.auth import set_jwt_config, create_token
from unittest.mock import AsyncMock
import tempfile
import os
set_jwt_config(secret="test", expire_minutes=60)
with tempfile.TemporaryDirectory() as tmp:
db_path = os.path.join(tmp, "test.db")
app = create_app(db_path=db_path, file_dir=None)
token = create_token("admin")
client = TestClient(app)
response = client.post(
"/api/v1/factor/analyze",
json={
"symbols": ["600000SH", "000001SZ"],
"factor_names": ["ts_mean_5", "ts_mean_20"],
"start": "2024-01-01",
"end": "2024-12-31"
}
)
with patch("sanguo_api.routes.get_orchestrator") as mock_get_orch:
mock_orch = Mock()
mock_orch.submit_factor = AsyncMock(return_value="factor_test_123")
mock_get_orch.return_value = mock_orch
client = TestClient(app)
response = client.post(
"/api/v1/factor/analyze",
json={
"symbols": ["600000SH", "000001SZ"],
"factor_names": ["ts_mean_5", "ts_mean_20"],
"start": "2024-01-01",
"end": "2024-12-31"
},
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
data = response.json()
assert "task_id" in data
assert data["task_id"] == "pending_impl"
assert data["task_id"] == "factor_test_123"
# ============================================
# NEW TESTS FOR TASK 5 (JWT + LOGIN + OPTIMIZE/FCTOR + WS)
# ============================================
def test_login_returns_token(tmp_path):
"""Test POST /api/v1/auth/login returns JWT token on successful login"""
from sanguo_api.app import create_app
from sanguo_api.auth import set_jwt_config, hash_password
import tempfile
set_jwt_config(secret="test", expire_minutes=60)
with tempfile.TemporaryDirectory() as tmp:
db_path = f"{tmp}/t.db"
app = create_app(db_path=db_path, auth_config={
"username": "admin",
"password_hash": hash_password("pass123"),
"jwt_secret": "test",
"expire_minutes": 60
})
client = TestClient(app)
resp = client.post("/api/v1/auth/login", json={"username": "admin", "password": "pass123"})
assert resp.status_code == 200
assert "token" in resp.json()
def test_protected_route_without_token_401(tmp_path):
"""Test that protected routes return 401 without JWT token"""
from sanguo_api.app import create_app
import tempfile
with tempfile.TemporaryDirectory() as tmp:
db_path = f"{tmp}/t.db"
app = create_app(db_path=db_path)
client = TestClient(app)
resp = client.get("/api/v1/task/t1")
assert resp.status_code == 401
def test_optimize_route_calls_submit(tmp_path):
"""Test POST /api/v1/backtest/optimize calls orchestrator submit_optimize"""
from sanguo_api.app import create_app
from sanguo_api.auth import set_jwt_config, create_token
from unittest.mock import AsyncMock
import tempfile
set_jwt_config(secret="test", expire_minutes=60)
with tempfile.TemporaryDirectory() as tmp:
db_path = f"{tmp}/t.db"
app = create_app(db_path=db_path)
client = TestClient(app)
token = create_token("admin")
with patch("sanguo_api.routes.get_orchestrator") as m:
orch = Mock()
orch.submit_optimize = AsyncMock(return_value="opt_1")
m.return_value = orch
resp = client.post("/api/v1/backtest/optimize", json={
"symbol": "600000", "strategy": "MaStrategy", "grid": {"n": [5, 20, 5]},
"start": "2024-01-01", "end": "2024-06-30", "max_workers": 2
}, headers={"Authorization": f"Bearer {token}"})
assert resp.status_code == 200
assert resp.json()["task_id"] == "opt_1"