mcgibs/tests/test_models.py
Ryan Malloy d13ba744d3 Add Hamilton fix test coverage and graceful shutdown
Tests: 79 total (32 new), covering LRU cache eviction, BBox
validation, _parse_rgb error handling, colormap ID extraction,
client retry logic, WMS response validation, dimension clamping,
geocode caching, classification colormaps, and scientific notation
interval parsing.

Shutdown: register atexit handler to close httpx connection pool
when the MCP server exits, since FastMCP Middleware has no
on_shutdown hook.
2026-02-18 18:19:21 -07:00

72 lines
2.2 KiB
Python

"""Tests for mcgibs.models — Pydantic model validation."""
import pytest
from pydantic import ValidationError
from mcgibs.models import BBox, ColorMap, ColorMapEntry, ColorMapSet
# ---------------------------------------------------------------------------
# BBox validation (M3 Hamilton fix)
# ---------------------------------------------------------------------------
def test_bbox_valid():
bbox = BBox(west=-120.0, south=30.0, east=-110.0, north=40.0)
assert bbox.west == -120.0
assert bbox.north == 40.0
def test_bbox_south_greater_than_north():
with pytest.raises(ValidationError, match=r"south.*must be <= north"):
BBox(west=0.0, south=50.0, east=10.0, north=40.0)
def test_bbox_latitude_out_of_range():
with pytest.raises(ValidationError, match="Latitudes must be in"):
BBox(west=0.0, south=-91.0, east=10.0, north=0.0)
def test_bbox_longitude_out_of_range():
with pytest.raises(ValidationError, match="Longitudes must be in"):
BBox(west=-181.0, south=0.0, east=0.0, north=10.0)
def test_bbox_wms_bbox_property():
bbox = BBox(west=-120.0, south=30.0, east=-110.0, north=40.0)
assert bbox.wms_bbox == "-120.0,30.0,-110.0,40.0"
def test_bbox_area_sq_deg():
bbox = BBox(west=0.0, south=0.0, east=10.0, north=10.0)
assert bbox.area_sq_deg == 100.0
def test_bbox_edge_values():
"""Extreme valid values: full globe."""
bbox = BBox(west=-180.0, south=-90.0, east=180.0, north=90.0)
assert bbox.area_sq_deg == 360.0 * 180.0
# ---------------------------------------------------------------------------
# ColorMapSet.data_map property
# ---------------------------------------------------------------------------
def test_data_map_returns_first_non_nodata():
"""data_map should return the first ColorMap that has non-nodata entries."""
nodata_only = ColorMap(
entries=[ColorMapEntry(rgb=(0, 0, 0), nodata=True)],
)
data_map = ColorMap(
title="Real Data",
entries=[ColorMapEntry(rgb=(255, 0, 0), value="[0,1)")],
)
cms = ColorMapSet(maps=[nodata_only, data_map])
assert cms.data_map is data_map
def test_data_map_empty_set():
"""data_map should return None for empty ColorMapSet."""
cms = ColorMapSet(maps=[])
assert cms.data_map is None