aprs.me/test/parser/parser_test.exs
2025-06-16 11:55:09 -05:00

314 lines
10 KiB
Elixir

defmodule Parser.ParserTest do
use ExUnit.Case
alias Parser.Types.MicE
test "timestamped position" do
aprs_message =
"KE7XXX>APRS,TCPIP*,qAC,NINTH:@211743z4444.67N/11111.68W_061/005g012t048r000p000P000h77b10015.DsVP\r\n"
{:ok, packet} = Parser.parse(aprs_message)
assert packet.data_type == :timestamped_position_with_message
end
test "mic_e convert digits" do
assert Parser.parse_mic_e_digit("0") == [0, 0, nil]
end
test "mic_e convert destination field" do
assert Parser.parse_mic_e_destination("T7SYWP") == %{
lat_degrees: 47,
lat_minutes: 39,
lat_fractional: 70,
lat_direction: :north,
lon_direction: :west,
longitude_offset: 100,
message_code: "M02",
message_description: "In Service"
}
end
test "mic_e convert information field" do
information_field = ~s(`\(_fn"Oj/]TEST=)
sut = Parser.parse_mic_e_information(information_field, 100)
assert sut ==
%{
dti: "`",
heading: 251,
lon_degrees: 112,
lon_fractional: 74,
lon_minutes: 7,
speed: 20,
symbol: "j",
table: "/",
message: "]TEST=",
manufacturer: "Kenwood DM-710"
}
end
test "mic_e" do
sut = Parser.parse_mic_e("T7SYWP", ~s(`\(_fn"Oj/))
assert %MicE{} = sut
end
test "weird format" do
aprs_message =
~s(ON4AVM-11>APDI23,WIDE1-1,WIDE2-2,qAR,ON0LB-10:=S4`k!OZ,C# sT/A=000049DIXPRS 2.3.0b\n)
Parser.parse(aprs_message)
end
describe "parse/1" do
test "with invalid packet" do
assert {:error, "Invalid packet format"} = Parser.parse("invalid packet")
end
end
describe "parse_callsign/1" do
test "callsign with ssid" do
assert Parser.parse_callsign("W5ISP-1") == {:ok, ["W5ISP", "1"]}
end
test "callsign without ssid" do
assert Parser.parse_callsign("W5ISP") == {:ok, ["W5ISP", "0"]}
end
end
describe "parse_datatype/1" do
test "position" do
Enum.each(
%{
":" => :message,
">" => :status,
"!" => :position,
"/" => :timestamped_position,
"=" => :position_with_message,
"@" => :timestamped_position_with_message,
";" => :object,
"`" => :mic_e,
"'" => :mic_e_old,
"_" => :weather,
"T" => :telemetry,
"$" => :raw_gps_ultimeter,
"<" => :station_capabilities,
"?" => :query,
"{" => :user_defined,
"}" => :third_party_traffic,
"" => :unknown_datatype
},
fn {key, value} -> assert Parser.parse_datatype(key) == value end
)
end
end
describe "parse_manufacturer/3" do
test "with any manufacturer" do
Enum.each(
[
%{matcher: [" ", nil, nil], result: "Original MIC-E"},
%{matcher: [">", nil, "="], result: "Kenwood TH-D72"},
%{matcher: [">", nil, "^"], result: "Kenwood TH-D74"},
%{matcher: [">", nil, nil], result: "Kenwood TH-D74A"},
%{matcher: ["]", nil, "="], result: "Kenwood DM-710"},
%{matcher: ["]", nil, nil], result: "Kenwood DM-700"},
%{matcher: ["`", "_", " "], result: "Yaesu VX-8"},
%{matcher: ["`", "_", "\""], result: "Yaesu FTM-350"},
%{matcher: ["`", "_", "#"], result: "Yaesu VX-8G"},
%{matcher: ["`", "_", "$"], result: "Yaesu FT1D"},
%{matcher: ["`", "_", "%"], result: "Yaesu FTM-400DR"},
%{matcher: ["`", "_", ")"], result: "Yaesu FTM-100D"},
%{matcher: ["`", "_", "("], result: "Yaesu FT2D"},
%{matcher: ["`", " ", "X"], result: "AP510"},
%{matcher: ["`", nil, nil], result: "Mic-Emsg"},
%{matcher: ["'", "|", "3"], result: "Byonics TinyTrack3"},
%{matcher: ["'", "|", "4"], result: "Byonics TinyTrack4"},
%{matcher: ["'", ":", "4"], result: "SCS GmbH & Co. P4dragon DR-7400 modems"},
%{matcher: ["'", ":", "8"], result: "SCS GmbH & Co. P4dragon DR-7800 modems"},
%{matcher: ["'", nil, nil], result: "McTrackr"},
%{matcher: [nil, "\"", nil], result: "Hamhud ?"},
%{matcher: [nil, "/", nil], result: "Argent ?"},
%{matcher: [nil, "^", nil], result: "HinzTec anyfrog"},
%{matcher: [nil, "*", nil], result: "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO"},
%{matcher: [nil, "~", nil], result: "Other"},
%{matcher: [nil, nil, nil], result: :unknown_manufacturer}
],
fn %{matcher: [s1, s2, s3], result: result} -> assert Parser.parse_manufacturer(s1, s2, s3) == result end
)
end
end
describe "parse_mic_e/2" do
test "with valid mic-e" do
# `|J!l4^\k/]"6?}=
mic_e_position = <<96, 124, 74, 33, 108, 52, 94, 107, 47, 93, 34, 54, 63, 125, 61>>
assert %MicE{} = Parser.parse_mic_e("SS0Y1S", mic_e_position)
end
end
describe "parse_mic_e_destination/1" do
test "1" do
assert Parser.parse_mic_e_destination("SS0Y1S") == %{
lat_degrees: 33,
lat_direction: :north,
lat_fractional: 13,
lat_minutes: 9,
lon_direction: :west,
longitude_offset: 0,
message_code: "M01",
message_description: "En Route"
}
end
test "2" do
assert Parser.parse_mic_e_destination("SS08LL") == %{
lat_degrees: 33,
lat_direction: :south,
lat_fractional: 0,
lat_minutes: 8,
lon_direction: :east,
longitude_offset: 0,
message_code: "M01",
message_description: "En Route"
}
end
test "3" do
assert Parser.parse_mic_e_destination("SS0L0A") == %{
lat_degrees: 33,
lat_direction: :south,
lat_fractional: 0,
lat_minutes: 0,
lon_direction: :unknown,
longitude_offset: 0,
message_code: "M01",
message_description: "En Route"
}
end
test "4" do
assert Parser.parse_mic_e_destination("SS0AA3") == %{
lat_degrees: 33,
lat_direction: :unknown,
lat_fractional: 3,
lat_minutes: 0,
lon_direction: :east,
longitude_offset: :unknown,
message_code: "M01",
message_description: "En Route"
}
end
end
describe "parse_mic_e_information/2" do
test "1" do
info = <<96, 40, 95, 102, 110, 34, 79, 106, 47, 93, 84, 69, 83, 84, 61>>
assert Parser.parse_mic_e_information(info, 100) == %{
dti: "`",
heading: 251,
lon_degrees: 112,
lon_fractional: 74,
lon_minutes: 7,
manufacturer: "Kenwood DM-710",
message: "]TEST=",
speed: 20,
symbol: "j",
table: "/"
}
end
test "2" do
info = <<96, 40, 95, 102, 50, 34, 79, 106, 47, 93, 61>>
assert Parser.parse_mic_e_information(info, 100) == %{
dti: "`",
heading: 251,
lon_degrees: 112,
lon_fractional: 74,
lon_minutes: 7,
manufacturer: "Kenwood DM-710",
message: "]=",
speed: 220,
symbol: "j",
table: "/"
}
end
end
describe "convert_compressed_cs/1" do
# !!
# I!
# Y"
test "1" do
assert Parser.convert_compressed_cs("Y$") == %{course: 12, speed: 0.3}
end
end
describe "compressed position parsing" do
test "parse compressed position without timestamp" do
# Test compressed position format: !/5L!!<*e7>7P[
# This represents a compressed position with lat/lon, symbol, and course/speed
compressed_data = "/5L!!<*e7>7P["
result = Parser.parse_position_without_timestamp(false, "!#{compressed_data}")
assert result.data_type == :position
assert result.aprs_messaging? == false
assert result.symbol_table_id == "/"
assert is_float(result.latitude)
assert is_float(result.longitude)
assert Map.has_key?(result, :course) or Map.has_key?(result, :speed) or Map.has_key?(result, :range)
end
test "convert compressed latitude" do
# Test with known compressed latitude value
compressed_lat = "5L!!"
result = Parser.convert_compressed_lat(compressed_lat)
assert is_float(result)
assert result >= -90.0 and result <= 90.0
end
test "convert compressed longitude" do
# Test with known compressed longitude value
compressed_lon = "<*e7"
result = Parser.convert_compressed_lon(compressed_lon)
assert is_float(result)
assert result >= -180.0 and result <= 180.0
end
end
describe "malformed position packets" do
test "handles malformed position packet gracefully" do
# Test with the specific malformed packet that was causing issues
malformed_packet = "KC3ARY>APDW16,KB3FCZ-2,WIDE1*,qAR,WA3YMM-1:!I:!&N:;\")# !"
{:ok, packet} = Parser.parse(malformed_packet)
assert packet.data_type == :position
assert packet.sender == "KC3ARY"
assert packet.destination == "APDW16"
assert packet.data_extended.data_type == :malformed_position
assert packet.data_extended.latitude == nil
assert packet.data_extended.longitude == nil
assert packet.data_extended.comment == "I:!&N:;\")# !"
assert packet.data_extended.raw_data == "!I:!&N:;\")# !"
end
test "handles other malformed position formats" do
# Test with another type of malformed position data that's too short
malformed_packet = "TEST>APRS,TCPIP*:!SHORT"
{:ok, packet} = Parser.parse(malformed_packet)
assert packet.data_type == :position
assert packet.data_extended.data_type == :malformed_position
assert packet.data_extended.latitude == nil
assert packet.data_extended.longitude == nil
assert packet.data_extended.comment == "SHORT"
assert packet.data_extended.raw_data == "!SHORT"
end
end
end