feat: import 6 MCP services + common workspace
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import pytest_httpx
|
||||
from mcp_macro.fetchers import (
|
||||
fetch_economic_indicators,
|
||||
fetch_macro_calendar,
|
||||
fetch_market_overview,
|
||||
)
|
||||
|
||||
# --- fetch_economic_indicators ---
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_economic_indicators_no_key():
|
||||
result = await fetch_economic_indicators(fred_api_key="")
|
||||
assert "error" in result
|
||||
assert result["error"] == "No FRED API key configured"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_economic_indicators_happy_path(httpx_mock: pytest_httpx.HTTPXMock):
|
||||
for series_id in ("FEDFUNDS", "CPIAUCSL", "UNRATE", "DGS10"):
|
||||
httpx_mock.add_response(
|
||||
url=httpx.URL(
|
||||
"https://api.stlouisfed.org/fred/series/observations",
|
||||
params={
|
||||
"series_id": series_id,
|
||||
"api_key": "testkey",
|
||||
"file_type": "json",
|
||||
"sort_order": "desc",
|
||||
"limit": "1",
|
||||
},
|
||||
),
|
||||
json={"observations": [{"value": "5.25"}]},
|
||||
)
|
||||
result = await fetch_economic_indicators(fred_api_key="testkey")
|
||||
assert result["fed_rate"] == 5.25
|
||||
assert result["cpi"] == 5.25
|
||||
assert result["unemployment"] == 5.25
|
||||
assert result["us10y_yield"] == 5.25
|
||||
assert "updated_at" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_economic_indicators_filter(httpx_mock: pytest_httpx.HTTPXMock):
|
||||
httpx_mock.add_response(
|
||||
url=httpx.URL(
|
||||
"https://api.stlouisfed.org/fred/series/observations",
|
||||
params={
|
||||
"series_id": "FEDFUNDS",
|
||||
"api_key": "k",
|
||||
"file_type": "json",
|
||||
"sort_order": "desc",
|
||||
"limit": "1",
|
||||
},
|
||||
),
|
||||
json={"observations": [{"value": "5.33"}]},
|
||||
)
|
||||
result = await fetch_economic_indicators(fred_api_key="k", indicators=["fed_rate"])
|
||||
assert "fed_rate" in result
|
||||
assert "cpi" not in result
|
||||
|
||||
|
||||
# --- fetch_macro_calendar ---
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_macro_calendar_forex_factory_happy(httpx_mock: pytest_httpx.HTTPXMock):
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
future = (datetime.now(UTC) + timedelta(days=1)).isoformat()
|
||||
httpx_mock.add_response(
|
||||
url="https://nfs.faireconomy.media/ff_calendar_thisweek.json",
|
||||
json=[
|
||||
{
|
||||
"date": future,
|
||||
"title": "CPI",
|
||||
"country": "US",
|
||||
"impact": "High",
|
||||
"forecast": "3.0%",
|
||||
"previous": "3.2%",
|
||||
}
|
||||
],
|
||||
)
|
||||
result = await fetch_macro_calendar()
|
||||
assert "events" in result
|
||||
assert len(result["events"]) >= 1
|
||||
assert result["events"][0]["name"] == "CPI"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_macro_calendar_no_source(httpx_mock: pytest_httpx.HTTPXMock):
|
||||
httpx_mock.add_response(
|
||||
url="https://nfs.faireconomy.media/ff_calendar_thisweek.json",
|
||||
status_code=500,
|
||||
)
|
||||
result = await fetch_macro_calendar(finnhub_api_key="")
|
||||
assert result == {"events": [], "note": "No calendar source available"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.httpx_mock(assert_all_responses_were_requested=False, assert_all_requests_were_expected=False)
|
||||
async def test_macro_calendar_finnhub_fallback(httpx_mock: pytest_httpx.HTTPXMock):
|
||||
httpx_mock.add_response(
|
||||
url="https://nfs.faireconomy.media/ff_calendar_thisweek.json",
|
||||
status_code=500,
|
||||
)
|
||||
|
||||
def dispatch(request: httpx.Request) -> httpx.Response:
|
||||
if "finnhub.io" in str(request.url):
|
||||
return httpx.Response(
|
||||
200,
|
||||
json=[{"date": "2024-01-15", "event": "FOMC", "importance": "high", "forecast": "", "prev": ""}],
|
||||
)
|
||||
return httpx.Response(500)
|
||||
|
||||
httpx_mock.add_callback(dispatch)
|
||||
result = await fetch_macro_calendar(finnhub_api_key="fkey")
|
||||
assert "events" in result
|
||||
assert result["events"][0]["name"] == "FOMC"
|
||||
|
||||
|
||||
# --- fetch_market_overview ---
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_market_overview_happy(httpx_mock: pytest_httpx.HTTPXMock):
|
||||
httpx_mock.add_response(
|
||||
url="https://api.coingecko.com/api/v3/global",
|
||||
json={
|
||||
"data": {
|
||||
"market_cap_percentage": {"btc": 52.3},
|
||||
"total_market_cap": {"usd": 2_000_000_000_000},
|
||||
}
|
||||
},
|
||||
)
|
||||
httpx_mock.add_response(
|
||||
url=httpx.URL(
|
||||
"https://api.coingecko.com/api/v3/simple/price",
|
||||
params={"ids": "bitcoin,ethereum", "vs_currencies": "usd"},
|
||||
),
|
||||
json={"bitcoin": {"usd": 65000}, "ethereum": {"usd": 3500}},
|
||||
)
|
||||
import re as _re
|
||||
httpx_mock.add_response(
|
||||
url=_re.compile(
|
||||
r"https://www\.deribit\.com/api/v2/public/get_volatility_index_data\?currency=BTC.*"
|
||||
),
|
||||
json={"result": {"data": [[1, 50, 52, 49, 51.5]], "continuation": None}},
|
||||
)
|
||||
httpx_mock.add_response(
|
||||
url=_re.compile(
|
||||
r"https://www\.deribit\.com/api/v2/public/get_volatility_index_data\?currency=ETH.*"
|
||||
),
|
||||
json={"result": {"data": [[1, 60, 62, 59, 61.2]], "continuation": None}},
|
||||
)
|
||||
import re as _re
|
||||
httpx_mock.add_response(
|
||||
url=_re.compile(r"https://query1\.finance\.yahoo\.com/v8/finance/chart/\^GSPC.*"),
|
||||
json={"chart": {"result": [{"meta": {"regularMarketPrice": 5830.12}}]}},
|
||||
)
|
||||
httpx_mock.add_response(
|
||||
url=_re.compile(r"https://query1\.finance\.yahoo\.com/v8/finance/chart/GC[%=].*"),
|
||||
json={"chart": {"result": [{"meta": {"regularMarketPrice": 2412.5}}]}},
|
||||
)
|
||||
httpx_mock.add_response(
|
||||
url=_re.compile(r"https://query1\.finance\.yahoo\.com/v8/finance/chart/\^VIX.*"),
|
||||
json={"chart": {"result": [{"meta": {"regularMarketPrice": 18.3}}]}},
|
||||
)
|
||||
# Clear module cache to force fresh fetch
|
||||
from mcp_macro import fetchers as _f
|
||||
_f._MARKET_CACHE["data"] = None
|
||||
_f._MARKET_CACHE["ts"] = 0.0
|
||||
result = await fetch_market_overview()
|
||||
assert result["btc_dominance"] == 52.3
|
||||
assert result["btc_price"] == 65000
|
||||
assert result["eth_price"] == 3500
|
||||
assert result["total_market_cap"] == 2_000_000_000_000
|
||||
assert result["dvol_btc"] == 51.5
|
||||
assert result["dvol_eth"] == 61.2
|
||||
assert result["sp500"] == 5830.12
|
||||
assert result["gold"] == 2412.5
|
||||
assert result["vix"] == 18.3
|
||||
assert "data_timestamp" in result
|
||||
@@ -0,0 +1,127 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from mcp_macro.server import create_app
|
||||
from option_mcp_common.auth import Principal, TokenStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def http():
|
||||
store = TokenStore(
|
||||
tokens={
|
||||
"ct": Principal("core", {"core"}),
|
||||
"ot": Principal("observer", {"observer"}),
|
||||
}
|
||||
)
|
||||
app = create_app(fred_api_key="testfred", finnhub_api_key="testfinn", token_store=store)
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
# --- Health ---
|
||||
|
||||
def test_health(http):
|
||||
assert http.get("/health").status_code == 200
|
||||
|
||||
|
||||
# --- get_economic_indicators ---
|
||||
|
||||
def test_get_economic_indicators_core_ok(http):
|
||||
with patch(
|
||||
"mcp_macro.server.fetch_economic_indicators",
|
||||
new=AsyncMock(return_value={"fed_rate": 5.25, "updated_at": "2024-01-01T00:00:00+00:00"}),
|
||||
):
|
||||
r = http.post(
|
||||
"/tools/get_economic_indicators",
|
||||
headers={"Authorization": "Bearer ct"},
|
||||
json={},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.json()["fed_rate"] == 5.25
|
||||
|
||||
|
||||
def test_get_economic_indicators_observer_ok(http):
|
||||
with patch(
|
||||
"mcp_macro.server.fetch_economic_indicators",
|
||||
new=AsyncMock(return_value={"fed_rate": 5.25}),
|
||||
):
|
||||
r = http.post(
|
||||
"/tools/get_economic_indicators",
|
||||
headers={"Authorization": "Bearer ot"},
|
||||
json={},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
def test_get_economic_indicators_no_auth_401(http):
|
||||
r = http.post("/tools/get_economic_indicators", json={})
|
||||
assert r.status_code == 401
|
||||
|
||||
|
||||
# --- get_macro_calendar ---
|
||||
|
||||
def test_get_macro_calendar_core_ok(http):
|
||||
with patch(
|
||||
"mcp_macro.server.fetch_macro_calendar",
|
||||
new=AsyncMock(return_value={"events": []}),
|
||||
):
|
||||
r = http.post(
|
||||
"/tools/get_macro_calendar",
|
||||
headers={"Authorization": "Bearer ct"},
|
||||
json={"days": 7},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
def test_get_macro_calendar_observer_ok(http):
|
||||
with patch(
|
||||
"mcp_macro.server.fetch_macro_calendar",
|
||||
new=AsyncMock(return_value={"events": []}),
|
||||
):
|
||||
r = http.post(
|
||||
"/tools/get_macro_calendar",
|
||||
headers={"Authorization": "Bearer ot"},
|
||||
json={},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
def test_get_macro_calendar_no_auth_401(http):
|
||||
r = http.post("/tools/get_macro_calendar", json={})
|
||||
assert r.status_code == 401
|
||||
|
||||
|
||||
# --- get_market_overview ---
|
||||
|
||||
def test_get_market_overview_core_ok(http):
|
||||
with patch(
|
||||
"mcp_macro.server.fetch_market_overview",
|
||||
new=AsyncMock(return_value={"btc_dominance": 52.0, "btc_price": 65000}),
|
||||
):
|
||||
r = http.post(
|
||||
"/tools/get_market_overview",
|
||||
headers={"Authorization": "Bearer ct"},
|
||||
json={},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.json()["btc_price"] == 65000
|
||||
|
||||
|
||||
def test_get_market_overview_observer_ok(http):
|
||||
with patch(
|
||||
"mcp_macro.server.fetch_market_overview",
|
||||
new=AsyncMock(return_value={"btc_dominance": 52.0}),
|
||||
):
|
||||
r = http.post(
|
||||
"/tools/get_market_overview",
|
||||
headers={"Authorization": "Bearer ot"},
|
||||
json={},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
def test_get_market_overview_no_auth_401(http):
|
||||
r = http.post("/tools/get_market_overview", json={})
|
||||
assert r.status_code == 401
|
||||
Reference in New Issue
Block a user