aprs.me/test/aprsme/device_identification_test.exs
Graham McIntire 403300f696
test: broaden unit & property-test coverage (61.8% → 64.7%)
Adds ~800 new tests (incl. 27 StreamData properties) across 22 modules
that previously had little or no coverage. Highlights:

- Pure utility modules (LogSanitizer, PacketFieldWhitelist,
  PacketSanitizer, DeviceParser, CoordinateUtils, BoundsUtils,
  ParamUtils, Convert, WeatherUnits) get thorough unit + property
  coverage including UTF-8 truncation boundaries, antimeridian
  longitude wrap, Haversine symmetry, and unit-conversion inverses.
- JSON view modules (CallsignJSON, ErrorJSON, ChangesetJSON) verified
  for envelope shape and error templating.
- Plugs (HealthCheck, RateLimiter, ApiCSRF) exercised for happy-path
  and halt paths.
- GenServer modules (RegexCache, CleanupScheduler, DeploymentNotifier)
  tested without touching the supervised singleton where possible.
- Drops the orphaned map_live/map_helpers_test.exs whose module name
  collided with the new live/shared/coordinate_utils_test.exs.
2026-04-23 13:32:09 -05:00

200 lines
7.1 KiB
Elixir

defmodule Aprsme.DeviceIdentificationTest do
use Aprsme.DataCase, async: true
alias Aprsme.DeviceIdentification
alias Aprsme.Devices
alias Aprsme.Repo
doctest DeviceIdentification
describe "identify_device/1" do
test "identifies Original MIC-E devices" do
assert DeviceIdentification.identify_device(" " <> <<0, 0>>) == "Original MIC-E"
end
test "identifies Kenwood devices" do
assert DeviceIdentification.identify_device(">" <> <<0>> <> "^") == "Kenwood TH-D74"
assert DeviceIdentification.identify_device(">" <> <<0, 0>>) == "Kenwood TH-D74A"
assert DeviceIdentification.identify_device("]" <> <<0>> <> "=") == "Kenwood DM-710"
assert DeviceIdentification.identify_device("]" <> <<0, 0>>) == "Kenwood DM-700"
end
test "identifies Yaesu devices" do
assert DeviceIdentification.identify_device("`_" <> " ") == "Yaesu VX-8"
assert DeviceIdentification.identify_device("`_" <> "\"") == "Yaesu FTM-350"
assert DeviceIdentification.identify_device("`_" <> "#") == "Yaesu VX-8G"
assert DeviceIdentification.identify_device("`_" <> "$") == "Yaesu FT1D"
assert DeviceIdentification.identify_device("`_" <> "%") == "Yaesu FTM-400DR"
assert DeviceIdentification.identify_device("`_" <> ")") == "Yaesu FTM-100D"
assert DeviceIdentification.identify_device("`_" <> "(") == "Yaesu FT2D"
end
test "identifies other devices" do
assert DeviceIdentification.identify_device("` X") == "AP510"
assert DeviceIdentification.identify_device("`" <> <<0, 0>>) == "Mic-Emsg"
assert DeviceIdentification.identify_device("'|3") == "Byonics TinyTrack3"
assert DeviceIdentification.identify_device("'|4") == "Byonics TinyTrack4"
assert DeviceIdentification.identify_device("':4") ==
"SCS GmbH & Co. P4dragon DR-7400 modems"
assert DeviceIdentification.identify_device("':8") ==
"SCS GmbH & Co. P4dragon DR-7800 modems"
assert DeviceIdentification.identify_device("'" <> <<0, 0>>) == "McTrackr"
assert DeviceIdentification.identify_device(<<0>> <> "\"" <> <<0>>) == "Hamhud"
assert DeviceIdentification.identify_device(<<0>> <> "/" <> <<0>>) == "Argent"
assert DeviceIdentification.identify_device(<<0>> <> "^" <> <<0>>) == "HinzTec anyfrog"
assert DeviceIdentification.identify_device(<<0>> <> "*" <> <<0>>) ==
"APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO"
assert DeviceIdentification.identify_device(<<0>> <> "~" <> <<0>>) == "Other"
end
test "returns Unknown for unidentified devices" do
assert DeviceIdentification.identify_device(<<0, 0, 0>>) == "Unknown"
end
end
describe "known_manufacturers/0" do
test "returns list of known manufacturers" do
manufacturers = DeviceIdentification.known_manufacturers()
assert is_list(manufacturers)
assert "Kenwood" in manufacturers
assert "Yaesu" in manufacturers
assert "Byonics" in manufacturers
end
end
describe "known_models/1" do
test "returns list of known models for Kenwood" do
models = DeviceIdentification.known_models("Kenwood")
assert is_list(models)
assert "TH-D74" in models
assert "TH-D74A" in models
assert "DM-710" in models
assert "DM-700" in models
end
test "returns list of known models for Yaesu" do
models = DeviceIdentification.known_models("Yaesu")
assert is_list(models)
assert "VX-8" in models
assert "FTM-350" in models
assert "VX-8G" in models
assert "FT1D" in models
assert "FTM-400DR" in models
assert "FTM-100D" in models
assert "FT2D" in models
end
test "returns empty list for unknown manufacturer" do
assert DeviceIdentification.known_models("Unknown") == []
end
end
describe "upsert_devices/1" do
test "replaces the devices table with data from the JSON" do
# Seed some dummy state to be wiped.
Repo.delete_all(Devices)
Repo.insert!(%Devices{
identifier: "WILLBEWIPED",
vendor: "Old",
model: "Old"
})
json = %{
"tocalls" => %{
"APXYZ" => %{"vendor" => "NewVendor", "model" => "NewModel", "features" => ["a", "b"]}
},
"mice" => %{
"]=" => %{"vendor" => "KenwoodMice", "model" => "TH-D7", "features" => "single"}
}
}
assert :ok == DeviceIdentification.upsert_devices(json)
# Previous row is gone, new rows inserted with correct attrs.
assert Repo.get_by(Devices, identifier: "WILLBEWIPED") == nil
assert %Devices{vendor: "NewVendor", model: "NewModel", features: ["a", "b"]} =
Repo.get_by(Devices, identifier: "APXYZ")
# Single-string feature gets wrapped into a list by process_device_attrs.
assert %Devices{features: ["single"]} = Repo.get_by(Devices, identifier: "]=")
end
test "handles empty JSON groups" do
Repo.delete_all(Devices)
assert :ok == DeviceIdentification.upsert_devices(%{})
assert Repo.all(Devices) == []
end
end
describe "lookup_device_by_identifier/1" do
test "matches APSK21 to APS??? pattern" do
# Seed the devices table from the JSON
Aprsme.DevicesSeeder.seed_from_json()
# Force cache refresh in test environment
if Code.ensure_loaded?(Aprsme.DeviceCache) do
# Manually refresh cache with seeded data
devices = Repo.all(Devices)
Aprsme.Cache.put(:device_cache, :all_devices, devices)
end
# Should match
found = DeviceIdentification.lookup_device_by_identifier("APSK21")
assert found
assert found.identifier == "APS???"
assert found.model
assert found.vendor
end
test "handles non-wildcard exact match" do
# Insert a device with an exact identifier (no ?).
Repo.delete_all(Devices)
Repo.insert!(%Devices{
identifier: "EXACTMATCH",
vendor: "Vendor",
model: "Model"
})
# Refresh the cache so lookups see the new record.
devices = Repo.all(Devices)
Aprsme.Cache.put(:device_cache, :all_devices, devices)
found = DeviceIdentification.lookup_device_by_identifier("EXACTMATCH")
assert found
assert found.identifier == "EXACTMATCH"
refute DeviceIdentification.lookup_device_by_identifier("NOTEXACTMATCH")
end
test "returns nil for nil input" do
assert DeviceIdentification.lookup_device_by_identifier(nil) == nil
end
test "matches Mic-E device identifier from raw packet" do
# Seed the devices table from the JSON
Aprsme.DevicesSeeder.seed_from_json()
# Force cache refresh in test environment
if Code.ensure_loaded?(Aprsme.DeviceCache) do
# Manually refresh cache with seeded data
devices = Repo.all(Devices)
Aprsme.Cache.put(:device_cache, :all_devices, devices)
end
# The device identifier extracted from the raw packet is "]="
found = DeviceIdentification.lookup_device_by_identifier("]=")
assert found
assert found.identifier == "]="
assert found.model
assert found.vendor
end
end
end