Files
stock/backend/smoke_test.py
T
cirry 2f9c8bee2b 日K加载提速:adj_factor 覆盖索引 + 冷路径并发 + 热路径进程内缓存直返
- adj_factor 覆盖索引 (ts_code,trade_date) INCLUDE (adj_factor):根治堆碎片化
  (单股 6516 行散 6516 块,满载时位图堆扫 1.5s+),Index Only Scan ~2ms;
  因子查询全部改 2 列投影,lag() 窗口只取变点(6516→32 行)
- preview 冷路径 2 会话 2 波并发,信息卡合并为 LEFT JOIN LATERAL 一条
- 鉴权会话 60s 进程内缓存(登出/全端登出即时失效),全站请求省 ~80ms
- pvj/chipsj/stocksj/facetsj 存 model_dump_json 原串直返(与 response_model
  字节一致),热路径 230-2190ms → 1-2ms;get_version 本地缓存+bump 即时可见
- 连接池 10+20;smoke_test 适配鉴权缓存
2026-09-02 16:52:28 +08:00

121 lines
4.7 KiB
Python

"""使用线上 PostgreSQL 跑鉴权与核心 API 自检,临时用户会自动清理。
用法:uv run --directory backend python smoke_test.py
"""
from __future__ import annotations
import asyncio
import json
import secrets
from datetime import datetime, timedelta, timezone
import httpx
from sqlalchemy import delete, select
from app.auth import drop_session_cache, hash_password, token_digest
from app.db import async_session, engine
from app.main import app
from app.models import AuthSession, Candle, User
async def main() -> None:
username = f"smoke_{secrets.token_hex(6)}"
password = secrets.token_urlsafe(24)
now = datetime.now(timezone.utc)
async with async_session() as db:
db.add(
User(
username=username,
password_hash=hash_password(password),
password_algo="argon2id",
is_active=True,
failed_login_count=0,
password_changed_at=now,
created_at=now,
updated_at=now,
)
)
await db.commit()
try:
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
health = await client.get("/api/health")
protected = await client.post("/api/backtest", json={})
bad_login = await client.post(
"/api/auth/login", json={"username": username, "password": "wrong-password"}
)
login = await client.post(
"/api/auth/login", json={"username": username, "password": password}
)
print("health:", health.status_code)
print("protected without login:", protected.status_code)
print("bad login:", bad_login.status_code)
print("login:", login.status_code)
assert health.status_code == 200
assert protected.status_code == 401
assert bad_login.status_code == 401
assert login.status_code == 200
assert client.cookies.get("stock_session")
me = await client.get("/api/auth/me")
assert me.status_code == 200 and me.json()["username"] == username
daily = await client.post(
"/api/backtest",
json={
"symbol": "000001",
"strategy": "macd_cross",
"params": {"fast": 12, "slow": 26, "signal": 9},
"initial_cash": 100000.0,
"fast_mode": False,
},
)
assert daily.status_code == 200, daily.text
result = daily.json()
assert len(result["candles"]) > 100
print("backtest metrics:", json.dumps(result["metrics"], ensure_ascii=False))
token = client.cookies.get("stock_session")
assert token
async with async_session() as db:
auth_session = (
await db.execute(
select(AuthSession).where(AuthSession.token_hash == token_digest(token))
)
).scalar_one()
auth_session.expires_at = datetime.now(timezone.utc) - timedelta(seconds=1)
await db.commit()
# 鉴权会话有 60s 进程内缓存(auth.py),会掩盖库里的过期态;
# 清缓存模拟 TTL 已过,验证 require_user 对过期会话本身的判定
drop_session_cache(digest=token_digest(token))
assert (await client.get("/api/auth/me")).status_code == 401
print("expired session: 401")
login = await client.post(
"/api/auth/login", json={"username": username, "password": password}
)
assert login.status_code == 200
logout = await client.post("/api/auth/logout")
assert logout.status_code == 204
assert (await client.get("/api/auth/me")).status_code == 401
print("logout and revoke: ok")
async with async_session() as db:
has_market_data = bool(await db.scalar(select(Candle.id).limit(1)))
print("market data present:", has_market_data)
finally:
async with async_session() as db:
user_id = await db.scalar(select(User.id).where(User.username == username))
if user_id is not None:
await db.execute(delete(AuthSession).where(AuthSession.user_id == user_id))
await db.execute(delete(User).where(User.id == user_id))
await db.commit()
await engine.dispose()
print("temporary user cleaned")
if __name__ == "__main__":
asyncio.run(main())