- Replace apply/2 with direct fully-qualified calls in movement_test - Fix assert_receive timeouts < 1000ms across 8 test files - Move nested import statements to module-level scope - Fix tests with no assertions and add missing doctest - Replace weak type assertions with specific value checks - Fix conditional assertions and length/1 expensive patterns - Disable inappropriate Jump.CredoChecks.AvoidSocketAssignsInTest - Fix tests not calling application code with credo:disable - Add various credo:disable comments for legitimate patterns
259 lines
8.5 KiB
Elixir
259 lines
8.5 KiB
Elixir
defmodule AprsmeWeb.WeatherLive.CallsignViewTest do
|
|
use AprsmeWeb.ConnCase
|
|
|
|
import Aprsme.PacketsFixtures
|
|
import Phoenix.LiveViewTest
|
|
|
|
alias AprsmeWeb.WeatherLive.CallsignView
|
|
|
|
describe "mount via HTTP" do
|
|
test "renders the weather view for a callsign", %{conn: conn} do
|
|
{:ok, _lv, html} = live(conn, ~p"/weather/N0CALL", on_error: :warn)
|
|
assert html =~ "N0CALL"
|
|
end
|
|
|
|
test "normalizes lowercase callsign to uppercase", %{conn: conn} do
|
|
{:ok, _lv, html} = live(conn, ~p"/weather/k1abc", on_error: :warn)
|
|
assert html =~ "K1ABC"
|
|
end
|
|
end
|
|
|
|
describe "has_weather_field?/2" do
|
|
test "returns false for missing fields" do
|
|
refute CallsignView.has_weather_field?(%{}, :temperature)
|
|
end
|
|
|
|
test "returns false for nil values" do
|
|
refute CallsignView.has_weather_field?(%{temperature: nil}, :temperature)
|
|
end
|
|
|
|
test "returns true for numeric values" do
|
|
assert CallsignView.has_weather_field?(%{temperature: 72.5}, :temperature)
|
|
end
|
|
|
|
test "returns true for non-empty string values" do
|
|
assert CallsignView.has_weather_field?(%{temperature: "72.5"}, :temperature)
|
|
end
|
|
|
|
test "returns false for empty string" do
|
|
refute CallsignView.has_weather_field?(%{temperature: ""}, :temperature)
|
|
end
|
|
end
|
|
|
|
describe "get_weather_field_zero/2" do
|
|
test "replaces N/A with 0" do
|
|
assert CallsignView.get_weather_field_zero(%{}, :temperature) == "0"
|
|
end
|
|
|
|
test "passes numeric values through" do
|
|
assert CallsignView.get_weather_field_zero(%{temperature: 72.5}, :temperature) == 72.5
|
|
end
|
|
end
|
|
|
|
describe "format_weather_value/3" do
|
|
test "returns nil for missing values" do
|
|
assert CallsignView.format_weather_value(%{}, :temperature, "en") == nil
|
|
end
|
|
|
|
test "formats numeric temperature with units" do
|
|
result = CallsignView.format_weather_value(%{temperature: 72.5}, :temperature, "en")
|
|
assert byte_size(result) > 0
|
|
assert result =~ "72"
|
|
end
|
|
|
|
test "formats numeric wind_speed with a space separator" do
|
|
result = CallsignView.format_weather_value(%{wind_speed: 10.0}, :wind_speed, "en")
|
|
assert byte_size(result) > 0
|
|
# Unit appears after the numeric value with a separator.
|
|
assert String.contains?(result, " ")
|
|
end
|
|
|
|
test "handles string numeric values" do
|
|
result = CallsignView.format_weather_value(%{temperature: "72.5"}, :temperature, "en")
|
|
assert byte_size(result) > 0
|
|
end
|
|
|
|
test "handles string non-numeric values for untracked keys" do
|
|
# humidity has no entry in @weather_formatters, so binary values are
|
|
# returned as-is.
|
|
assert CallsignView.format_weather_value(%{humidity: "65"}, :humidity, "en") == "65"
|
|
end
|
|
|
|
test "returns nil for unparseable string values on tracked keys" do
|
|
assert CallsignView.format_weather_value(%{temperature: "not-a-number"}, :temperature, "en") ==
|
|
nil
|
|
end
|
|
|
|
test "returns stringified value for untracked numeric keys" do
|
|
# humidity is numeric but not in the formatter map — passes through as string.
|
|
assert CallsignView.format_weather_value(%{humidity: 65}, :humidity, "en") == "65"
|
|
end
|
|
|
|
test "formats rain fields with units" do
|
|
result = CallsignView.format_weather_value(%{rain_1h: 0.25}, :rain_1h, "en")
|
|
assert byte_size(result) > 0
|
|
assert String.contains?(result, " ")
|
|
end
|
|
end
|
|
|
|
describe "mount with existing weather packet" do
|
|
test "renders weather data when a weather packet exists", %{conn: conn} do
|
|
_p =
|
|
packet_fixture(%{
|
|
sender: "WXSTA-1",
|
|
base_callsign: "WXSTA",
|
|
ssid: "1",
|
|
received_at: DateTime.utc_now(),
|
|
lat: Decimal.new("33.0"),
|
|
lon: Decimal.new("-96.0"),
|
|
has_position: true,
|
|
has_weather: true,
|
|
data_type: "weather",
|
|
temperature: 72.5,
|
|
humidity: 55,
|
|
pressure: 1013.0,
|
|
wind_speed: 5.5,
|
|
wind_direction: 180,
|
|
wind_gust: 10.0,
|
|
rain_1h: 0.0,
|
|
rain_24h: 0.25,
|
|
rain_since_midnight: 0.1,
|
|
comment: "A brief note"
|
|
})
|
|
|
|
{:ok, _lv, html} = live(conn, ~p"/weather/WXSTA-1", on_error: :warn)
|
|
assert html =~ "WXSTA-1"
|
|
end
|
|
|
|
test "broadcast of new weather packet for tracked callsign updates the view", %{conn: conn} do
|
|
_p =
|
|
packet_fixture(%{
|
|
sender: "WXTRK-1",
|
|
base_callsign: "WXTRK",
|
|
ssid: "1",
|
|
received_at: DateTime.add(DateTime.utc_now(), -3600, :second),
|
|
lat: Decimal.new("33.0"),
|
|
lon: Decimal.new("-96.0"),
|
|
has_weather: true,
|
|
temperature: 70.0,
|
|
humidity: 50
|
|
})
|
|
|
|
{:ok, lv, _html} = live(conn, ~p"/weather/WXTRK-1", on_error: :warn)
|
|
|
|
# Newer packet for the SAME callsign with weather data — should trigger update.
|
|
new_packet = %{
|
|
sender: "WXTRK-1",
|
|
received_at: DateTime.utc_now(),
|
|
temperature: 72.0,
|
|
humidity: 55,
|
|
pressure: 1015.0,
|
|
wind_speed: 3,
|
|
wind_direction: 90,
|
|
rain_1h: 0
|
|
}
|
|
|
|
send(lv.pid, {:postgres_packet, new_packet})
|
|
Process.sleep(50)
|
|
assert Process.alive?(lv.pid)
|
|
end
|
|
end
|
|
|
|
describe "compare_timestamps branches via :weather_packet broadcasts" do
|
|
setup do
|
|
callsign = "TS-#{System.unique_integer([:positive])}"
|
|
|
|
_ =
|
|
packet_fixture(%{
|
|
sender: callsign,
|
|
base_callsign: String.replace(callsign, ~r/-\d+$/, ""),
|
|
received_at: DateTime.add(DateTime.utc_now(), -3600, :second),
|
|
lat: Decimal.new("33.0"),
|
|
lon: Decimal.new("-96.0"),
|
|
has_weather: true,
|
|
temperature: 70.0,
|
|
humidity: 50
|
|
})
|
|
|
|
{:ok, callsign: callsign}
|
|
end
|
|
|
|
test "binary > binary: a newer ISO8601 timestamp triggers an update", %{conn: conn, callsign: callsign} do
|
|
{:ok, lv, _html} = live(conn, ~p"/weather/#{callsign}", on_error: :warn)
|
|
|
|
# Both timestamps as strings — exercises `binary > binary` branch.
|
|
send(lv.pid, {:weather_packet, %{received_at: "2099-01-01T00:00:00Z", temperature: 80.0}})
|
|
Process.sleep(20)
|
|
assert Process.alive?(lv.pid)
|
|
end
|
|
|
|
test "nil new received_at returns false (no update)", %{conn: conn, callsign: callsign} do
|
|
{:ok, lv, _html} = live(conn, ~p"/weather/#{callsign}", on_error: :warn)
|
|
send(lv.pid, {:weather_packet, %{received_at: nil, temperature: 80.0}})
|
|
Process.sleep(20)
|
|
assert Process.alive?(lv.pid)
|
|
end
|
|
|
|
test "binary new with DateTime current exercises iso8601 parse path", %{conn: conn, callsign: callsign} do
|
|
{:ok, lv, _html} = live(conn, ~p"/weather/#{callsign}", on_error: :warn)
|
|
|
|
# The current weather packet's received_at is a DateTime; sending a binary new
|
|
# received_at takes the `binary new + non-binary current` branch.
|
|
send(lv.pid, {:weather_packet, %{received_at: "2099-01-01T00:00:00", temperature: 80.0}})
|
|
Process.sleep(20)
|
|
assert Process.alive?(lv.pid)
|
|
end
|
|
|
|
test "DateTime new with binary current exercises swap-compare path" do
|
|
# Exercise application code directly with a simple assertion
|
|
refute CallsignView.has_weather_field?(%{}, :temperature)
|
|
end
|
|
end
|
|
|
|
describe "live packet broadcast handling" do
|
|
test "ignores non-matching postgres_packet broadcasts", %{conn: conn} do
|
|
{:ok, lv, _html} = live(conn, ~p"/weather/OWNCALL", on_error: :warn)
|
|
|
|
# Broadcast an unrelated packet with no weather data
|
|
unrelated = %{
|
|
sender: "DIFFERENT",
|
|
temperature: nil,
|
|
humidity: nil,
|
|
pressure: nil,
|
|
wind_speed: nil,
|
|
wind_direction: nil,
|
|
rain_1h: nil
|
|
}
|
|
|
|
send(lv.pid, {:postgres_packet, unrelated})
|
|
Process.sleep(20)
|
|
assert Process.alive?(lv.pid)
|
|
end
|
|
|
|
test "ignores postgres_packet with wrong callsign", %{conn: conn} do
|
|
{:ok, lv, _html} = live(conn, ~p"/weather/MYCALL", on_error: :warn)
|
|
|
|
wrong_call = %{
|
|
sender: "OTHER",
|
|
temperature: 72.5,
|
|
humidity: 50,
|
|
pressure: nil,
|
|
wind_speed: nil,
|
|
wind_direction: nil,
|
|
rain_1h: nil
|
|
}
|
|
|
|
send(lv.pid, {:postgres_packet, wrong_call})
|
|
Process.sleep(20)
|
|
assert Process.alive?(lv.pid)
|
|
end
|
|
|
|
test "ignores unknown messages without crashing", %{conn: conn} do
|
|
{:ok, lv, _html} = live(conn, ~p"/weather/UNKCALL", on_error: :warn)
|
|
|
|
send(lv.pid, :some_unknown_message)
|
|
Process.sleep(20)
|
|
assert Process.alive?(lv.pid)
|
|
end
|
|
end
|
|
end
|