Spaces:
Running
Running
| """End-to-end test for Retro Alpha API (LLM endpoints only — game runs in browser).""" | |
| import os | |
| import sys | |
| # Force UTF-8 stdout for ₹ symbol on Windows | |
| try: | |
| sys.stdout.reconfigure(encoding="utf-8") | |
| except Exception: | |
| pass | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from fastapi.testclient import TestClient | |
| import app as app_module | |
| client = TestClient(app_module.app) | |
| PASSED = 0 | |
| FAILED = 0 | |
| def check(name, condition, detail=""): | |
| global PASSED, FAILED | |
| if condition: | |
| PASSED += 1 | |
| print(f" PASS: {name}") | |
| else: | |
| FAILED += 1 | |
| print(f" FAIL: {name} {detail}") | |
| print("=== Static assets ===") | |
| r = client.get("/") | |
| check("homepage 200", r.status_code == 200) | |
| check("homepage has RETRO ALPHA", "RETRO ALPHA" in r.text) | |
| check("homepage loads engine.js", "/static/engine.js" in r.text) | |
| check("homepage loads events.js", "/static/events.js" in r.text) | |
| check("homepage loads app.js", "/static/app.js" in r.text) | |
| check("homepage loads style.css", "/static/style.css" in r.text) | |
| check("homepage has chat panel", "chat-panel" in r.text) | |
| check("homepage has positions table", "positions-table" in r.text) | |
| check("homepage has market watch", "market-watch" in r.text) | |
| r = client.get("/static/style.css") | |
| check("style.css 200", r.status_code == 200) | |
| check("style.css has scanlines", "scanlines" in r.text) | |
| check("style.css has kite-grid", "kite-grid" in r.text) | |
| r = client.get("/static/engine.js") | |
| check("engine.js 200", r.status_code == 200) | |
| check("engine.js has newGame", "newGame" in r.text) | |
| check("engine.js has advanceMonth", "advanceMonth" in r.text) | |
| check("engine.js has localAgentDecide", "localAgentDecide" in r.text) | |
| r = client.get("/static/events.js") | |
| check("events.js 200", r.status_code == 200) | |
| check("events.js has eventForMonth", "eventForMonth" in r.text) | |
| check("events.js has dot-com", "Dot-com" in r.text) | |
| check("events.js has 9/11", "9/11" in r.text) | |
| r = client.get("/static/app.js") | |
| check("app.js 200", r.status_code == 200) | |
| check("app.js uses RetroEngine", "RetroEngine" in r.text) | |
| print("\n=== Health ===") | |
| r = client.get("/api/health") | |
| check("health 200", r.status_code == 200) | |
| h = r.json() | |
| check("health status ok", h.get("status") == "ok") | |
| check("health reports llm", "llm" in h) | |
| print("\n=== Game endpoints removed (state is local now) ===") | |
| for ep in ["/api/state", "/api/trade", "/api/advance", "/api/reset"]: | |
| r = client.get(ep) if ep == "/api/state" else client.post(ep) | |
| check(f"{ep} returns 404 (removed)", r.status_code == 404, f"got {r.status_code}") | |
| print("\n=== /api/chat ===") | |
| r = client.post("/api/chat", json={"message": "should I buy Nifty?", "snapshot": { | |
| "cash": 500000, "total_value": 1000000, "unrealized_pnl": 0, | |
| "positions": [{"asset": "Gold", "qty": 1.0, "price": 3000, "value": 3000}], | |
| }}) | |
| check("chat 200", r.status_code == 200) | |
| data = r.json() | |
| check("chat has reply", "reply" in data and len(data["reply"]) > 0) | |
| # Regression: no raw "error: format only" leaks from the mock | |
| check("chat never returns 'error: format only'", | |
| "error: format only" not in data["reply"], f"reply='{data['reply']}'") | |
| check("chat reply looks like real commentary (has ₹ or words)", | |
| ("₹" in data["reply"] or len(data["reply"].split()) >= 4), | |
| f"reply='{data['reply']}'") | |
| r = client.post("/api/chat", json={"message": "", "snapshot": {}}) | |
| check("empty message rejected", r.status_code == 400) | |
| print("\n=== /api/insight ===") | |
| r = client.post("/api/insight", json={ | |
| "event": {"headline": "Test crash", "regime": "market_crash"}, | |
| "snapshot": {"unrealized_pnl": -100000, "cash": 500000, "total_value": 900000}, | |
| }) | |
| check("insight 200", r.status_code == 200) | |
| data = r.json() | |
| check("insight has text", "insight" in data and len(data["insight"]) > 0) | |
| check("insight never returns 'error: format only'", | |
| "error: format only" not in data["insight"], f"insight='{data['insight']}'") | |
| print("\n=== /api/mentor ===") | |
| r = client.post("/api/mentor", json={"summary": { | |
| "year": 1995, "month": 4, "starting_value": 1000000, "ending_value": 1500000, | |
| "invested_value": 800000, "cash": 700000, "unrealized_pnl": 200000, | |
| "max_drawdown": -0.15, "sharpe_ratio": 1.2, | |
| "allocations": {"fd": 0.2, "nifty_50": 0.4, "gold": 0.1, "crypto": 0.1}, | |
| }}) | |
| check("mentor 200", r.status_code == 200) | |
| data = r.json() | |
| rev = data.get("review", {}) | |
| check("mentor has roast", "roast" in rev and rev["roast"] != "Could not parse review.") | |
| check("mentor has lesson", "lesson" in rev) | |
| check("mentor has suggestion", "suggestion" in rev) | |
| check("no Parse error leak", "Parse error" not in rev.get("lesson", "")) | |
| print(f"\n{'='*40}") | |
| print(f"PASSED: {PASSED} FAILED: {FAILED}") | |
| print(f"{'='*40}") | |
| if FAILED: | |
| print("SOME TESTS FAILED") | |
| sys.exit(1) | |
| else: | |
| print("ALL E2E TESTS PASSED") | |