from __future__ import annotations from pathlib import Path from fastapi.testclient import TestClient from src.ephemeris_mcp.server import ( _parse_datetime, create_app, get_constellation_at_ecliptic, get_moon_phase, get_lunar_state, get_planetary_positions, get_sidereal_time, get_solar_events, list_available_bodies, ) from src.ephemeris_mcp.storage import EphemerisCache, cache_key def test_parse_datetime_accepts_iso_zulu() -> None: jd = _parse_datetime("2026-05-10T12:00:00Z") assert isinstance(jd, float) assert 2461170 < jd < 2461172 def test_cache_roundtrip(tmp_path: Path) -> None: cache = EphemerisCache(tmp_path / "cache.sqlite3") key = cache_key("demo", lon=8.5, lat=47.3) assert cache.get(key) is None cache.set(key, {"ok": True, "value": 42}, ttl=60) assert cache.get(key) == {"ok": True, "value": 42} def test_health_endpoint_smoke() -> None: client = TestClient(create_app()) res = client.get("/health") assert res.status_code == 200 data = res.json() assert data == {"ok": True, "server": "ephemeris-mcp", "port": 7015} def test_root_lists_core_tools() -> None: client = TestClient(create_app()) res = client.get("/") assert res.status_code == 200 data = res.json() assert data["server"] == "ephemeris-mcp" assert data["status"] == "ready" assert data["tools"] == [ "get_planetary_positions", "get_solar_events", "get_lunar_state", "get_moon_phase", "get_sidereal_time", "get_constellation_at_ecliptic", "list_available_bodies", ] assert data["mcp"] == {"sse": "/mcp/sse", "messages": "/mcp/messages"} def test_list_available_bodies_shape() -> None: result = list_available_bodies() assert "bodies" in result names = {body["name"] for body in result["bodies"]} assert "sun" in names assert "moon" in names assert "mars" in names def test_tool_shapes_are_present() -> None: body_positions = get_planetary_positions(datetime="2026-05-10T12:00:00Z") solar_events = get_solar_events(date="2026-05-10", lat=47.0, lon=8.0) lunar_state = get_lunar_state(datetime="2026-05-10T12:00:00Z") sidereal_time = get_sidereal_time(datetime="2026-05-10T12:00:00Z") constellation = get_constellation_at_ecliptic(120.0) assert body_positions["input"]["datetime"] == "2026-05-10T12:00:00Z" assert "bodies" in body_positions assert solar_events["input"]["date"] == "2026-05-10" assert "events_jd" in solar_events assert "lunar_state" in lunar_state assert "greenwich_sidereal_time" in sidereal_time assert constellation["input"]["ecliptic_lon"] == 120.0 def test_moon_phase_tool_shape() -> None: moon_phase = get_moon_phase(datetime="2026-05-10T12:00:00Z") assert moon_phase["phase_name"] assert 0.0 <= moon_phase["illumination_fraction"] <= 1.0 assert moon_phase["input"]["datetime"] == "2026-05-10T12:00:00Z" assert moon_phase["phase_name"] == "Last Quarter" assert moon_phase["next_major_phase"]["phase_name"] in {"New Moon", "First Quarter", "Full Moon", "Last Quarter"} assert "at_utc" in moon_phase["next_major_phase"] assert moon_phase["next_major_phase"]["in_text"] assert "d" in moon_phase["next_major_phase"]["in_text"] or "h" in moon_phase["next_major_phase"]["in_text"] or "m" in moon_phase["next_major_phase"]["in_text"] def test_default_location_can_be_configured(monkeypatch) -> None: monkeypatch.setenv("EPHEMERIS_DEFAULT_LAT", "48.2") monkeypatch.setenv("EPHEMERIS_DEFAULT_LON", "16.37") from importlib import reload import src.ephemeris_mcp.config as config import src.ephemeris_mcp.server as server reload(config) server = reload(server) result = server.get_moon_phase(datetime="2026-05-10T12:00:00Z") assert result["input"]["lat"] == 48.2 assert result["input"]["lon"] == 16.37