defmodule MicrowavepropWeb.UserProfileLiveTest do use MicrowavepropWeb.ConnCase, async: true import Ecto.Query import Microwaveprop.AccountsFixtures import Microwaveprop.BeaconsFixtures import Phoenix.LiveViewTest alias Microwaveprop.Accounts.User alias Microwaveprop.BeaconMonitors alias Microwaveprop.Beacons alias Microwaveprop.Radio.Contact alias Microwaveprop.Repo defp contact_for(user, attrs) do defaults = %{ station1: "W5XD", station2: "K5TR", qso_timestamp: ~U[2026-03-28 18:00:00Z], mode: "CW", band: Decimal.new("10000"), grid1: "EM12", grid2: "EM00", pos1: %{"lat" => 32.9, "lon" => -97.0}, pos2: %{"lat" => 30.3, "lon" => -97.7}, distance_km: Decimal.new("295"), user_id: user.id } merged = Map.merge(defaults, attrs) {user_id, changeset_attrs} = Map.pop(merged, :user_id) {:ok, contact} = %Contact{user_id: user_id} |> Contact.changeset(changeset_attrs) |> Repo.insert() contact end describe "mount" do test "renders a user's contributions", %{conn: conn} do user = user_fixture(%{callsign: "W5ISP"}) _contact = contact_for(user, %{station1: "W5OWN"}) _beacon = beacon_fixture(user, callsign: "W5ISP") {:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}") assert html =~ "W5ISP" assert html =~ "Contacts submitted" assert html =~ "Beacons submitted" assert html =~ "W5OWN" end test "callsign lookup is case-insensitive", %{conn: conn} do _user = user_fixture(%{callsign: "W5ISP"}) {:ok, _lv, html} = live(conn, ~p"/u/w5isp") assert html =~ "W5ISP" end test "does not show contacts belonging to another user", %{conn: conn} do user = user_fixture(%{callsign: "W5ISP"}) other = user_fixture(%{callsign: "W5OTH"}) _owned = contact_for(user, %{station1: "W5MINE"}) _theirs = contact_for(other, %{station1: "W5NOTMINE"}) {:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}") assert html =~ "W5MINE" refute html =~ "W5NOTMINE" end test "shows empty-state messages when the user has no submissions", %{conn: conn} do user = user_fixture(%{callsign: "W5EMP"}) {:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}") assert html =~ "No contacts submitted yet" assert html =~ "No beacons submitted yet" end test "redirects to the home page for an unknown callsign", %{conn: conn} do assert {:error, {:live_redirect, %{to: "/"}}} = live(conn, ~p"/u/NO1SUCH") end test "owners see both approved and pending beacons on their own profile", %{conn: conn} do user = user_fixture(%{callsign: "W5ISP"}) {:ok, approved} = Beacons.create_beacon(user, valid_beacon_attrs()) {:ok, _approved} = Beacons.approve_beacon(approved) _pending = beacon_fixture(user, callsign: "W5PND") conn = log_in_user(conn, user) {:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}") assert html =~ "W5PND" assert html =~ "Pending" assert html =~ "Approved" end test "anonymous visitors do not see pending beacons", %{conn: conn} do user = user_fixture(%{callsign: "W5ISP"}) {:ok, approved} = Beacons.create_beacon(user, valid_beacon_attrs()) {:ok, _approved} = Beacons.approve_beacon(approved) _pending = beacon_fixture(user, callsign: "W5PND") {:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}") refute html =~ "W5PND" refute html =~ "Pending" assert html =~ "Approved" end test "another logged-in user does not see pending beacons", %{conn: conn} do owner = user_fixture(%{callsign: "W5ISP"}) other = user_fixture(%{callsign: "W5OTH"}) _pending = beacon_fixture(owner, callsign: "W5PND") conn = log_in_user(conn, other) {:ok, _lv, html} = live(conn, ~p"/u/#{owner.callsign}") refute html =~ "W5PND" refute html =~ "Pending" end test "admins see pending beacons on any profile", %{conn: conn} do owner = user_fixture(%{callsign: "W5ISP"}) admin = user_fixture(%{callsign: "W5ADM"}) {1, _} = Repo.update_all( from(u in User, where: u.id == ^admin.id), set: [is_admin: true] ) _pending = beacon_fixture(owner, callsign: "W5PND") conn = log_in_user(conn, %{admin | is_admin: true}) {:ok, _lv, html} = live(conn, ~p"/u/#{owner.callsign}") assert html =~ "W5PND" assert html =~ "Pending" end test "anonymous visitors do not see private contacts", %{conn: conn} do user = user_fixture(%{callsign: "W5ISP"}) _public = contact_for(user, %{station1: "W5PUB"}) _hidden = contact_for(user, %{station1: "W5SECRET", private: true}) {:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}") assert html =~ "W5PUB" refute html =~ "W5SECRET" end test "owners see their own private contacts", %{conn: conn} do user = user_fixture(%{callsign: "W5ISP"}) _hidden = contact_for(user, %{station1: "W5SECRET", private: true}) conn = log_in_user(conn, user) {:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}") assert html =~ "W5SECRET" end test "another logged-in user does not see private contacts", %{conn: conn} do owner = user_fixture(%{callsign: "W5ISP"}) other = user_fixture(%{callsign: "W5OTH"}) _hidden = contact_for(owner, %{station1: "W5SECRET", private: true}) conn = log_in_user(conn, other) {:ok, _lv, html} = live(conn, ~p"/u/#{owner.callsign}") refute html =~ "W5SECRET" end test "renders the 'Contacts involving' section with contacts that mention the callsign as a remote station", %{conn: conn} do # The profile owner (W5ISP) isn't the submitter — another user logged # the contact — but W5ISP shows up as station2. The involving block # should surface that QSO. owner = user_fixture(%{callsign: "W5ISP"}) other = user_fixture(%{callsign: "W5OTH"}) _mention = contact_for(other, %{station1: "K5TR", station2: "W5ISP"}) {:ok, _lv, html} = live(conn, ~p"/u/#{owner.callsign}") assert html =~ "Contacts W5ISP is in" assert html =~ "K5TR" assert html =~ "W5ISP" end test "uses first character of the user's name for the avatar initial when set", %{conn: conn} do _user = user_fixture(%{callsign: "W5ISP", name: " graham mcintire"}) {:ok, _lv, html} = live(conn, ~p"/u/W5ISP") # initial(%{name: "graham mcintire"}) trims, takes the first char, # and uppercases → "G". Assert on the font-mono span that renders it. assert html =~ ~r/font-mono[^>]*>\s*G\s*]*>\s*W\s* "Shack Pi", "hardware_type" => "RTL-SDR", "hardware_id" => "SN-001", "user_id" => user.id }) conn = log_in_user(conn, user) {:ok, _lv, html} = live(conn, ~p"/u/#{user.callsign}") assert html =~ "Beacon monitors" assert html =~ "Shack Pi" assert html =~ "RTL-SDR" assert html =~ "SN-001" assert html =~ "never" end end end