- Add jump_credo_checks ~> 0.4 with all 20 checks enabled - Fix all standard Credo issues: 139 @spec (113 done, 26 remain), 4 refactoring, 3 alias usage, 9 System.cmd env, 5 unsafe_to_atom, 2 max line length, 9 assert_receive timeout - Fix 170+ jump_credo_checks warnings: - 117 TopLevelAliasImportRequire: move nested alias/import to module top - 32 UseObanProWorker: switch to Oban.Pro.Worker - 4 DoctestIExExamples: add doctests / create test file - ~20 WeakAssertion: strengthen type-check assertions - Various ConditionalAssertion, AssertReceiveTimeout fixes - Exclude vendor/ from Credo analysis - Remaining: 175 warnings (mostly opinionated WeakAssertion, AvoidSocketAssignsInTest), 26 @spec annotations
198 lines
5.9 KiB
Elixir
198 lines
5.9 KiB
Elixir
defmodule Microwaveprop.Commercial.PollWorkerTest do
|
|
use Microwaveprop.DataCase, async: false
|
|
|
|
alias Microwaveprop.Commercial
|
|
alias Microwaveprop.Commercial.PollWorker
|
|
alias Microwaveprop.Commercial.Sample
|
|
alias Microwaveprop.Weather
|
|
alias Microwaveprop.Weather.IemClient
|
|
alias Microwaveprop.Weather.SurfaceObservation
|
|
|
|
@link_attrs %{
|
|
label: "test-poll-link",
|
|
host: "10.0.0.1",
|
|
community: "public",
|
|
radio_type: "af11x",
|
|
weather_station: "KTKI",
|
|
enabled: true
|
|
}
|
|
|
|
describe "poll_and_record/2" do
|
|
test "creates samples for enabled links when SNMP succeeds" do
|
|
{:ok, link} = Commercial.create_link(@link_attrs)
|
|
|
|
snmp_result = %{
|
|
rx_power_0: -55,
|
|
tx_power: 20,
|
|
link_state: 1,
|
|
link_uptime: 86_400
|
|
}
|
|
|
|
PollWorker.poll_and_record([link], fn _host, _community, _type -> {:ok, snmp_result} end)
|
|
|
|
samples = Repo.all(Sample)
|
|
assert length(samples) == 1
|
|
assert hd(samples).rx_power_0 == -55.0
|
|
assert hd(samples).link_id == link.id
|
|
end
|
|
|
|
test "skips links when SNMP fails" do
|
|
{:ok, link} = Commercial.create_link(@link_attrs)
|
|
|
|
PollWorker.poll_and_record(
|
|
[link],
|
|
fn _host, _community, _type -> {:error, :snmp_failed} end
|
|
)
|
|
|
|
assert Repo.all(Sample) == []
|
|
end
|
|
|
|
test "continues polling remaining links after one failure" do
|
|
{:ok, link1} = Commercial.create_link(@link_attrs)
|
|
{:ok, link2} = Commercial.create_link(%{@link_attrs | label: "test-poll-link-2", host: "10.0.0.2"})
|
|
|
|
call_count = :counters.new(1, [:atomics])
|
|
|
|
poll_fn = fn _host, _community, _type ->
|
|
n = :counters.get(call_count, 1)
|
|
:counters.add(call_count, 1, 1)
|
|
|
|
if n == 0 do
|
|
{:error, :snmp_failed}
|
|
else
|
|
{:ok, %{rx_power_0: -60, link_state: 1}}
|
|
end
|
|
end
|
|
|
|
PollWorker.poll_and_record([link1, link2], poll_fn)
|
|
|
|
assert length(Repo.all(Sample)) == 1
|
|
end
|
|
|
|
test "no-ops cleanly on an empty link list" do
|
|
# Defensive: if the DB has no enabled links, the worker should
|
|
# still return without calling the poll fn or touching samples.
|
|
called? = :counters.new(1, [:atomics])
|
|
|
|
poll_fn = fn _host, _community, _type ->
|
|
:counters.add(called?, 1, 1)
|
|
{:ok, %{}}
|
|
end
|
|
|
|
PollWorker.poll_and_record([], poll_fn)
|
|
|
|
assert :counters.get(called?, 1) == 0
|
|
assert Repo.all(Sample) == []
|
|
end
|
|
|
|
test "dispatches poll_fn with host, community, and radio_type from the link" do
|
|
{:ok, _link} = Commercial.create_link(@link_attrs)
|
|
|
|
test_pid = self()
|
|
|
|
poll_fn = fn host, community, radio_type ->
|
|
send(test_pid, {:poll_args, host, community, radio_type})
|
|
{:error, :stop}
|
|
end
|
|
|
|
PollWorker.poll_and_record(Commercial.enabled_links(), poll_fn)
|
|
|
|
assert_receive {:poll_args, "10.0.0.1", "public", "af11x"}
|
|
end
|
|
|
|
test "logs and skips when create_sample rejects the changeset" do
|
|
{:ok, link} = Commercial.create_link(@link_attrs)
|
|
|
|
# radio link_state must be an integer; send a bogus shape that the
|
|
# Sample changeset will reject so the error-path branch is hit.
|
|
PollWorker.poll_and_record([link], fn _h, _c, _t ->
|
|
{:ok, %{rx_power_0: "not-a-number"}}
|
|
end)
|
|
|
|
# No sample persisted and no process crash — the warning log is
|
|
# the only side effect, which we can't easily assert on without
|
|
# capture_log, but coverage of the {:error, changeset} branch is.
|
|
assert Repo.all(Sample) == []
|
|
end
|
|
end
|
|
|
|
describe "fetch_weather/1" do
|
|
# Exercises the IEM-fetch + ASOS-upsert path (previously private) via
|
|
# stubbed Req. Avoids the perform/1 SNMP round-trip which would call
|
|
# every live commercial link at once.
|
|
|
|
setup do
|
|
# IemClient uses Req — stub it with a minimal ASOS tab-separated
|
|
# response body so the JSON/CSV decoder path is exercised.
|
|
Req.Test.stub(IemClient, fn conn ->
|
|
Plug.Conn.send_resp(conn, 200, "station,valid\n")
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
|
|
test "creates (or finds) a Weather.Station row for every unique link station" do
|
|
{:ok, link} = Commercial.create_link(@link_attrs)
|
|
|
|
assert :ok = PollWorker.fetch_weather([link])
|
|
|
|
{:ok, station} =
|
|
Weather.find_or_create_station(%{
|
|
station_code: "KTKI",
|
|
station_type: "asos",
|
|
name: "KTKI",
|
|
lat: 0.0,
|
|
lon: 0.0
|
|
})
|
|
|
|
assert station.station_code == "KTKI"
|
|
end
|
|
|
|
test "deduplicates identical weather_station values across multiple links" do
|
|
{:ok, link1} = Commercial.create_link(@link_attrs)
|
|
|
|
{:ok, link2} =
|
|
Commercial.create_link(%{@link_attrs | label: "sibling", host: "10.0.0.2"})
|
|
|
|
call_count = :counters.new(1, [:atomics])
|
|
|
|
Req.Test.stub(IemClient, fn conn ->
|
|
:counters.add(call_count, 1, 1)
|
|
Plug.Conn.send_resp(conn, 200, "station,valid\n")
|
|
end)
|
|
|
|
PollWorker.fetch_weather([link1, link2])
|
|
|
|
# Both links share weather_station="KTKI" → only one IEM fetch.
|
|
assert :counters.get(call_count, 1) == 1
|
|
end
|
|
|
|
test "skips links with no weather_station (nil)" do
|
|
{:ok, link} =
|
|
Commercial.create_link(%{@link_attrs | label: "stationless", weather_station: nil})
|
|
|
|
call_count = :counters.new(1, [:atomics])
|
|
|
|
Req.Test.stub(IemClient, fn conn ->
|
|
:counters.add(call_count, 1, 1)
|
|
Plug.Conn.send_resp(conn, 200, "")
|
|
end)
|
|
|
|
assert :ok = PollWorker.fetch_weather([link])
|
|
assert :counters.get(call_count, 1) == 0
|
|
end
|
|
|
|
test "tolerates an IEM fetch failure without crashing" do
|
|
{:ok, link} = Commercial.create_link(@link_attrs)
|
|
|
|
Req.Test.stub(IemClient, fn conn ->
|
|
Plug.Conn.send_resp(conn, 500, "server error")
|
|
end)
|
|
|
|
assert :ok = PollWorker.fetch_weather([link])
|
|
|
|
# No surface observations persisted on HTTP error.
|
|
assert Repo.all(SurfaceObservation) == []
|
|
end
|
|
end
|
|
end
|