defmodule AprsmeWeb.StatusLive.IndexTest do use AprsmeWeb.ConnCase import Phoenix.LiveViewTest alias AprsmeWeb.StatusLive.Index alias Phoenix.LiveView.Socket describe "mount via HTTP" do test "renders the status page", %{conn: conn} do {:ok, _lv, html} = live(conn, ~p"/status", on_error: :warn) assert html =~ "System Status" assert html =~ "APRS-IS Connection" end test "renders health score 3 (stable under 1 hour)", %{conn: conn} do _ = case :ets.info(:query_cache) do :undefined -> :ets.new(:query_cache, [:named_table, :public, :set]) _ -> :ok end status = %{ connected: true, server: "test.aprs.test", port: 14_580, connected_at: DateTime.add(DateTime.utc_now(), -1000, :second), uptime_seconds: 1000, login_id: "N0CALL", filter: "r/33/-96/100", packet_stats: %{total_packets: 10, last_packet_at: DateTime.utc_now(), packets_per_second: 1}, stored_packet_count: 50, oldest_packet_timestamp: DateTime.add(DateTime.utc_now(), -3600, :second) } Aprsme.Cache.put(:query_cache, "aprs_status", status) {:ok, _lv, html} = live(conn, ~p"/status", on_error: :warn) # Uptime 1000s < 3600 → score 3. 50 stored packets. assert html =~ "System Status" assert html =~ "Connected" Aprsme.Cache.del(:query_cache, "aprs_status") end test "renders health score 4 (stable under 1 day)", %{conn: conn} do _ = case :ets.info(:query_cache) do :undefined -> :ets.new(:query_cache, [:named_table, :public, :set]) _ -> :ok end status = %{ connected: true, server: "test", port: 14_580, connected_at: DateTime.add(DateTime.utc_now(), -10_000, :second), # Uptime 10,000s is between 3600 and 86_400 → score 4. uptime_seconds: 10_000, login_id: "N0CALL", filter: "r/0/0/100", packet_stats: %{total_packets: 1, last_packet_at: nil, packets_per_second: 0}, stored_packet_count: 0, oldest_packet_timestamp: nil } Aprsme.Cache.put(:query_cache, "aprs_status", status) {:ok, _lv, html} = live(conn, ~p"/status", on_error: :warn) assert html =~ "Connected" Aprsme.Cache.del(:query_cache, "aprs_status") end test "renders the connected-path template when cache has a live status", %{conn: conn} do # Ensure the cache ETS table exists for test. _ = case :ets.info(:query_cache) do :undefined -> :ets.new(:query_cache, [:named_table, :public, :set]) _ -> :ok end status = %{ connected: true, server: "cached.aprs.test", port: 14_580, connected_at: DateTime.add(DateTime.utc_now(), -3600, :second), uptime_seconds: 3600, login_id: "N0CALL", filter: "r/33/-96/100", packet_stats: %{ total_packets: 1234, last_packet_at: DateTime.add(DateTime.utc_now(), -30, :second), packets_per_second: 5 }, stored_packet_count: 5000, oldest_packet_timestamp: DateTime.add(DateTime.utc_now(), -86_400, :second) } Aprsme.Cache.put(:query_cache, "aprs_status", status) {:ok, _lv, html} = live(conn, ~p"/status", on_error: :warn) assert html =~ "Connected" assert html =~ "cached.aprs.test" assert html =~ "N0CALL" # Total packets formatted with thousands separator. assert html =~ "1,234" # Cleanup so other tests aren't affected. Aprsme.Cache.del(:query_cache, "aprs_status") end end describe "handle_info({:status_updated, status}, socket)" do test "updates assigns with the new status" do status = %{ connected: true, server: "test-server", port: 14_580, connected_at: DateTime.utc_now(), uptime_seconds: 60, login_id: "TEST-ID", filter: "r/0/0/100", packet_stats: %{total_packets: 0, packets_per_second: 0, last_packet_at: nil}, stored_packet_count: 0, oldest_packet_timestamp: nil } socket = %Socket{assigns: %{__changed__: %{}}} assert {:noreply, new_socket} = Index.handle_info({:status_updated, status}, socket) assigns = Map.get(new_socket, :assigns) assert assigns.aprs_status == status assert %DateTime{} = assigns.current_time # Connected+uptime 60s → health score 2. assert assigns.health_score == 2 end end describe "handle_info({:aprs_status_update, status}, socket)" do test "updates status without flipping loading flag" do status = %{ connected: true, server: "test-server", port: 14_580, connected_at: DateTime.utc_now(), uptime_seconds: 100_000, login_id: "TEST-ID", filter: "r/0/0/100", packet_stats: %{total_packets: 5, packets_per_second: 1, last_packet_at: DateTime.utc_now()}, stored_packet_count: 123, oldest_packet_timestamp: DateTime.utc_now() } socket = %Socket{assigns: %{__changed__: %{}, loading: false}} assert {:noreply, new_socket} = Index.handle_info({:aprs_status_update, status}, socket) assigns = Map.get(new_socket, :assigns) assert assigns.aprs_status == status # Uptime > 86_400? 100_000 > 86_400 → yes → score 5. assert assigns.health_score == 5 # Loading flag is untouched (this code path doesn't toggle it). assert assigns.loading == false end end describe "format_uptime/1" do test "returns 'Not connected' when seconds <= 0" do assert Index.format_uptime(0) =~ "Not connected" assert Index.format_uptime(-100) =~ "Not connected" end test "formats seconds-only durations" do assert Index.format_uptime(45) == "45s" end test "formats minutes+seconds durations" do assert Index.format_uptime(125) == "2m 5s" end test "formats hours+minutes+seconds durations" do assert Index.format_uptime(3 * 3600 + 45 * 60 + 30) == "3h 45m 30s" end test "formats day+hour+minute+second durations" do total = 2 * 86_400 + 3 * 3600 + 4 * 60 + 5 assert Index.format_uptime(total) == "2d 3h 4m 5s" end end describe "get_health_description/2" do test "score 1 returns disconnected" do assert Index.get_health_description(1, false) =~ "Disconnected" end test "score 5 connected returns excellent/healthy text" do result = Index.get_health_description(5, true) assert byte_size(result) > 0 end test "all defined score combinations return non-empty text" do for score <- 1..5 do for connected <- [true, false] do result = Index.get_health_description(score, connected) assert byte_size(result) > 0 end end end end describe "render with cluster_info populated" do test "renders the Cluster Status section when cluster_info is present", %{conn: conn} do _ = case :ets.info(:query_cache) do :undefined -> :ets.new(:query_cache, [:named_table, :public, :set]) _ -> :ok end status = %{ connected: true, server: "clustered.aprs.test", port: 14_580, connected_at: DateTime.add(DateTime.utc_now(), -60, :second), uptime_seconds: 60, login_id: "N0CALL", filter: "r/33/-96/100", packet_stats: %{total_packets: 10, packets_per_second: 1, last_packet_at: DateTime.utc_now()}, stored_packet_count: 100, oldest_packet_timestamp: DateTime.utc_now(), cluster_info: %{ total_nodes: 3, connected_nodes: 2, leader_node: "node-a", all_nodes: ["node-a", "node-b", "node-c"] } } Aprsme.Cache.put(:query_cache, "aprs_status", status) {:ok, _lv, html} = live(conn, ~p"/status", on_error: :warn) assert html =~ "Cluster Status" assert html =~ "Total Nodes" assert html =~ "node-a" assert html =~ "node-b" Aprsme.Cache.del(:query_cache, "aprs_status") end end describe "{:status_updated, status} update path" do test "applies a fresh status map and updates loading=false" do socket = %Socket{ assigns: %{ aprs_status: %{}, current_time: DateTime.utc_now(), health_score: 1, loading: true, __changed__: %{} } } status = %{ connected: true, server: "test", port: 14_580, connected_at: DateTime.utc_now(), uptime_seconds: 100, login_id: "N0CALL", filter: "r/33/-96/100", packet_stats: %{total_packets: 1, packets_per_second: 0, last_packet_at: DateTime.utc_now()}, stored_packet_count: 0, oldest_packet_timestamp: nil } assert {:noreply, new_socket} = Index.handle_info({:status_updated, status}, socket) assigns = Map.get(new_socket, :assigns) # Loading is unchanged (handle_info doesn't touch it) assert assigns.loading == true assert assigns.aprs_status == status end end end