prop/test/microwaveprop/rover_planning_test.exs
Graham McIntire 209244f364
refactor(rover-locations): rename status atoms ideal→good, off_limits→bad
Renames the rover-location status enum and every label that referenced
it. Existing rows are migrated in place. Also touches the consumers:

- Rover.Location enum + default
- RoverLocationsLive index, status filter, form, show, badges
- Rover.Compute and rover_live (only_ideal_locations → only_good_locations)
- RoverPlanning context candidate filter (status == :good)
- RoverPlanning.Show / Form copy
- All rover-related tests
2026-05-03 11:13:18 -05:00

144 lines
5.1 KiB
Elixir
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

defmodule Microwaveprop.RoverPlanningTest do
use Microwaveprop.DataCase, async: true
alias Microwaveprop.AccountsFixtures
alias Microwaveprop.Repo
alias Microwaveprop.Rover
alias Microwaveprop.RoverPlanning
alias Microwaveprop.RoverPlanning.Mission
alias Microwaveprop.Terrain.ElevationClient
setup do
# Oban runs inline in test, so RoverPathProfileWorker fires immediately
# when the context enqueues jobs. Stub elevation lookups so the worker
# can complete without hitting the real API.
Req.Test.stub(ElevationClient, fn conn ->
params = Plug.Conn.fetch_query_params(conn).query_params
lat_count = params["latitude"] |> String.split(",") |> length()
Req.Test.json(conn, %{"elevation" => List.duplicate(200.0, lat_count)})
end)
:ok
end
defp create_rover_location(attrs) do
user = AccountsFixtures.user_fixture()
{:ok, loc} =
Rover.create_location(
user,
Map.merge(%{lat: 32.5, lon: -97.5, status: :good}, attrs)
)
loc
end
defp valid_attrs(extra \\ %{}) do
Map.merge(
%{
"name" => "Test mission",
"band_mhz" => 10_000,
"only_known_good" => true,
"rover_height_ft" => 8.0,
"station_height_ft" => 30.0,
"stations" => %{
"0" => %{"input" => "EM12kp", "position" => 0}
}
},
extra
)
end
describe "create_mission/2" do
test "inserts a mission with stations and enqueues a path per (rover × station) pair" do
user = AccountsFixtures.user_fixture()
_ideal_loc = create_rover_location(%{lat: 32.0, lon: -97.0, status: :good})
_off_limits = create_rover_location(%{lat: 33.0, lon: -98.0, status: :bad})
assert {:ok, mission} = RoverPlanning.create_mission(user, valid_attrs())
assert mission.user_id == user.id
assert [station] = mission.stations
assert station.grid == "EM12KP"
assert is_float(station.lat)
assert is_float(station.lon)
paths = RoverPlanning.list_paths(mission)
# only_known_good=true → only the :good location pairs with the station.
# Oban runs inline in test so the worker has already completed.
assert length(paths) == 1
assert hd(paths).status in [:complete, :failed]
end
test "with only_known_good=false, all rover-locations get a path entry" do
user = AccountsFixtures.user_fixture()
_ideal = create_rover_location(%{lat: 32.0, lon: -97.0, status: :good})
_off = create_rover_location(%{lat: 33.0, lon: -98.0, status: :bad})
attrs = valid_attrs(%{"only_known_good" => false})
assert {:ok, mission} = RoverPlanning.create_mission(user, attrs)
assert length(RoverPlanning.list_paths(mission)) == 2
end
test "missing stations returns a changeset error" do
user = AccountsFixtures.user_fixture()
attrs = Map.delete(valid_attrs(), "stations")
assert {:error, %Ecto.Changeset{} = cs} = RoverPlanning.create_mission(user, attrs)
refute cs.valid?
assert {_, _} = cs.errors[:stations] || hd(cs.errors)
end
end
describe "update_mission/3" do
test "owner can update a mission and paths get rebuilt" do
user = AccountsFixtures.user_fixture()
_ = create_rover_location(%{lat: 32.0, lon: -97.0, status: :good})
{:ok, mission} = RoverPlanning.create_mission(user, valid_attrs())
[original] = RoverPlanning.list_paths(mission)
attrs = valid_attrs(%{"name" => "Updated"})
assert {:ok, updated} = RoverPlanning.update_mission(user, mission.id, attrs)
assert updated.name == "Updated"
# Old path was deleted by replace, fresh row inserted with new id.
[fresh] = RoverPlanning.list_paths(updated)
refute fresh.id == original.id
end
test "non-owner update is denied" do
owner = AccountsFixtures.user_fixture()
other = AccountsFixtures.user_fixture()
_ = create_rover_location(%{lat: 32.0, lon: -97.0, status: :good})
{:ok, mission} = RoverPlanning.create_mission(owner, valid_attrs())
assert {:error, :not_found} =
RoverPlanning.update_mission(other, mission.id, valid_attrs())
end
test "admin can update any mission" do
owner = AccountsFixtures.user_fixture()
admin = AccountsFixtures.user_fixture()
{:ok, admin} = Microwaveprop.Accounts.admin_update_user(admin, %{is_admin: true})
_ = create_rover_location(%{lat: 32.0, lon: -97.0, status: :good})
{:ok, mission} = RoverPlanning.create_mission(owner, valid_attrs())
assert {:ok, updated} =
RoverPlanning.update_mission(admin, mission.id, valid_attrs(%{"name" => "By admin"}))
assert updated.name == "By admin"
end
end
describe "delete_mission/2" do
test "owner can delete; non-owner cannot" do
owner = AccountsFixtures.user_fixture()
other = AccountsFixtures.user_fixture()
{:ok, mission} = RoverPlanning.create_mission(owner, valid_attrs())
assert {:error, :not_found} = RoverPlanning.delete_mission(other, mission.id)
assert {:ok, _} = RoverPlanning.delete_mission(owner, mission.id)
refute Repo.get(Mission, mission.id)
end
end
end