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%.
142 lines
4.8 KiB
Elixir
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
|