diff --git a/tests/smoke/README.md b/tests/smoke/README.md index c140951..4b0996a 100644 --- a/tests/smoke/README.md +++ b/tests/smoke/README.md @@ -1,25 +1,26 @@ # Smoke locale Cerbero_mcp ```bash -# da repo root Cerbero_mcp/ -docker compose up -d -bash tests/smoke/run.sh -docker compose down +# da repo root, con il server attivo (docker compose up -d) +PORT=9000 TESTNET_TOKEN="$TESTNET_TOKEN" bash tests/smoke/run.sh +MAINNET_TOKEN="$MAINNET_TOKEN" bash tests/smoke/run_unified.sh ``` -Il file `run.sh` verifica: -- `/health` di tutti i 6 MCP (atteso `200`) -- `environment_info` dei 4 exchange (atteso shape `{environment, source, env_value, base_url, max_leverage}`) -- live tool check read-only contro upstream testnet: - - deribit `get_ticker BTC-PERPETUAL` - - bybit `get_ticker BTCUSDT` (linear) - - hyperliquid `get_ticker BTC` - - alpaca `get_clock` (richiede credenziali paper valide) - - macro `get_treasury_yields` - - sentiment `get_funding_rates BTC` +## `run.sh` — endpoint pubblici + un tool per-exchange +Verifica `/health`, `/apidocs`, `/openapi.json` (schema bearer) e, se +`TESTNET_TOKEN` è settato, che un tool senza bearer dia `401` e con bearer +testnet venga instradato (deribit `get_ticker BTC-PERPETUAL`). + +## `run_unified.sh` — interfaccia comune `/mcp` (upstream reali) +Verifica end-to-end contro Deribit/Hyperliquid (endpoint pubblici): +- `get_instruments exchange=deribit` → `fees` e `history_start` popolati live +- `get_instruments` fan-out → contribuiscono sia deribit sia hyperliquid +- `get_historical` deribit + hyperliquid → candele non vuote, schema uniforme +- `get_historical exchange=bybit` → `422` (rifiutato a livello schema) Variabili di ambiente: -- `GATEWAY` — URL base gateway (default `http://localhost`; in produzione `https://cerbero-mcp.tielogic.xyz`) -- `TOKEN_FILE` — path al token bearer di lettura (default `secrets/observer.token`) +- `PORT` (default `9000`) o `GATEWAY` (URL base, default `http://localhost:PORT`) +- `MAINNET_TOKEN` / `TESTNET_TOKEN` — bearer (uno dei due basta; mainnet per dati reali) +- `BOT_TAG` (default `smoke`) — header `X-Bot-Tag` -Exit code 0 = tutto OK, 1 = uno o più check falliti. +Exit code 0 = tutto OK, 1 = uno o più check falliti, 0 con skip se nessun token. diff --git a/tests/smoke/run_unified.sh b/tests/smoke/run_unified.sh new file mode 100755 index 0000000..59ba99e --- /dev/null +++ b/tests/smoke/run_unified.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Smoke test interfaccia comune /mcp contro un server vivo + upstream reali. +# get_instruments e get_historical di Deribit/Hyperliquid sono pubblici, ma il +# bearer (testnet o mainnet) e X-Bot-Tag sono comunque richiesti dall'auth. +set -euo pipefail + +PORT="${PORT:-9000}" +BASE="${GATEWAY:-http://localhost:${PORT}}" +TOKEN="${MAINNET_TOKEN:-${TESTNET_TOKEN:-}}" +TAG="${BOT_TAG:-smoke}" + +if [[ -z "${TOKEN}" ]]; then + echo "==> skip: né MAINNET_TOKEN né TESTNET_TOKEN settati" + exit 0 +fi + +PY="$(command -v python3 || command -v python)" +auth=(-H "Authorization: Bearer ${TOKEN}" -H "X-Bot-Tag: ${TAG}" -H "Content-Type: application/json") +start="$(date -u -d '7 days ago' +%F)" +end="$(date -u +%F)" + +# post PATH JSON_BODY EXPECTED_STATUS PY_ASSERT +post() { + local path="$1" body="$2" want="$3" assert="${4:-}" + local resp status out + resp="$(curl -s -w '\n%{http_code}' -X POST "${BASE}${path}" "${auth[@]}" -d "${body}")" + status="$(printf '%s' "${resp}" | tail -n1)" + out="$(printf '%s' "${resp}" | sed '$d')" + if [[ "${status}" != "${want}" ]]; then + echo " FAIL ${path}: atteso HTTP ${want}, got ${status}" + printf '%s\n' "${out}" | head -c 400; echo + exit 1 + fi + if [[ -n "${assert}" ]]; then + printf '%s' "${out}" | "${PY}" -c "import sys,json; d=json.load(sys.stdin); ${assert}" \ + || { echo " FAIL ${path}: assert"; exit 1; } + fi + echo " OK ${path} → HTTP ${status}" +} + +echo "==> get_instruments exchange=deribit (fees + history_start live)" +post "/mcp/tools/get_instruments" '{"exchange":"deribit","currency":"BTC","kind":"future"}' 200 \ + "assert d['instruments'], 'no instruments'; i=d['instruments'][0]; assert i['exchange']=='deribit'; assert 'fees' in i and 'history_start' in i; print(' ', i['symbol'], i['fees'], i['history_start'])" + +echo "==> get_instruments fan-out (deribit + hyperliquid)" +post "/mcp/tools/get_instruments" '{}' 200 \ + "exs={i['exchange'] for i in d['instruments']}; assert {'deribit','hyperliquid'} <= exs, exs; print(' venues:', sorted(exs), '| failed:', d['failed_sources'])" + +echo "==> get_historical deribit BTC-PERPETUAL 1h" +post "/mcp/tools/get_historical" "{\"exchange\":\"deribit\",\"instrument\":\"BTC-PERPETUAL\",\"interval\":\"1h\",\"start_date\":\"${start}\",\"end_date\":\"${end}\"}" 200 \ + "assert d['exchange']=='deribit'; assert d['candles'], 'no candles'; c=d['candles'][0]; assert {'timestamp','open','high','low','close','volume'} <= set(c); print(' candles:', len(d['candles']))" + +echo "==> get_historical hyperliquid BTC 1h" +post "/mcp/tools/get_historical" "{\"exchange\":\"hyperliquid\",\"instrument\":\"BTC\",\"interval\":\"1h\",\"start_date\":\"${start}\",\"end_date\":\"${end}\"}" 200 \ + "assert d['exchange']=='hyperliquid'; assert d['candles'], 'no candles'; print(' candles:', len(d['candles']))" + +echo "==> get_historical exchange non supportato → 422" +post "/mcp/tools/get_historical" "{\"exchange\":\"bybit\",\"instrument\":\"BTCUSDT\",\"interval\":\"1h\",\"start_date\":\"${start}\",\"end_date\":\"${end}\"}" 422 + +echo "==> SMOKE /mcp OK"