mcgibs/tests/test_client.py
Ryan Malloy f7fad32a9e Implement mcgibs FastMCP server for NASA GIBS
Complete implementation of all modules:
- constants.py: GIBS API endpoints, projections, TileMatrixSet defs
- models.py: Pydantic models for layers, colormaps, geocoding
- geo.py: Nominatim geocoding with rate limiting and caching
- capabilities.py: WMTS GetCapabilities XML parser with search
- colormaps.py: Colormap v1.3 parser with natural-language summaries
- client.py: Async GIBS HTTP client wrapping all API interactions
- server.py: FastMCP 3.0 tools, resources, and prompts

11 MCP tools, 3 resources, 2 prompts. 47 tests, all passing.
2026-02-18 14:55:41 -07:00

185 lines
5.5 KiB
Python

"""Tests for GIBSClient using respx mocks — no real HTTP calls."""
from io import BytesIO
import httpx
import respx
from PIL import Image
from mcgibs.client import GIBSClient
from mcgibs.constants import WMTS_TILE_URL
@respx.mock
async def test_client_initialize(capabilities_xml):
"""Loading capabilities populates layer_index with all three sample layers."""
respx.get(url__regex=r".*WMTSCapabilities\.xml").mock(
return_value=httpx.Response(200, text=capabilities_xml)
)
client = GIBSClient()
await client.initialize()
assert len(client.layer_index) == 3
assert "MODIS_Terra_CorrectedReflectance_TrueColor" in client.layer_index
assert "AMSR2_Sea_Ice_Concentration_12km_Monthly" in client.layer_index
assert "AIRS_L3_Surface_Air_Temperature_Daily_Day" in client.layer_index
# Spot-check a parsed layer
modis = client.layer_index["MODIS_Terra_CorrectedReflectance_TrueColor"]
assert modis.title == "Corrected Reflectance (True Color, Terra/MODIS)"
assert "image/jpeg" in modis.formats
assert modis.time is not None
assert modis.time.start == "2000-02-24"
assert modis.has_colormap is False
await client.close()
@respx.mock
async def test_client_fetch_layer_metadata(capabilities_xml, layer_metadata_json):
"""Fetching layer metadata enriches the layer with instrument/platform fields."""
respx.get(url__regex=r".*WMTSCapabilities\.xml").mock(
return_value=httpx.Response(200, text=capabilities_xml)
)
layer_id = "MODIS_Terra_CorrectedReflectance_TrueColor"
respx.get(url__regex=rf".*layer-metadata.*/{layer_id}\.json").mock(
return_value=httpx.Response(200, text=layer_metadata_json)
)
client = GIBSClient()
await client.initialize()
data = await client.fetch_layer_metadata(layer_id)
assert data["instrument"] == "MODIS"
assert data["platform"] == "Terra"
assert data["ongoing"] is True
# Verify the layer_index entry was enriched
layer = client.get_layer(layer_id)
assert layer is not None
assert layer.instrument == "MODIS"
assert layer.platform == "Terra"
assert layer.measurement == "Corrected Reflectance"
assert layer.day_night == "Day"
# Second call should use cache
data2 = await client.fetch_layer_metadata(layer_id)
assert data2 is data
await client.close()
@respx.mock
async def test_client_fetch_colormap(capabilities_xml, colormap_xml):
"""Parsing colormap XML produces entries and legend data."""
respx.get(url__regex=r".*WMTSCapabilities\.xml").mock(
return_value=httpx.Response(200, text=capabilities_xml)
)
respx.get(url__regex=r".*colormaps/v1\.3/.*\.xml").mock(
return_value=httpx.Response(200, text=colormap_xml)
)
client = GIBSClient()
await client.initialize()
colormap_set = await client.fetch_colormap("AIRS_L3_Surface_Air_Temperature_Daily_Day")
assert colormap_set is not None
assert len(colormap_set.maps) == 2
data_map = colormap_set.data_map
assert data_map is not None
assert data_map.title == "Surface Air Temperature"
assert data_map.units == "K"
assert len(data_map.entries) == 14
assert data_map.legend_type == "continuous"
# Verify the nodata map
nodata_map = colormap_set.maps[1]
assert any(e.nodata for e in nodata_map.entries)
# Second fetch should be cached
cached = await client.fetch_colormap("AIRS_L3_Surface_Air_Temperature_Daily_Day")
assert cached is colormap_set
await client.close()
@respx.mock
async def test_client_get_wms_image(capabilities_xml):
"""WMS GetMap returns raw image bytes when content-type is image/*."""
respx.get(url__regex=r".*WMTSCapabilities\.xml").mock(
return_value=httpx.Response(200, text=capabilities_xml)
)
# Build a tiny valid JPEG to return as the mock response
buf = BytesIO()
Image.new("RGB", (10, 10), "blue").save(buf, format="JPEG")
fake_jpeg = buf.getvalue()
respx.get(url__regex=r".*wms\.cgi.*").mock(
return_value=httpx.Response(
200,
content=fake_jpeg,
headers={"content-type": "image/jpeg"},
)
)
client = GIBSClient()
await client.initialize()
from mcgibs.models import BBox
bbox = BBox(west=-120.0, south=30.0, east=-110.0, north=40.0)
result = await client.get_wms_image(
"MODIS_Terra_CorrectedReflectance_TrueColor",
"2025-06-01",
bbox,
)
assert isinstance(result, bytes)
assert len(result) > 0
# Verify the returned bytes are valid JPEG (starts with FFD8)
assert result[:2] == b"\xff\xd8"
await client.close()
def test_client_build_tile_url():
"""build_tile_url produces the expected WMTS REST URL format."""
client = GIBSClient()
url = client.build_tile_url(
layer_id="MODIS_Terra_CorrectedReflectance_TrueColor",
date="2025-06-01",
zoom=3,
row=2,
col=4,
tile_matrix_set="250m",
ext="jpg",
epsg="4326",
)
assert "MODIS_Terra_CorrectedReflectance_TrueColor" in url
assert "2025-06-01" in url
assert "/250m/" in url
assert "/3/" in url
assert "/2/" in url
assert "/4.jpg" in url
assert "epsg4326" in url
# Verify it matches the constant template
expected = WMTS_TILE_URL.format(
epsg="4326",
layer_id="MODIS_Terra_CorrectedReflectance_TrueColor",
date="2025-06-01",
tile_matrix_set="250m",
z=3,
row=2,
col=4,
ext="jpg",
)
assert url == expected