"""Isolated browser acceptance server: temporary database, synthetic upstream, no real network.""" import os import tempfile from contextlib import asynccontextmanager from datetime import datetime, timedelta, timezone import httpx from cryptography.fernet import Fernet from app.config import Settings from app.main import create_app from app.models import Base from app.worldquant import WqClient from tests.ai_fake import fake_model from tests.backtest_fake import Platform from tests.catalog_fake import catalog_response from tests.research_metadata_fake import response as research_metadata_response TEST_PASSWORD = "browser-test-password" def sample(index): super_alpha = index % 7 == 0 language = "PYTHON" if index % 11 == 0 and not super_alpha else "FASTEXPR" names = [ "短期价格反转", "成交量与价格背离", "分析师预期修正", "行业中性动量", "低波动质量信号", "资金流向偏离", "组合选择实验", ] return { "id": f"TEST{index:04}", "name": names[index % len(names)] + f" · {index:03}", "type": "SUPER" if super_alpha else "REGULAR", "stage": "OS" if index % 3 == 0 else "IS", "status": "ACTIVE" if index % 3 == 0 else "UNSUBMITTED", "hidden": index % 5 == 0, "regular": None if super_alpha else { "code": "def alpha(data):\n return -data.close.rank()" if language == "PYTHON" else "group_neutralize(-rank(ts_delta(close, 5)), industry)" }, "selection": {"code": "self_correlation < 0.5"} if super_alpha else None, "combo": {"code": "alpha"} if super_alpha else None, "settings": { "region": ["USA", "CHN", "EUR"][(index // 3) % 3], "universe": "TOP3000", "language": language, "delay": 1, "decay": 4, "neutralization": "INDUSTRY", "truncation": 0.08, "pasteurization": "ON", }, "is": { "sharpe": None if index % 13 == 0 else round(0.8 + (index % 25) * 0.1, 2), "fitness": round(0.3 + (index % 20) * 0.06, 2), "turnover": round(0.08 + (index % 12) * 0.015, 3), "returns": 0.09, "margin": 0.0002, "drawdown": 0.11, "checks": [{"name": "LOW_SHARPE", "result": "PASS", "value": 2.1, "limit": 1.58}], }, "os": {"sharpe": 1.1} if index % 3 == 0 else None, "dateCreated": (datetime(2025, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=index)).isoformat(), "dateSubmitted": (datetime(2025, 2, 1, tzinfo=timezone.utc) + timedelta(days=index % 2)).isoformat() if index % 3 == 0 else None, } def create_test_app(): temporary = tempfile.TemporaryDirectory(prefix="wq-browser-test-") settings = Settings( _env_file=None, database_url=f"sqlite+aiosqlite:///{temporary.name}/test.db", admin_password=TEST_PASSWORD, mcp_enabled=True, encryption_key=Fernet.generate_key().decode(), public_origin=os.environ.get("BROWSER_TEST_ORIGIN", "http://127.0.0.1:5179"), ) records = [sample(i) for i in range(620)] simulations = Platform() simulations.existing_alpha_ids = [f"TEST{i:04}" for i in range(1, 100)] def upstream(request): metadata = research_metadata_response(request) if metadata is not None: return metadata path = request.url.path if path == "/authentication" and request.method == "POST": return httpx.Response( 201, json={ "user": {"id": "TEST_USER"}, "token": {"expiry": 14400}, "permissions": [ "CONSULTANT", "SUPER_ALPHA", "MULTI_SIMULATION", "PROD_ALPHAS", "BRAIN_LABS", "BRAIN_LABS_JUPYTER_LAB", "REFERRAL", "VISUALIZATION", "WORKDAY", "BEFORE_AND_AFTER_PERFORMANCE_V2", ], }, headers={"Set-Cookie": "mock=only; Path=/"}, ) if request.method == "OPTIONS": return catalog_response(request) or httpx.Response(404) if path.startswith("/simulations") or ( path.startswith("/alphas/") and path.rsplit("/", 1)[-1] in simulations.alphas ): return simulations(request) if request.method != "GET": raise AssertionError("Browser acceptance attempted an upstream mutation") catalog = catalog_response(request) if catalog is not None: return catalog if path == "/users/self": return httpx.Response( 200, json={ "id": "TEST_USER", "fullName": "模拟研究员", "email": "test@example.com", "level": "CONSULTANT", "geniusLevel": "GOLD", "verified": True, "approved": True, "dateCreated": "2025-01-01T00:00:00Z", }, ) if path == "/users/self/alphas/summary": return httpx.Response(200, json={"unsubmitted": 413, "active": 207, "decommissioned": 0}) if path in ("/users/self/activities/simulations", "/users/self/activities/submissions"): from zoneinfo import ZoneInfo today = datetime.now(ZoneInfo("America/New_York")).date() return httpx.Response( 200, json={ "yesterday": {"end": (today - timedelta(days=1)).isoformat(), "value": 2}, "total": {"value": 207}, "records": { "schema": {"properties": [{"name": "date"}, {"name": "value"}]}, "records": [[today.isoformat(), 3]], }, }, ) if path == "/users/self/alphas": unsubmitted = "status" in request.url.params hidden = request.url.params.get("hidden") == "true" matched = [ r for r in records if (r["status"] == "UNSUBMITTED") == unsubmitted and r["hidden"] == hidden ] for field in ("dateCreated", "dateSubmitted"): if field + ">=" in request.url.params: return httpx.Response(400, json=["Expected ISO 8601 datetime with timezone"]) for suffix in (">", "<"): boundary = request.url.params.get(field + suffix) if boundary: bound = datetime.fromisoformat(boundary).replace(tzinfo=timezone.utc) matched = [ r for r in matched if r.get(field) and ( datetime.fromisoformat(r[field]) >= bound if suffix == ">" else datetime.fromisoformat(r[field]) <= bound ) ] offset, limit = ( int(request.url.params.get("offset", 0)), int(request.url.params.get("limit", 100)), ) return httpx.Response( 200, json={"count": len(matched), "results": matched[offset : offset + limit]} ) if path.endswith("/recordsets/pnl"): return httpx.Response( 200, json={ "schema": {"properties": [{"name": "date"}, {"name": "pnl"}]}, "records": [ [ (datetime(2025, 1, 1) + timedelta(days=i)).date().isoformat(), i * 250 + (i % 13) * 85, ] for i in range(180) ], }, ) record = next((r for r in records if path == f"/alphas/{r['id']}"), None) return httpx.Response(200, json=record) if record else httpx.Response(404) application = create_app( settings, WqClient(settings, transport=httpx.MockTransport(upstream)), ai_model_factory=fake_model ) # Test-only fixture, excluded from production images by .dockerignore. from fastapi import Depends from app.mcp_api.auth import SCOPES, create_token from app.security import require_auth @application.post("/api/v1/test/mcp-token", dependencies=[Depends(require_auth)]) async def synthetic_token(): async with application.state.sessions.begin() as db: _, token = await create_token(db, "browser-fixture", SCOPES) return {"token": token} original_lifespan = application.router.lifespan_context @asynccontextmanager async def lifespan(app): async with app.state.engine.begin() as connection: await connection.run_sync(Base.metadata.create_all) async with original_lifespan(app): yield temporary.cleanup() application.router.lifespan_context = lifespan return application