mcgibs/tests/test_colormaps.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

144 lines
4.6 KiB
Python

"""Tests for mcgibs.colormaps -- XML parsing and natural-language explanations."""
import pytest
from mcgibs.colormaps import (
_describe_rgb,
_parse_interval_value,
explain_colormap,
parse_colormap,
)
# ---------------------------------------------------------------------------
# parse_colormap
# ---------------------------------------------------------------------------
def test_parse_colormap_map_count(colormap_xml: str):
"""Sample XML contains exactly 2 ColorMap elements."""
result = parse_colormap(colormap_xml)
assert len(result.maps) == 2
def test_parse_colormap_data_entries(colormap_xml: str):
"""First ColorMap has 14 data entries, correct title and units."""
result = parse_colormap(colormap_xml)
first = result.maps[0]
assert len(first.entries) == 14
assert first.title == "Surface Air Temperature"
assert first.units == "K"
def test_parse_colormap_nodata_entry(colormap_xml: str):
"""Second ColorMap has a nodata entry labelled 'Missing Data'."""
result = parse_colormap(colormap_xml)
second = result.maps[1]
nodata = [e for e in second.entries if e.nodata]
assert len(nodata) == 1
assert nodata[0].label == "Missing Data"
def test_parse_colormap_rgb_values(colormap_xml: str):
"""First entry of the first ColorMap has rgb (227, 245, 255)."""
result = parse_colormap(colormap_xml)
first_entry = result.maps[0].entries[0]
assert first_entry.rgb == (227, 245, 255)
def test_parse_colormap_value_intervals(colormap_xml: str):
"""First entry value is '[-INF,200.0)', last entry is '[320.0,+INF)'."""
result = parse_colormap(colormap_xml)
entries = result.maps[0].entries
assert entries[0].value == "[-INF,200.0)"
assert entries[-1].value == "[320.0,+INF)"
def test_parse_colormap_legend(colormap_xml: str):
"""First ColorMap has legend entries with expected tooltips."""
result = parse_colormap(colormap_xml)
legend = result.maps[0].legend
assert len(legend) == 5
tooltips = [le.tooltip for le in legend]
assert "< 200 K" in tooltips
assert "200 - 210 K" in tooltips
assert "> 320 K" in tooltips
# ---------------------------------------------------------------------------
# _parse_interval_value
# ---------------------------------------------------------------------------
def test_parse_interval_value_bounded():
"""Bounded interval '[200.0,200.5)' parses to (200.0, 200.5)."""
assert _parse_interval_value("[200.0,200.5)") == (200.0, 200.5)
def test_parse_interval_value_neg_inf():
"""Negative infinity '[-INF,200.0)' parses to (None, 200.0)."""
assert _parse_interval_value("[-INF,200.0)") == (None, 200.0)
def test_parse_interval_value_pos_inf():
"""Positive infinity '[320.0,+INF)' parses to (320.0, None)."""
assert _parse_interval_value("[320.0,+INF)") == (320.0, None)
def test_parse_interval_value_single():
"""Single value '[42]' parses to (42.0, 42.0)."""
assert _parse_interval_value("[42]") == (42.0, 42.0)
# ---------------------------------------------------------------------------
# explain_colormap
# ---------------------------------------------------------------------------
def test_explain_colormap_includes_title(colormap_xml: str):
"""Explanation text contains the layer title."""
cms = parse_colormap(colormap_xml)
text = explain_colormap(cms)
assert "Surface Air Temperature" in text
def test_explain_colormap_includes_units(colormap_xml: str):
"""Explanation mentions the native unit and Celsius conversion."""
cms = parse_colormap(colormap_xml)
text = explain_colormap(cms)
assert "(K)" in text
assert "C)" in text # Celsius conversion appears as e.g. "(-73 C)"
def test_explain_colormap_nodata_mention(colormap_xml: str):
"""Explanation mentions 'Missing Data' from the nodata entry."""
cms = parse_colormap(colormap_xml)
text = explain_colormap(cms)
assert "Missing Data" in text
# ---------------------------------------------------------------------------
# _describe_rgb
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(
("rgb", "expected_substring"),
[
((255, 0, 0), "red"),
((0, 128, 0), "green"),
((0, 0, 255), "blue"),
((255, 255, 255), "white"),
((0, 0, 0), "black"),
((255, 255, 0), "yellow"),
],
)
def test_describe_rgb_basic(rgb: tuple[int, int, int], expected_substring: str):
"""Known RGB triples produce color names containing the expected word."""
result = _describe_rgb(rgb)
assert expected_substring in result.lower()