"""Tests for chart rendering and visualization tool registration.""" import pytest from fastmcp import Client from noaa_tides.charts.conditions import render_conditions_html, render_conditions_png from noaa_tides.charts.tides import ( _parse_observed, _parse_predictions, render_tide_chart_html, render_tide_chart_png, ) # Mock data (matches conftest.py fixtures) MOCK_PREDICTIONS = { "predictions": [ {"t": "2026-02-21 04:30", "v": "4.521", "type": "H"}, {"t": "2026-02-21 10:42", "v": "-0.123", "type": "L"}, {"t": "2026-02-21 16:55", "v": "5.012", "type": "H"}, {"t": "2026-02-21 23:08", "v": "0.234", "type": "L"}, ] } MOCK_WATER_LEVEL = { "data": [ {"t": "2026-02-21 00:00", "v": "2.34", "s": "0.003", "f": "0,0,0,0", "q": "p"}, {"t": "2026-02-21 00:06", "v": "2.38", "s": "0.003", "f": "0,0,0,0", "q": "p"}, ] } MOCK_WIND = { "data": [ {"t": "2026-02-21 00:00", "s": "12.5", "d": "225.00", "dr": "SW", "g": "18.2", "f": "0,0"} ] } MOCK_AIR_TEMP = {"data": [{"t": "2026-02-21 00:00", "v": "42.3", "f": "0,0,0"}]} MOCK_WATER_TEMP = {"data": [{"t": "2026-02-21 00:00", "v": "38.7", "f": "0,0,0"}]} MOCK_PRESSURE = {"data": [{"t": "2026-02-21 00:00", "v": "1013.2", "f": "0,0,0"}]} # -- Parsing helpers -- def test_parse_predictions(): preds = MOCK_PREDICTIONS["predictions"] times, values, markers = _parse_predictions(preds) assert len(times) == 4 assert len(values) == 4 assert len(markers) == 4 assert markers[0]["type"] == "H" assert markers[1]["type"] == "L" assert values[0] == pytest.approx(4.521) def test_parse_observed(): obs = MOCK_WATER_LEVEL["data"] times, values = _parse_observed(obs) assert len(times) == 2 assert values[0] == pytest.approx(2.34) def test_parse_observed_skips_blank_values(): data = [ {"t": "2026-02-21 00:00", "v": "2.34"}, {"t": "2026-02-21 00:06", "v": ""}, {"t": "2026-02-21 00:12", "v": None}, ] times, values = _parse_observed(data) assert len(times) == 1 # -- Tide chart rendering -- def test_tide_chart_png_returns_bytes(): preds = MOCK_PREDICTIONS["predictions"] result = render_tide_chart_png(preds, station_name="Test Station") assert isinstance(result, bytes) assert len(result) > 0 # PNG magic bytes assert result[:4] == b"\x89PNG" def test_tide_chart_png_with_observed(): preds = MOCK_PREDICTIONS["predictions"] obs = MOCK_WATER_LEVEL["data"] result = render_tide_chart_png(preds, observed=obs, station_name="Providence") assert isinstance(result, bytes) assert result[:4] == b"\x89PNG" def test_tide_chart_html_returns_string(): preds = MOCK_PREDICTIONS["predictions"] result = render_tide_chart_html(preds, station_name="Test Station") assert isinstance(result, str) assert "" in result.lower() or " dict: """Build a snapshot dict from mock data.""" snapshot = { "station_id": "8454000", "predictions": MOCK_PREDICTIONS, "water_level": MOCK_WATER_LEVEL, "wind": MOCK_WIND, "air_temperature": MOCK_AIR_TEMP, "water_temperature": MOCK_WATER_TEMP, "air_pressure": MOCK_PRESSURE, } snapshot.update(overrides) return snapshot def test_conditions_png_returns_bytes(): snapshot = _build_snapshot() result = render_conditions_png(snapshot, station_name="Providence") assert isinstance(result, bytes) assert result[:4] == b"\x89PNG" def test_conditions_png_partial_data(): """Dashboard should render even with missing products.""" snapshot = _build_snapshot() del snapshot["wind"] del snapshot["air_temperature"] result = render_conditions_png(snapshot, station_name="Providence") assert isinstance(result, bytes) assert result[:4] == b"\x89PNG" def test_conditions_png_empty_snapshot(): """Dashboard with no data produces a placeholder image.""" result = render_conditions_png({"station_id": "8454000"}) assert isinstance(result, bytes) assert result[:4] == b"\x89PNG" def test_conditions_html_returns_string(): snapshot = _build_snapshot() result = render_conditions_html(snapshot, station_name="Providence") assert isinstance(result, str) assert "plotly" in result.lower() def test_conditions_html_empty_snapshot(): result = render_conditions_html({"station_id": "8454000"}) assert isinstance(result, str) assert "No data available" in result # -- Tool registration -- async def test_visualization_tools_registered(mcp_client: Client): """The 2 new visualization tools should appear in the tool list.""" tools = await mcp_client.list_tools() names = {t.name for t in tools} assert "visualize_tides" in names assert "visualize_conditions" in names async def test_total_tool_count(mcp_client: Client): """Verify total tool count after adding visualization tools (7 + 2 = 9).""" tools = await mcp_client.list_tools() assert len(tools) == 9