Add NIP-01 filter validation and AND/OR matching engine
This commit is contained in:
81
test/parrhesia/protocol/filter_test.exs
Normal file
81
test/parrhesia/protocol/filter_test.exs
Normal file
@@ -0,0 +1,81 @@
|
||||
defmodule Parrhesia.Protocol.FilterTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Parrhesia.Protocol.EventValidator
|
||||
alias Parrhesia.Protocol.Filter
|
||||
|
||||
test "validates a supported filter set" do
|
||||
filters = [
|
||||
%{
|
||||
"ids" => [String.duplicate("a", 64)],
|
||||
"authors" => [String.duplicate("b", 64)],
|
||||
"kinds" => [1, 3_000],
|
||||
"since" => 1_700_000_000,
|
||||
"until" => 1_900_000_000,
|
||||
"limit" => 100,
|
||||
"#p" => [String.duplicate("c", 64)]
|
||||
}
|
||||
]
|
||||
|
||||
assert :ok = Filter.validate_filters(filters)
|
||||
end
|
||||
|
||||
test "rejects unsupported filter keys" do
|
||||
filters = [%{"search" => "hello"}]
|
||||
|
||||
assert {:error, :invalid_filter_key} = Filter.validate_filters(filters)
|
||||
|
||||
assert Filter.error_message(:invalid_filter_key) ==
|
||||
"invalid: filter contains unknown elements"
|
||||
end
|
||||
|
||||
test "rejects invalid ids/authors/kinds" do
|
||||
assert {:error, :invalid_ids} = Filter.validate_filters([%{"ids" => ["abc"]}])
|
||||
|
||||
assert {:error, :invalid_authors} =
|
||||
Filter.validate_filters([%{"authors" => [String.duplicate("A", 64)]}])
|
||||
|
||||
assert {:error, :invalid_kinds} = Filter.validate_filters([%{"kinds" => ["1"]}])
|
||||
end
|
||||
|
||||
test "matches with AND semantics inside filter and OR across filters" do
|
||||
event = valid_event()
|
||||
|
||||
matching_filter = %{
|
||||
"authors" => [event["pubkey"]],
|
||||
"kinds" => [event["kind"]],
|
||||
"#e" => ["ref-2"],
|
||||
"since" => event["created_at"],
|
||||
"until" => event["created_at"]
|
||||
}
|
||||
|
||||
non_matching_filter = %{"authors" => [String.duplicate("d", 64)]}
|
||||
|
||||
assert Filter.matches_filter?(event, matching_filter)
|
||||
refute Filter.matches_filter?(event, non_matching_filter)
|
||||
|
||||
assert Filter.matches_any?(event, [non_matching_filter, matching_filter])
|
||||
refute Filter.matches_any?(event, [non_matching_filter])
|
||||
end
|
||||
|
||||
test "rejects when req includes more filters than allowed" do
|
||||
filters = Enum.map(1..17, fn _ -> %{"kinds" => [1]} end)
|
||||
|
||||
assert {:error, :too_many_filters} = Filter.validate_filters(filters)
|
||||
end
|
||||
|
||||
defp valid_event do
|
||||
created_at = System.system_time(:second)
|
||||
|
||||
base_event = %{
|
||||
"pubkey" => String.duplicate("1", 64),
|
||||
"created_at" => created_at,
|
||||
"kind" => 1,
|
||||
"tags" => [["e", "ref-1"], ["e", "ref-2"], ["p", String.duplicate("2", 64)]],
|
||||
"content" => "hello",
|
||||
"sig" => String.duplicate("3", 128)
|
||||
}
|
||||
|
||||
Map.put(base_event, "id", EventValidator.compute_id(base_event))
|
||||
end
|
||||
end
|
||||
@@ -40,6 +40,21 @@ defmodule Parrhesia.Web.ConnectionTest do
|
||||
assert Jason.decode!(response) == ["NOTICE", "invalid: malformed JSON"]
|
||||
end
|
||||
|
||||
test "REQ with invalid filter returns CLOSED and does not subscribe" do
|
||||
{:ok, state} = Connection.init(%{})
|
||||
|
||||
req_payload = Jason.encode!(["REQ", "sub-123", %{"kinds" => ["1"]}])
|
||||
|
||||
assert {:push, {:text, response}, ^state} =
|
||||
Connection.handle_in({req_payload, [opcode: :text]}, state)
|
||||
|
||||
assert Jason.decode!(response) == [
|
||||
"CLOSED",
|
||||
"sub-123",
|
||||
"invalid: kinds must be a non-empty array of integers between 0 and 65535"
|
||||
]
|
||||
end
|
||||
|
||||
test "valid EVENT currently replies with unsupported OK" do
|
||||
{:ok, state} = Connection.init(%{})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user