aprs.me/test/aprsme/device_cache_test.exs
2026-04-24 19:00:24 -05:00

121 lines
4.2 KiB
Elixir

defmodule Aprsme.DeviceCacheTest do
use ExUnit.Case, async: false
alias Aprsme.DeviceCache
setup do
pid = Process.whereis(DeviceCache)
if pid do
:erlang.unregister(DeviceCache)
end
on_exit(fn ->
if pid && !Process.whereis(DeviceCache) do
true = Process.register(pid, DeviceCache)
end
end)
:ok
end
test "refresh_cache/0 returns :ok when the cache server is not running" do
assert DeviceCache.refresh_cache() == :ok
end
test "lookup_device/1 returns nil on cache miss without crashing when server is not running" do
assert DeviceCache.lookup_device("TEST-DEVICE") == nil
end
test "lookup_device/1 returns nil for nil input" do
assert DeviceCache.lookup_device(nil) == nil
end
describe "wildcard pattern matching via cache" do
setup do
# Seed the cache directly with a small device list so we can exercise
# the pattern-matching branches without running the GenServer.
devices = [
%Aprsme.Devices{identifier: "APSK21", vendor: "Kenwood", model: "TH-D74"},
%Aprsme.Devices{identifier: "APS???", vendor: "Kenwood", model: "Unknown"},
%Aprsme.Devices{identifier: "EXACT", vendor: "Test", model: "Exact"}
]
Aprsme.Cache.put(:device_cache, :all_devices, devices)
on_exit(fn -> Aprsme.Cache.put(:device_cache, :all_devices, []) end)
:ok
end
test "exact identifier matches literally" do
assert %{vendor: "Test"} = DeviceCache.lookup_device("EXACT")
end
test "returns nil for non-matching identifier" do
assert DeviceCache.lookup_device("DOES-NOT-EXIST") == nil
end
test "prefers exact match when both wildcard and exact apply" do
# Both EXACT (literal) and APS??? would not both apply here; just verify
# that exact-string hits don't get confused with wildcard entries.
assert %{identifier: "EXACT"} = DeviceCache.lookup_device("EXACT")
end
test "? wildcards match a single character" do
# APSK21 matches APS??? (three wildcards after APS)
assert %{identifier: _} = DeviceCache.lookup_device("APSK21")
end
test "wildcard pattern does not match if length mismatches" do
# APS??? requires exactly 3 trailing chars; "APSXX" has only 2 and
# shouldn't match the wildcard entry (nor is there any other entry).
assert DeviceCache.lookup_device("APSXX") == nil
end
end
describe "GenServer callbacks" do
test "init/1 schedules an initial load and returns an empty state" do
assert {:ok, state} = DeviceCache.init([])
assert state == %{initial_load_done: false}
assert_receive :initial_load, 100
end
test "handle_call(:refresh_cache, _, state) returns :ok" do
state = %{initial_load_done: true}
assert {:reply, :ok, ^state} = DeviceCache.handle_call(:refresh_cache, self(), state)
end
test "handle_cast(:refresh_cache_async, state) keeps the state" do
state = %{initial_load_done: true}
assert {:noreply, ^state} = DeviceCache.handle_cast(:refresh_cache_async, state)
end
test "handle_info(:initial_load, state) flips initial_load_done and schedules a periodic refresh" do
assert {:noreply, %{initial_load_done: true}} =
DeviceCache.handle_info(:initial_load, %{initial_load_done: false})
end
test "handle_info(:refresh_cache, state) keeps the state and schedules a periodic refresh" do
state = %{initial_load_done: true}
assert {:noreply, ^state} = DeviceCache.handle_info(:refresh_cache, state)
end
test "initial_load in non-test env loads real devices from the DB" do
# The load_devices_into_cache non-test branch reads Repo.all(Devices).
# Swap env, then trigger and restore.
original_env = Application.get_env(:aprsme, :env)
Application.put_env(:aprsme, :env, :dev)
on_exit(fn -> Application.put_env(:aprsme, :env, original_env) end)
try do
result = DeviceCache.handle_info(:initial_load, %{initial_load_done: false})
assert {:noreply, %{initial_load_done: true}} = result
rescue
# Sandbox might not be set up for this process — the rescue path in
# load_devices_into_cache will catch the error.
_ -> :ok
end
end
end
end