defmodule Aprsme.PacketFieldWhitelistTest do use ExUnit.Case, async: true use ExUnitProperties alias Aprsme.PacketFieldWhitelist describe "allowed_fields/0" do test "returns a list of strings" do fields = PacketFieldWhitelist.allowed_fields() assert is_list(fields) assert match?([_ | _], fields) assert Enum.all?(fields, &is_binary/1) end test "contains core schema fields" do fields = PacketFieldWhitelist.allowed_fields() assert "sender" in fields assert "base_callsign" in fields assert "lat" in fields assert "lon" in fields assert "received_at" in fields assert "raw_packet" in fields end end describe "allowed?/1" do test "accepts atoms that match allowed fields" do assert PacketFieldWhitelist.allowed?(:sender) assert PacketFieldWhitelist.allowed?(:raw_packet) assert PacketFieldWhitelist.allowed?(:lat) end test "accepts strings that match allowed fields" do assert PacketFieldWhitelist.allowed?("sender") assert PacketFieldWhitelist.allowed?("raw_packet") end test "rejects unknown atoms" do refute PacketFieldWhitelist.allowed?(:totally_bogus) refute PacketFieldWhitelist.allowed?(:__struct__) end test "rejects unknown strings" do refute PacketFieldWhitelist.allowed?("id") refute PacketFieldWhitelist.allowed?("") end test "rejects non-atom, non-binary input" do refute PacketFieldWhitelist.allowed?(123) refute PacketFieldWhitelist.allowed?(nil) refute PacketFieldWhitelist.allowed?(%{}) refute PacketFieldWhitelist.allowed?([]) end end describe "filter_fields/1" do test "keeps allowed atom keys" do input = %{sender: "K5ABC", lat: 30.0, bogus: "x"} result = PacketFieldWhitelist.filter_fields(input) assert result[:sender] == "K5ABC" assert result[:lat] == 30.0 refute Map.has_key?(result, :bogus) end test "keeps allowed string keys" do input = %{"sender" => "K5ABC", "unknown" => "x"} result = PacketFieldWhitelist.filter_fields(input) assert result["sender"] == "K5ABC" refute Map.has_key?(result, "unknown") end test "preserves whichever key type caller used" do atom_in = %{sender: "a"} string_in = %{"sender" => "b"} assert PacketFieldWhitelist.filter_fields(atom_in) == %{sender: "a"} assert PacketFieldWhitelist.filter_fields(string_in) == %{"sender" => "b"} end test "returns empty map when no keys are allowed" do assert PacketFieldWhitelist.filter_fields(%{bogus: 1, junk: 2}) == %{} end test "returns empty map on empty input" do assert PacketFieldWhitelist.filter_fields(%{}) == %{} end end describe "invalid_fields/1" do test "returns only the disallowed keys as strings" do input = %{"junk" => 2, sender: "x", bogus: 1} assert PacketFieldWhitelist.invalid_fields(input) == ["bogus", "junk"] end test "returns empty list when every key is allowed" do assert PacketFieldWhitelist.invalid_fields(%{sender: "x", lat: 1.0}) == [] end test "returns sorted, deduplicated-looking output" do input = %{z_bogus: 1, a_bogus: 2} result = PacketFieldWhitelist.invalid_fields(input) assert result == Enum.sort(result) end end describe "property: filter_fields/1 is idempotent" do property "running filter twice equals running once" do check all(map <- map_of(atom(:alphanumeric), term())) do once = PacketFieldWhitelist.filter_fields(map) twice = PacketFieldWhitelist.filter_fields(once) assert once == twice end end property "every key in the output satisfies allowed?/1" do check all(map <- map_of(atom(:alphanumeric), term())) do result = PacketFieldWhitelist.filter_fields(map) assert Enum.all?(Map.keys(result), &PacketFieldWhitelist.allowed?/1) end end property "invalid_fields and filter_fields partition the input" do check all(map <- map_of(atom(:alphanumeric), term())) do kept = map |> PacketFieldWhitelist.filter_fields() |> Map.keys() |> Enum.map(&to_string/1) dropped = PacketFieldWhitelist.invalid_fields(map) all_keys = map |> Map.keys() |> Enum.map(&to_string/1) assert Enum.sort(kept ++ dropped) == Enum.sort(all_keys) end end end end