from __future__ import annotations import logging from app.logs import RingLogHandler from app.metrics import render_metrics from app.schemas import MultiStatusOut, StrategyStatusOut def make_status(**overrides) -> MultiStatusOut: strategy = StrategyStatusOut( strategy_key="sma_cross", strategy_name="SMA Crossover", params={}, symbol="BTC/USDT", timeframe="5m", allocation=0.5, cash=5000, equity=5100, realized_pnl=100, unrealized_pnl=0, total_pnl=100, open_positions=1, halted=False, trades_today=2, ) base = { "running": True, "mode": "paper", "exchange": "binance", "price": 100.0, "starting_balance": 10000, "equity": 10100, "total_pnl": 100, "strategies": [strategy], } base.update(overrides) return MultiStatusOut(**base) def test_render_metrics_contains_core_series() -> None: body = render_metrics(make_status(), "paper", "binance") assert "crypto_running 1" in body assert "crypto_equity 10100" in body assert "crypto_total_pnl 100" in body assert "crypto_open_positions 1" in body assert 'crypto_strategy_equity{strategy="sma_cross",symbol="BTC/USDT"} 5100' in body assert 'crypto_strategy_allocation{strategy="sma_cross"} 0.5' in body assert 'crypto_info{mode="paper",exchange="binance"} 1' in body def test_render_metrics_omits_price_when_none() -> None: body = render_metrics(make_status(price=None), "paper", "binance") assert "crypto_price" not in body def test_ring_log_handler_captures() -> None: handler = RingLogHandler(capacity=5) logger = logging.getLogger("test.ring") logger.addHandler(handler) logger.setLevel(logging.INFO) try: logger.info("hello %s", "world") entries = handler.snapshot() assert entries[-1]["message"] == "hello world" assert entries[-1]["level"] == "info" finally: logger.removeHandler(handler) def test_ring_log_handler_respects_capacity() -> None: handler = RingLogHandler(capacity=3) logger = logging.getLogger("test.ring.cap") logger.addHandler(handler) logger.setLevel(logging.INFO) try: for i in range(10): logger.info("m%d", i) entries = handler.snapshot() assert len(entries) == 3 assert entries[-1]["message"] == "m9" finally: logger.removeHandler(handler) def test_metrics_endpoint(runtime) -> None: from fastapi.testclient import TestClient from app.main import create_app app = create_app(runtime.settings, runtime) with TestClient(app) as client: response = client.get("/api/metrics") assert response.status_code == 200 assert "crypto_running" in response.text def test_logs_endpoint(runtime) -> None: from fastapi.testclient import TestClient from app.main import create_app app = create_app(runtime.settings, runtime) with TestClient(app) as client: response = client.get("/api/logs") assert response.status_code == 200 assert "entries" in response.json()