defmodule MicrowavepropWeb.WeatherTileControllerTest do use MicrowavepropWeb.ConnCase, async: false alias Microwaveprop.Propagation.ProfilesFile alias Microwaveprop.Weather.GridCache setup do GridCache.clear() on_exit(fn -> GridCache.clear() ProfilesFile.prune_older_than(~U[2099-12-31 23:59:59Z]) end) :ok end test "serves an SVG weather tile for the requested viewport", %{conn: conn} do valid_time = ~U[2026-04-28 12:00:00Z] lat = 33.0 lon = -97.0 ProfilesFile.write!(valid_time, %{ {lat, lon} => %{ surface_temp_c: 22.0, surface_dewpoint_c: 12.0, surface_pressure_mb: 1010.0, hpbl_m: 800.0, pwat_mm: 25.0, profile: [] } }) conn = get( conn, ~p"/weather/tiles/0/0/0?layer=temperature&time=#{DateTime.to_iso8601(valid_time)}" ) assert response(conn, 200) =~ " %{ surface_temp_c: 22.0, surface_dewpoint_c: 12.0, surface_pressure_mb: 1010.0, hpbl_m: 800.0, pwat_mm: 25.0, profile: [] }, {33.125, -97.0} => %{ surface_temp_c: 23.0, surface_dewpoint_c: 13.0, surface_pressure_mb: 1011.0, hpbl_m: 700.0, pwat_mm: 26.0, profile: [] } }) time = DateTime.to_iso8601(valid_time) conn = get( conn, ~p"/weather/cells?time=#{time}&south=32.5&north=33.5&west=-97.5&east=-96.5" ) assert ["application/json; charset=utf-8"] = get_resp_header(conn, "content-type") body = json_response(conn, 200) assert is_list(body["cells"]) assert length(body["cells"]) == 2 assert body["valid_time"] == time first = hd(body["cells"]) assert is_number(first["lat"]) assert is_number(first["lon"]) assert Map.has_key?(first, "temperature") assert Map.has_key?(first, "dewpoint_depression") end test "returns 400 for missing or invalid params", %{conn: conn} do conn = get(conn, ~p"/weather/cells?time=not-a-time") assert json_response(conn, 400) end test "returns empty cells when no data exists for the valid_time", %{conn: conn} do valid_time = ~U[2099-04-28 12:00:00Z] time = DateTime.to_iso8601(valid_time) conn = get( conn, ~p"/weather/cells?time=#{time}&south=32.0&north=34.0&west=-98.0&east=-96.0" ) body = json_response(conn, 200) assert body["cells"] == [] end test "with ?layers=foo,bar only includes those fields per cell", %{conn: conn} do valid_time = ~U[2026-04-28 12:00:00Z] ProfilesFile.write!(valid_time, %{ {33.0, -97.0} => %{ surface_temp_c: 22.0, surface_dewpoint_c: 12.0, surface_pressure_mb: 1010.0, hpbl_m: 800.0, pwat_mm: 25.0, profile: [] } }) time = DateTime.to_iso8601(valid_time) conn = get( conn, ~p"/weather/cells?time=#{time}&south=32.0&north=34.0&west=-98.0&east=-96.0&layers=temperature,pwat" ) body = json_response(conn, 200) [cell | _] = body["cells"] assert Map.has_key?(cell, "lat") assert Map.has_key?(cell, "lon") assert Map.has_key?(cell, "temperature") assert Map.has_key?(cell, "pwat") refute Map.has_key?(cell, "dewpoint_depression") refute Map.has_key?(cell, "surface_rh") end test "with ?format=bin returns the binary cell pack", %{conn: conn} do valid_time = ~U[2026-04-28 12:00:00Z] ProfilesFile.write!(valid_time, %{ {33.0, -97.0} => %{ surface_temp_c: 22.0, surface_dewpoint_c: 12.0, surface_pressure_mb: 1010.0, hpbl_m: 800.0, pwat_mm: 25.0, profile: [] }, {33.125, -97.0} => %{ surface_temp_c: 23.0, surface_dewpoint_c: 13.0, surface_pressure_mb: 1011.0, hpbl_m: 700.0, pwat_mm: 26.0, profile: [] } }) time = DateTime.to_iso8601(valid_time) conn = get( conn, ~p"/weather/cells?time=#{time}&south=32.0&north=34.0&west=-98.0&east=-96.0&layers=temperature,pwat&format=bin" ) assert ["application/octet-stream"] = get_resp_header(conn, "content-type") body = response(conn, 200) # Magic + version + cell count + layer count + 2 layer names + lats + lons + 2 fields assert <<"WCEL", 1::8, cell_count::little-32, 2::8, rest::binary>> = body assert cell_count == 2 # Two layer names: "temperature" (11), "pwat" (4) <<11::8, "temperature", 4::8, "pwat", padded_rest::binary>> = rest # The header so far is: 4(magic) + 1 + 4 + 1 + (1+11) + (1+4) = 27 bytes. # Pad to 4-byte boundary: 1 byte of zero padding → 28 bytes. <<0::8, lats_and_more::binary>> = padded_rest <> = lats_and_more assert_in_delta lat1, 33.0, 0.001 assert_in_delta lat2, 33.125, 0.001 <<_lon1::little-float-32, _lon2::little-float-32, _rest::binary>> = rest_after_lats end end end