prop/test/microwaveprop/radio/adif_import_test.exs
Graham McIntire c3becdac9f Show beacon submitter callsign, add donate link, prefer 8-char grids
- Add belongs_to :user on Beacon schema, preload in get_beacon!/1
- Show "Submitted by {callsign}" on beacon detail page
- Add NTMS donation link on the about page
- Change grid preference text to 8 characters (e.g. EM12kp37)
2026-04-11 18:08:18 -05:00

189 lines
6.3 KiB
Elixir

defmodule Microwaveprop.Radio.AdifImportTest do
use Microwaveprop.DataCase, async: true
alias Microwaveprop.Radio.AdifImport
alias Microwaveprop.Terrain.ElevationClient
alias Microwaveprop.Weather.HrrrClient
alias Microwaveprop.Weather.IemClient
setup do
Req.Test.stub(IemClient, fn conn ->
case conn.request_path do
"/cgi-bin/request/asos.py" -> Req.Test.text(conn, "#DEBUG,\nstation,valid,tmpf\n")
_ -> Req.Test.json(conn, %{"profiles" => []})
end
end)
Req.Test.stub(HrrrClient, fn conn ->
Plug.Conn.send_resp(conn, 404, "not found")
end)
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
@submitter "test@example.com"
@valid_adif """
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<FREQ:9>10368.000<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<EOR>
"""
describe "preview/2" do
test "parses a valid ADIF record" do
assert {:ok, preview} = AdifImport.preview(@valid_adif, @submitter)
assert length(preview.valid) == 1
assert preview.invalid == []
assert preview.duplicates == []
[row] = preview.valid
assert row.attrs["station1"] == "W5XD"
assert row.attrs["station2"] == "K5TR"
assert row.attrs["grid1"] == "EM12"
assert row.attrs["grid2"] == "EM00"
assert row.attrs["band"] == "10000"
assert row.attrs["mode"] == "CW"
end
test "parses ADIF with BAND field instead of FREQ" do
adif = """
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<BAND:4>23cm<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert [row] = preview.valid
assert row.attrs["band"] == "1296"
end
test "maps ADIF frequency to correct band" do
for {freq, expected_band} <- [
{"1296.200", "1296"},
{"2304.000", "2304"},
{"5760.100", "5760"},
{"10368.000", "10000"},
{"24192.000", "24000"},
{"47088.000", "47000"},
{"76032.000", "75000"}
] do
adif = """
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<FREQ:#{byte_size(freq)}>#{freq}<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert [row] = preview.valid
assert row.attrs["band"] == expected_band,
"FREQ #{freq} should map to band #{expected_band}, got #{row.attrs["band"]}"
end
end
test "handles multiple records" do
adif = """
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<FREQ:9>10368.000<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<EOR>
<STATION_CALLSIGN:4>W5XD<CALL:5>N5DUP<GRIDSQUARE:4>EM13<MY_GRIDSQUARE:4>EM12<FREQ:9>10368.000<MODE:3>SSB<QSO_DATE:8>20260328<TIME_ON:6>190000<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert length(preview.valid) == 2
end
test "skips ADIF header before <EOH>" do
adif = """
ADIF Export from Logger32
<ADIF_VER:5>3.1.4
<PROGRAMID:8>Logger32
<EOH>
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<FREQ:9>10368.000<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert length(preview.valid) == 1
end
test "returns error for empty ADIF" do
assert {:error, :no_records} = AdifImport.preview("", @submitter)
end
test "returns error for header-only ADIF" do
adif = """
ADIF Export
<ADIF_VER:5>3.1.4
<EOH>
"""
assert {:error, :no_records} = AdifImport.preview(adif, @submitter)
end
test "marks record invalid when missing required fields" do
adif = """
<CALL:4>K5TR<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert preview.valid == []
assert length(preview.invalid) == 1
end
test "skips non-microwave contacts" do
adif = """
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<FREQ:7>144.200<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert preview.valid == []
assert length(preview.invalid) == 1
end
test "deduplicates against existing DB contacts" do
# Insert one via CSV import path
{:ok, _} =
Microwaveprop.Radio.create_contact(%{
station1: "W5XD",
station2: "K5TR",
grid1: "EM12",
grid2: "EM00",
band: "10000",
mode: "CW",
qso_timestamp: ~U[2026-03-28 18:00:00Z],
submitter_email: @submitter
})
assert {:ok, preview} = AdifImport.preview(@valid_adif, @submitter)
assert preview.valid == []
assert length(preview.duplicates) == 1
end
test "is case-insensitive for field names" do
adif = """
<station_callsign:4>W5XD<call:4>K5TR<gridsquare:4>EM00<my_gridsquare:4>EM12<freq:9>10368.000<mode:2>CW<qso_date:8>20260328<time_on:6>180000<eor>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert length(preview.valid) == 1
end
test "handles TIME_ON with 4 digits (HHMM)" do
adif = """
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<FREQ:9>10368.000<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:4>1800<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert [row] = preview.valid
assert row.timestamp == ~U[2026-03-28 18:00:00Z]
end
test "uses OPERATOR field when STATION_CALLSIGN is missing" do
adif = """
<OPERATOR:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<FREQ:9>10368.000<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert [row] = preview.valid
assert row.attrs["station1"] == "W5XD"
end
end
end