defmodule Aprsme.BadPacketTest do use Aprsme.DataCase, async: true alias Aprsme.BadPacket alias Aprsme.Repo describe "changeset/2" do test "valid changeset with all fields" do attrs = %{ raw_packet: "N0CALL>APRS:>Test packet", error_message: "Invalid format", error_type: "parse_error", attempted_at: DateTime.utc_now() } changeset = BadPacket.changeset(%BadPacket{}, attrs) assert changeset.valid? assert get_change(changeset, :raw_packet) == attrs.raw_packet assert get_change(changeset, :error_message) == attrs.error_message assert get_change(changeset, :error_type) == attrs.error_type assert get_change(changeset, :attempted_at) == attrs.attempted_at end test "valid changeset with only required fields" do attrs = %{raw_packet: "N0CALL>APRS:>Test packet"} changeset = BadPacket.changeset(%BadPacket{}, attrs) assert changeset.valid? assert get_change(changeset, :raw_packet) == attrs.raw_packet end test "invalid changeset without raw_packet" do attrs = %{ error_message: "Invalid format", error_type: "parse_error" } changeset = BadPacket.changeset(%BadPacket{}, attrs) refute changeset.valid? assert %{raw_packet: ["can't be blank"]} = errors_on(changeset) end test "invalid changeset with empty raw_packet" do attrs = %{raw_packet: ""} changeset = BadPacket.changeset(%BadPacket{}, attrs) refute changeset.valid? assert %{raw_packet: ["can't be blank"]} = errors_on(changeset) end test "invalid changeset with nil raw_packet" do attrs = %{raw_packet: nil} changeset = BadPacket.changeset(%BadPacket{}, attrs) refute changeset.valid? assert %{raw_packet: ["can't be blank"]} = errors_on(changeset) end end describe "recent/2" do setup do # Create test bad packets with different timestamps now = DateTime.utc_now() old_packet = insert_bad_packet(%{ raw_packet: "OLD>APRS:>Old packet", attempted_at: DateTime.add(now, -3600, :second) }) recent_packet = insert_bad_packet(%{ raw_packet: "RECENT>APRS:>Recent packet", attempted_at: DateTime.add(now, -60, :second) }) newest_packet = insert_bad_packet(%{ raw_packet: "NEWEST>APRS:>Newest packet", attempted_at: now }) %{ old_packet: old_packet, recent_packet: recent_packet, newest_packet: newest_packet } end test "returns packets ordered by attempted_at descending", %{ old_packet: old_packet, recent_packet: recent_packet, newest_packet: newest_packet } do result = Repo.all(BadPacket.recent()) packet_ids = Enum.map(result, & &1.id) assert packet_ids == [newest_packet.id, recent_packet.id, old_packet.id] end test "limits results to specified number" do # Insert 5 more packets to test limiting for i <- 1..5 do insert_bad_packet(%{raw_packet: "TEST#{i}>APRS:>Test packet #{i}"}) end result = BadPacket |> BadPacket.recent(3) |> Repo.all() assert length(result) == 3 end test "works with custom query" do query = from(b in BadPacket, where: like(b.raw_packet, "%RECENT%")) result = query |> BadPacket.recent(10) |> Repo.all() assert length(result) == 1 assert hd(result).raw_packet =~ "RECENT" end test "default limit is 100" do # Test that default limit is applied correctly query = BadPacket.recent() _query = BadPacket.recent() # Check the query structure contains limit _query = BadPacket.recent() result = Repo.all(query) # Verify the query works and the default limit is reasonable assert is_struct(query, Ecto.Query) assert length(result) <= 100 end end describe "by_error_type/2" do setup do parse_error = insert_bad_packet(%{ raw_packet: "PARSE>APRS:>Parse error packet", error_type: "parse_error" }) format_error = insert_bad_packet(%{ raw_packet: "FORMAT>APRS:>Format error packet", error_type: "format_error" }) another_parse_error = insert_bad_packet(%{ raw_packet: "PARSE2>APRS:>Another parse error", error_type: "parse_error" }) %{ parse_error: parse_error, format_error: format_error, another_parse_error: another_parse_error } end test "returns packets with specified error type", %{ parse_error: parse_error, another_parse_error: another_parse_error } do result = "parse_error" |> BadPacket.by_error_type() |> Repo.all() packet_ids = result |> Enum.map(& &1.id) |> Enum.sort() expected_ids = Enum.sort([parse_error.id, another_parse_error.id]) assert packet_ids == expected_ids end test "returns empty list for non-existent error type" do result = "non_existent_error" |> BadPacket.by_error_type() |> Repo.all() assert result == [] end test "works with custom query" do query = from(b in BadPacket, where: like(b.raw_packet, "%FORMAT%")) result = query |> BadPacket.by_error_type("format_error") |> Repo.all() assert length(result) == 1 assert hd(result).error_type == "format_error" end test "is case sensitive" do result = "Parse_Error" |> BadPacket.by_error_type() |> Repo.all() assert result == [] end end describe "count_recent/1" do test "counts packets within specified hours" do now = DateTime.utc_now() # Insert packets at different times insert_bad_packet(%{ raw_packet: "RECENT1>APRS:>Recent packet 1", # 30 minutes ago attempted_at: DateTime.add(now, -30 * 60, :second) }) insert_bad_packet(%{ raw_packet: "RECENT2>APRS:>Recent packet 2", # 2 hours ago attempted_at: DateTime.add(now, -2 * 3600, :second) }) insert_bad_packet(%{ raw_packet: "OLD>APRS:>Old packet", # 25 hours ago attempted_at: DateTime.add(now, -25 * 3600, :second) }) count = 24 |> BadPacket.count_recent() |> Repo.one() assert count == 2 end test "default is 24 hours" do now = DateTime.utc_now() insert_bad_packet(%{ raw_packet: "RECENT>APRS:>Recent packet", # 12 hours ago attempted_at: DateTime.add(now, -12 * 3600, :second) }) insert_bad_packet(%{ raw_packet: "OLD>APRS:>Old packet", # 25 hours ago attempted_at: DateTime.add(now, -25 * 3600, :second) }) count = Repo.one(BadPacket.count_recent()) assert count == 1 end test "returns 0 when no packets in time range" do now = DateTime.utc_now() insert_bad_packet(%{ raw_packet: "OLD>APRS:>Old packet", # 25 hours ago attempted_at: DateTime.add(now, -25 * 3600, :second) }) count = 1 |> BadPacket.count_recent() |> Repo.one() assert count == 0 end test "handles edge case at exact boundary" do now = DateTime.utc_now() # Insert packet exactly 24 hours ago insert_bad_packet(%{ raw_packet: "BOUNDARY>APRS:>Boundary packet", attempted_at: DateTime.add(now, -24 * 3600, :second) }) count = 24 |> BadPacket.count_recent() |> Repo.one() # Should not include the packet exactly at the boundary assert count == 0 end end # Helper function to insert bad packets defp insert_bad_packet(attrs) do default_attrs = %{ raw_packet: "DEFAULT>APRS:>Default packet", error_message: "Test error", error_type: "test_error", attempted_at: DateTime.utc_now() } attrs = Map.merge(default_attrs, attrs) %BadPacket{} |> BadPacket.changeset(attrs) |> Repo.insert!() end end