212 lines
6.9 KiB
Elixir
212 lines
6.9 KiB
Elixir
defmodule ToweropsWeb.Api.V1.CoveragesControllerTest do
|
|
use ToweropsWeb.ConnCase, async: false
|
|
|
|
import Ecto.Query
|
|
import Towerops.AccountsFixtures
|
|
import Towerops.CoveragesFixtures
|
|
import Towerops.OrganizationsFixtures
|
|
|
|
alias Towerops.ApiTokens
|
|
alias Towerops.Coverages.Coverage
|
|
|
|
setup %{conn: conn} do
|
|
user = user_fixture()
|
|
org = organization_fixture(user.id)
|
|
site = site_fixture(org.id, %{latitude: 30.27, longitude: -97.74})
|
|
|
|
{:ok, {_token, raw}} =
|
|
ApiTokens.create_api_token(%{
|
|
organization_id: org.id,
|
|
user_id: user.id,
|
|
name: "test"
|
|
})
|
|
|
|
conn = put_req_header(conn, "authorization", "Bearer #{raw}")
|
|
%{conn: conn, org: org, user: user, site: site}
|
|
end
|
|
|
|
describe "GET /api/v1/coverages" do
|
|
test "returns coverages for the authenticated organization", %{conn: conn, org: org, site: site} do
|
|
cov = coverage_fixture(org.id, site.id, %{name: "Sector A"})
|
|
|
|
resp = conn |> get(~p"/api/v1/coverages") |> json_response(200)
|
|
|
|
assert [%{"id" => id, "name" => "Sector A"}] = resp["coverages"]
|
|
assert id == cov.id
|
|
end
|
|
|
|
test "returns empty list when org has no coverages", %{conn: conn} do
|
|
assert %{"coverages" => []} = conn |> get(~p"/api/v1/coverages") |> json_response(200)
|
|
end
|
|
end
|
|
|
|
describe "POST /api/v1/coverages" do
|
|
test "creates and queues a coverage", %{conn: conn, site: site} do
|
|
attrs = %{
|
|
"name" => "API-created",
|
|
"site_id" => site.id,
|
|
"antenna_slug" => "simulate-isotropic-omni-0",
|
|
"frequency_ghz" => 5.8,
|
|
"tx_power_dbm" => 20.0,
|
|
"height_agl_ft" => 80,
|
|
"azimuth_deg" => 0,
|
|
"downtilt_deg" => 0,
|
|
"radius_mi" => 3
|
|
}
|
|
|
|
resp = conn |> post(~p"/api/v1/coverages", coverage: attrs) |> json_response(201)
|
|
|
|
assert resp["name"] == "API-created"
|
|
assert resp["status"] == "queued"
|
|
assert resp["site_id"] == site.id
|
|
assert is_binary(resp["id"]) and byte_size(resp["id"]) > 0
|
|
end
|
|
|
|
test "returns 422 with field errors on bad input", %{conn: conn} do
|
|
resp =
|
|
conn
|
|
|> post(~p"/api/v1/coverages", coverage: %{"name" => ""})
|
|
|> json_response(422)
|
|
|
|
assert resp["errors"]
|
|
end
|
|
|
|
test "returns 400 when 'coverage' wrapper is missing", %{conn: conn} do
|
|
resp = conn |> post(~p"/api/v1/coverages", %{}) |> json_response(400)
|
|
assert resp["error"] =~ "Missing"
|
|
end
|
|
end
|
|
|
|
describe "GET /api/v1/coverages/:id" do
|
|
test "returns a single coverage", %{conn: conn, org: org, site: site} do
|
|
cov = coverage_fixture(org.id, site.id)
|
|
resp = conn |> get(~p"/api/v1/coverages/#{cov.id}") |> json_response(200)
|
|
assert resp["id"] == cov.id
|
|
end
|
|
|
|
test "returns 404 for cross-org coverage", %{conn: conn} do
|
|
assert conn
|
|
|> get(~p"/api/v1/coverages/#{Ecto.UUID.generate()}")
|
|
|> json_response(404)
|
|
end
|
|
end
|
|
|
|
describe "PATCH /api/v1/coverages/:id" do
|
|
test "updates editable fields", %{conn: conn, org: org, site: site} do
|
|
cov = coverage_fixture(org.id, site.id, %{name: "before"})
|
|
|
|
resp =
|
|
conn
|
|
|> patch(~p"/api/v1/coverages/#{cov.id}", coverage: %{"name" => "after"})
|
|
|> json_response(200)
|
|
|
|
assert resp["name"] == "after"
|
|
end
|
|
end
|
|
|
|
describe "DELETE /api/v1/coverages/:id" do
|
|
test "deletes the coverage", %{conn: conn, org: org, site: site} do
|
|
cov = coverage_fixture(org.id, site.id)
|
|
|
|
conn = delete(conn, ~p"/api/v1/coverages/#{cov.id}")
|
|
assert response(conn, 204)
|
|
|
|
assert nil == Towerops.Coverages.get_coverage(org.id, cov.id)
|
|
end
|
|
end
|
|
|
|
describe "POST /api/v1/coverages/:id/recompute" do
|
|
test "queues a recompute and returns 202", %{conn: conn, org: org, site: site} do
|
|
cov = coverage_fixture(org.id, site.id, %{status: "ready"})
|
|
resp = conn |> post(~p"/api/v1/coverages/#{cov.id}/recompute") |> json_response(202)
|
|
assert resp["status"] == "queued"
|
|
end
|
|
end
|
|
|
|
describe "GET /api/v1/coverages/:id/kmz" do
|
|
defp set_coverage_state(cov, attrs) do
|
|
Towerops.Repo.update_all(
|
|
from(c in Coverage, where: c.id == ^cov.id),
|
|
set: Map.to_list(attrs)
|
|
)
|
|
|
|
Towerops.Repo.get!(Coverage, cov.id)
|
|
end
|
|
|
|
test "returns 409 when coverage is not ready", %{conn: conn, org: org, site: site} do
|
|
cov = org.id |> coverage_fixture(site.id) |> set_coverage_state(%{status: "queued"})
|
|
resp = conn |> get(~p"/api/v1/coverages/#{cov.id}/kmz") |> json_response(409)
|
|
assert resp["error"] =~ "not ready"
|
|
end
|
|
|
|
test "returns 409 when coverage is ready but png_path is nil",
|
|
%{conn: conn, org: org, site: site} do
|
|
cov =
|
|
org.id
|
|
|> coverage_fixture(site.id)
|
|
|> set_coverage_state(%{status: "ready", png_path: nil})
|
|
|
|
resp = conn |> get(~p"/api/v1/coverages/#{cov.id}/kmz") |> json_response(409)
|
|
assert resp["error"] =~ "not ready"
|
|
end
|
|
|
|
test "returns 500 when png file is missing on disk",
|
|
%{conn: conn, org: org, site: site} do
|
|
cov =
|
|
org.id
|
|
|> coverage_fixture(site.id)
|
|
|> set_coverage_state(%{
|
|
status: "ready",
|
|
png_path: "/coverage/missing/#{System.unique_integer([:positive])}.png",
|
|
bbox_min_lat: 30.0,
|
|
bbox_max_lat: 30.5,
|
|
bbox_min_lon: -97.5,
|
|
bbox_max_lon: -97.0
|
|
})
|
|
|
|
resp = conn |> get(~p"/api/v1/coverages/#{cov.id}/kmz") |> json_response(500)
|
|
assert resp["error"] =~ "Failed to build KMZ"
|
|
end
|
|
|
|
test "returns 404 for an unknown coverage id", %{conn: conn} do
|
|
conn = get(conn, ~p"/api/v1/coverages/#{Ecto.UUID.generate()}/kmz")
|
|
assert response(conn, 404)
|
|
end
|
|
|
|
test "returns 200 + KMZ bytes when png exists on disk",
|
|
%{conn: conn, org: org, site: site} do
|
|
png_dir = Path.join([Application.app_dir(:towerops, "priv/static"), "coverage", "kmztest"])
|
|
File.mkdir_p!(png_dir)
|
|
png_path_rel = "/coverage/kmztest/cov-#{System.unique_integer([:positive])}.png"
|
|
png_abs = Path.join(Application.app_dir(:towerops, "priv/static"), Path.relative_to(png_path_rel, "/"))
|
|
File.write!(png_abs, <<137, 80, 78, 71, 13, 10, 26, 10>>)
|
|
on_exit(fn -> File.rm(png_abs) end)
|
|
|
|
cov =
|
|
org.id
|
|
|> coverage_fixture(site.id)
|
|
|> set_coverage_state(%{
|
|
status: "ready",
|
|
png_path: png_path_rel,
|
|
name: "T<&> coverage",
|
|
bbox_min_lat: 30.1,
|
|
bbox_max_lat: 30.5,
|
|
bbox_min_lon: -97.5,
|
|
bbox_max_lon: -97.0
|
|
})
|
|
|
|
conn = get(conn, ~p"/api/v1/coverages/#{cov.id}/kmz")
|
|
assert response(conn, 200)
|
|
|
|
assert [disposition] = get_resp_header(conn, "content-disposition")
|
|
assert disposition =~ "coverage-#{cov.id}.kmz"
|
|
end
|
|
end
|
|
|
|
describe "auth" do
|
|
test "rejects requests without a Bearer token" do
|
|
conn = get(build_conn(), ~p"/api/v1/coverages")
|
|
assert response(conn, 401)
|
|
end
|
|
end
|
|
end
|