towerops/test/towerops_web/live/coverage_live/show_test.exs
Graham McIntire 0e2cc6b7ce chore(coverage,types): clean dialyzer + new tests + ETH canopy provider
Dialyzer:
* Add Lidar.Tile @type, narrow several @specs to match success-typing,
  prefix discarded function returns with `_ =`, fix the broken
  File.stream! call in the ms-buildings worker (it was causing dialyzer
  to mark every private fun unreachable), broaden Antenna.spec key
  types from `float()` to `number()` to match catalog int literals.
* Add :geo, :geo_postgis, :db_connection to plt_add_apps so the dep
  PLT actually contains the modules we use.
* Result: 0 unsuppressed dialyzer warnings (was 50+ in project code).

Tree canopy:
* Replace the dead landfire.gov URL with the ETH Zurich Global
  Canopy Height 10 m product. ETH publishes proper COGs with
  overviews on libdrive.ethz.ch — confirmed working with
  /vsicurl/ byte-range reads. The module now picks the 3°×3° tile
  containing the bbox centroid, with both a `:metres` decoder
  (ETH Byte raster, NoData=255) and a `:landfire_evh` decoder for
  operators self-hosting LANDFIRE EVH COGs.
* Re-enable canopy by default now that there's a working source.

Tests (+105 tests, 0 failures):
* CoverageLive Index/Show/Form/Map — empty/loaded/auth states,
  LiveView event handlers, formatters.
* Coverages.Building changeset + helpers.
* Coverages.TreeCanopy provider tile-URL builder and decoders.
* Workers.MsBuildingsImportWorker (full import flow incl. blank
  lines, MultiPolygon, idempotency, hash fallback) — also fixed
  the partial-index ON CONFLICT bug that prevented imports from
  ever working: the unique index has WHERE ms_footprint_id IS
  NOT NULL, so the worker now uses an :unsafe_fragment conflict
  target that includes the predicate.
* GraphQLDocsController smoke test (was 0%).

Coverage rose from 73.19% → 74% with the major project modules I
touched all moved from 0% / <50% to >75%.
2026-05-07 09:11:17 -05:00

142 lines
4.8 KiB
Elixir

defmodule ToweropsWeb.CoverageLive.ShowTest do
use ToweropsWeb.ConnCase
import Phoenix.LiveViewTest
import Towerops.CoveragesFixtures
import Towerops.OrganizationsFixtures
alias Towerops.Coverages
setup :register_and_log_in_user
setup %{conn: conn, user: user} do
org = organization_fixture(user.id)
site = site_fixture(org.id, %{latitude: 30.27, longitude: -97.74})
conn = Plug.Conn.put_session(conn, :current_organization_id, org.id)
%{conn: conn, org: org, site: site}
end
describe "mount" do
test "renders coverage details for a draft coverage", %{conn: conn, org: org, site: site} do
cov = coverage_fixture(org.id, site.id, %{name: "Hilltop Sector"})
{:ok, _view, html} = live(conn, ~p"/coverage/#{cov.id}")
assert html =~ "Hilltop Sector"
assert html =~ "No heatmap available yet"
end
test "renders the parameters drawer when coverage is ready", %{conn: conn, org: org, site: site} do
cov = coverage_fixture(org.id, site.id, %{name: "ReadyOne"})
{:ok, _ready} =
Coverages.mark_status(cov, "ready", %{
png_path: "/coverage/#{org.id}/#{cov.id}/rssi.png",
raster_path: "/coverage/#{org.id}/#{cov.id}/rssi.tif",
bbox_min_lat: 30.0,
bbox_min_lon: -98.0,
bbox_max_lat: 30.5,
bbox_max_lon: -97.5
})
{:ok, _view, html} = live(conn, ~p"/coverage/#{cov.id}")
assert html =~ "Parameters"
end
test "shows queued banner for queued coverage", %{conn: conn, org: org, site: site} do
cov = coverage_fixture(org.id, site.id)
{:ok, queued} = Coverages.mark_status(cov, "queued", %{})
{:ok, _view, html} = live(conn, ~p"/coverage/#{queued.id}")
assert html =~ "queued"
end
test "shows failed banner with error message", %{conn: conn, org: org, site: site} do
cov = coverage_fixture(org.id, site.id)
{:ok, failed} = Coverages.mark_status(cov, "failed", %{error_message: "test failure"})
{:ok, _view, html} = live(conn, ~p"/coverage/#{failed.id}")
assert html =~ "test failure"
end
test "404 for unknown coverage", %{conn: conn} do
assert_raise Ecto.NoResultsError, fn ->
live(conn, ~p"/coverage/#{Ecto.UUID.generate()}")
end
end
test "requires authentication" do
conn = build_conn()
assert {:error, {:redirect, %{to: path}}} = live(conn, ~p"/coverage/#{Ecto.UUID.generate()}")
assert path =~ "/users/log-in"
end
end
describe "events" do
test "toggle_params hides and re-shows the parameters drawer", %{conn: conn, org: org, site: site} do
cov = coverage_fixture(org.id, site.id)
{:ok, view, html} = live(conn, ~p"/coverage/#{cov.id}")
assert html =~ "Hide details"
html = render_click(view, "toggle_params", %{})
assert html =~ "Details"
refute html =~ "Hide details"
end
test "set_probe_units switches between ft and m", %{conn: conn, org: org, site: site} do
cov = coverage_fixture(org.id, site.id)
{:ok, view, _html} = live(conn, ~p"/coverage/#{cov.id}")
assert render_click(view, "set_probe_units", %{"units" => "m"})
assert render_click(view, "set_probe_units", %{"units" => "ft"})
end
test "delete removes the coverage and redirects", %{conn: conn, org: org, site: site} do
cov = coverage_fixture(org.id, site.id, %{name: "DoomedCoverage"})
{:ok, view, _html} = live(conn, ~p"/coverage/#{cov.id}")
result = render_click(view, "delete", %{})
assert {:error, {:live_redirect, %{to: "/coverage"}}} = result
assert nil == Coverages.get_coverage(org.id, cov.id)
end
test "recompute queues compute and refreshes status", %{conn: conn, org: org, site: site} do
cov = coverage_fixture(org.id, site.id)
{:ok, view, _html} = live(conn, ~p"/coverage/#{cov.id}")
html = render_click(view, "recompute", %{})
assert html =~ "queued"
end
end
describe "format helpers" do
alias ToweropsWeb.CoverageLive.Show
test "format_ft converts metres to feet" do
assert Show.format_ft(30.0) == "98 ft"
assert Show.format_ft(nil) == ""
end
test "format_mi converts metres to miles" do
assert Show.format_mi(1609.344) == "1.0 mi"
assert Show.format_mi(nil) == ""
end
test "format_ghz converts MHz to GHz" do
assert Show.format_ghz(5800) == "5.8 GHz"
assert Show.format_ghz(nil) == ""
end
test "status_badge_class maps known statuses" do
assert Show.status_badge_class("ready") =~ "bg-green"
assert Show.status_badge_class("failed") =~ "bg-red"
assert Show.status_badge_class("computing") =~ "bg-yellow"
assert Show.status_badge_class("queued") =~ "bg-blue"
assert Show.status_badge_class("draft") =~ "bg-gray"
assert Show.status_badge_class("anything-else") =~ "bg-gray"
end
end
end