diff --git a/test/gc_index_relay/nostr/filter_test.exs b/test/gc_index_relay/nostr/filter_test.exs index 5654a37..d1478ef 100644 --- a/test/gc_index_relay/nostr/filter_test.exs +++ b/test/gc_index_relay/nostr/filter_test.exs @@ -170,7 +170,10 @@ defmodule GcIndexRelay.Nostr.FilterTest do test "returns {:error, message} for mixed valid and invalid #{field}" do valid_value = String.duplicate("a", 64) invalid_value = "short" - assert {:error, message} = Filter.from_map(%{unquote(field) => [valid_value, invalid_value]}) + + assert {:error, message} = + Filter.from_map(%{unquote(field) => [valid_value, invalid_value]}) + assert message =~ unquote(invalid_msg) end diff --git a/test/gc_index_relay/nostr/pub_event_test.exs b/test/gc_index_relay/nostr/pub_event_test.exs new file mode 100644 index 0000000..8b10cc2 --- /dev/null +++ b/test/gc_index_relay/nostr/pub_event_test.exs @@ -0,0 +1,178 @@ +defmodule GcIndexRelay.Nostr.PubEventTest do + use ExUnit.Case, async: true + + alias GcIndexRelay.Nostr.Event + alias GcIndexRelay.Nostr.PubEvent + alias GcIndexRelay.Nostr.Tag + + import GcIndexRelay.NostrFixtures + + @moduletag :unit + + describe "to_db/1" do + test "converts tags with name and value" do + pub_event = valid_pub_event_fixture(tags: [["e", "abc123"], ["p", "def456"]]) + event = PubEvent.to_db(pub_event) + + assert [tag1, tag2] = event.tags + assert %Tag{name: "e", value: "abc123", additional_values: []} = tag1 + assert %Tag{name: "p", value: "def456", additional_values: []} = tag2 + end + + test "converts tags with additional values" do + tags = [["p", "pubkey_hex", "wss://relay.example.com", "alice"]] + pub_event = valid_pub_event_fixture(tags: tags) + event = PubEvent.to_db(pub_event) + + assert [tag] = event.tags + assert %Tag{name: "p", value: "pubkey_hex"} = tag + assert tag.additional_values == ["wss://relay.example.com", "alice"] + end + + test "preserves empty content" do + pub_event = valid_pub_event_fixture(content: "") + event = PubEvent.to_db(pub_event) + + assert event.content == "" + end + end + + describe "from_db/1" do + test "returns {:error, :not_found} for nil" do + assert {:error, :not_found} = PubEvent.from_db(nil) + end + + test "converts Tag structs back to nested lists" do + event = %Event{ + id: Base.decode16!(String.duplicate("ab", 32), case: :lower), + pubkey: Base.decode16!(String.duplicate("cd", 32), case: :lower), + created_at: ~U[2021-12-20 17:46:40Z], + kind: 1, + content: "test", + sig: Base.decode16!(String.duplicate("ef", 64), case: :lower), + tags: [ + %Tag{name: "e", value: "event_id", additional_values: []}, + %Tag{name: "p", value: "pubkey", additional_values: ["relay", "petname"]} + ] + } + + assert {:ok, pub_event} = PubEvent.from_db(event) + assert pub_event.tags == [["e", "event_id"], ["p", "pubkey", "relay", "petname"]] + end + + test "preserves kind and content" do + event = %Event{ + id: Base.decode16!(String.duplicate("ab", 32), case: :lower), + pubkey: Base.decode16!(String.duplicate("cd", 32), case: :lower), + created_at: ~U[2021-12-20 17:46:40Z], + kind: 30023, + content: "long-form content", + sig: Base.decode16!(String.duplicate("ef", 64), case: :lower), + tags: [] + } + + assert {:ok, pub_event} = PubEvent.from_db(event) + assert pub_event.kind == 30023 + assert pub_event.content == "long-form content" + end + + test "handles empty content" do + event = %Event{ + id: Base.decode16!(String.duplicate("ab", 32), case: :lower), + pubkey: Base.decode16!(String.duplicate("cd", 32), case: :lower), + created_at: ~U[2021-12-20 17:46:40Z], + kind: 1, + content: "", + sig: Base.decode16!(String.duplicate("ef", 64), case: :lower), + tags: [] + } + + assert {:ok, pub_event} = PubEvent.from_db(event) + assert pub_event.content == "" + end + end + + describe "round-trip: to_db |> from_db" do + test "preserves all fields for a basic event" do + pub_event = valid_pub_event_fixture(content: "round-trip test") + + assert {:ok, result} = pub_event |> PubEvent.to_db() |> PubEvent.from_db() + + assert result.id == pub_event.id + assert result.pubkey == pub_event.pubkey + assert result.created_at == pub_event.created_at + assert result.kind == pub_event.kind + assert result.content == pub_event.content + assert result.sig == pub_event.sig + assert result.tags == pub_event.tags + end + + test "preserves event with empty tags" do + pub_event = valid_pub_event_fixture(tags: []) + + assert {:ok, result} = pub_event |> PubEvent.to_db() |> PubEvent.from_db() + assert result.tags == [] + end + + test "preserves event with tags" do + tags = [ + ["e", "abc123"], + ["p", "def456", "wss://relay.example.com"], + ["t", "nostr", "extra1", "extra2"] + ] + + pub_event = valid_pub_event_fixture(tags: tags) + + assert {:ok, result} = pub_event |> PubEvent.to_db() |> PubEvent.from_db() + assert result.tags == tags + end + + test "preserves event with empty content" do + pub_event = valid_pub_event_fixture(content: "") + + assert {:ok, result} = pub_event |> PubEvent.to_db() |> PubEvent.from_db() + assert result.content == "" + end + + test "preserves different keypairs" do + pub_event_1 = valid_pub_event_fixture(keypair: :keypair1) + pub_event_2 = valid_pub_event_fixture(keypair: :keypair2) + + assert {:ok, result_1} = pub_event_1 |> PubEvent.to_db() |> PubEvent.from_db() + assert {:ok, result_2} = pub_event_2 |> PubEvent.to_db() |> PubEvent.from_db() + + assert result_1.pubkey == pub_event_1.pubkey + assert result_2.pubkey == pub_event_2.pubkey + refute result_1.pubkey == result_2.pubkey + end + end + + describe "to_db/1 with invalid hex input" do + test "raises on invalid hex in id" do + pub_event = valid_pub_event_fixture() + invalid = %{pub_event | id: String.duplicate("zz", 32)} + + assert_raise BadStructError, fn -> + PubEvent.to_db(invalid) + end + end + + test "raises on invalid hex in pubkey" do + pub_event = valid_pub_event_fixture() + invalid = %{pub_event | pubkey: String.duplicate("zz", 32)} + + assert_raise BadStructError, fn -> + PubEvent.to_db(invalid) + end + end + + test "raises on invalid hex in sig" do + pub_event = valid_pub_event_fixture() + invalid = %{pub_event | sig: String.duplicate("zz", 64)} + + assert_raise BadStructError, fn -> + PubEvent.to_db(invalid) + end + end + end +end