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, 1000 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 "refresh_cache/0 calls into the running GenServer process" do # Start a temporary DeviceCache to exercise the live-process branch # (line 51: _pid -> GenServer.call(...)). {:ok, pid} = GenServer.start_link(DeviceCache, []) original_pid = Process.whereis(DeviceCache) if original_pid, do: :erlang.unregister(DeviceCache) Process.register(pid, DeviceCache) try do assert DeviceCache.refresh_cache() == :ok after if Process.whereis(DeviceCache) == pid, do: :erlang.unregister(DeviceCache) if original_pid && !Process.whereis(DeviceCache) do Process.register(original_pid, DeviceCache) end Process.exit(pid, :normal) end 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