Implement MIP-01 #h query guardrails and ordering tests

This commit is contained in:
2026-03-13 21:58:53 +01:00
parent cf5ae772b2
commit fff507d760
8 changed files with 258 additions and 5 deletions

View File

@@ -57,6 +57,47 @@ defmodule Parrhesia.Policy.EventPolicyTest do
EventPolicy.authorize_write(event, MapSet.new([String.duplicate("c", 64)]))
end
test "requires #h when querying kind 445" do
filter = %{"kinds" => [445]}
assert {:error, :marmot_group_h_tag_required} =
EventPolicy.authorize_read([filter], MapSet.new())
assert :ok =
EventPolicy.authorize_read(
[%{"kinds" => [445], "#h" => [String.duplicate("a", 64)]}],
MapSet.new()
)
end
test "enforces max #h values and query window for kind 445 filters" do
Application.put_env(
:parrhesia,
:policies,
marmot_group_max_h_values_per_filter: 1,
marmot_group_max_query_window_seconds: 10,
marmot_require_h_for_group_queries: true
)
too_many_groups = %{
"kinds" => [445],
"#h" => [String.duplicate("a", 64), String.duplicate("b", 64)]
}
wide_window = %{
"kinds" => [445],
"#h" => [String.duplicate("c", 64)],
"since" => 1,
"until" => 100
}
assert {:error, :marmot_group_h_values_exceeded} =
EventPolicy.authorize_read([too_many_groups], MapSet.new())
assert {:error, :marmot_group_filter_window_too_wide} =
EventPolicy.authorize_read([wide_window], MapSet.new())
end
test "rejects mls kinds when feature is disabled" do
Application.put_env(:parrhesia, :features, nip_ee_mls: false)

View File

@@ -268,6 +268,51 @@ defmodule Parrhesia.Storage.Adapters.Postgres.EventsQueryCountTest do
assert result["id"] == matching["id"]
end
test "query/3 routes Marmot group events by #h and keeps deterministic order" do
group_id = String.duplicate("a", 64)
other_group_id = String.duplicate("b", 64)
older =
persist_event(%{
"kind" => 445,
"created_at" => 1_700_000_600,
"tags" => [["h", group_id]],
"content" => Base.encode64("older")
})
tie_a =
persist_event(%{
"kind" => 445,
"created_at" => 1_700_000_601,
"tags" => [["h", group_id]],
"content" => Base.encode64("tie-a")
})
tie_b =
persist_event(%{
"kind" => 445,
"created_at" => 1_700_000_601,
"tags" => [["h", group_id]],
"content" => Base.encode64("tie-b")
})
_other_group =
persist_event(%{
"kind" => 445,
"created_at" => 1_700_000_602,
"tags" => [["h", other_group_id]],
"content" => Base.encode64("other-group")
})
assert {:ok, results} =
Events.query(%{}, [%{"kinds" => [445], "#h" => [group_id]}], [])
tie_winner_id = Enum.min([tie_a["id"], tie_b["id"]])
tie_loser_id = Enum.max([tie_a["id"], tie_b["id"]])
assert Enum.map(results, & &1["id"]) == [tie_winner_id, tie_loser_id, older["id"]]
end
test "mls keypackage relay list kind 10051 follows replaceable conflict semantics" do
author = String.duplicate("c", 64)

View File

@@ -93,6 +93,19 @@ defmodule Parrhesia.Web.ConnectionTest do
assert Enum.any?(decoded, fn frame -> frame == ["AUTH", state.auth_challenge] end)
end
test "kind 445 REQ without #h is rejected" do
state = connection_state()
req_payload = Jason.encode!(["REQ", "sub-445", %{"kinds" => [445]}])
assert {:push, frames, ^state} = Connection.handle_in({req_payload, [opcode: :text]}, state)
decoded = Enum.map(frames, fn {:text, frame} -> Jason.decode!(frame) end)
assert ["CLOSED", "sub-445", "restricted: kind 445 queries must include a #h tag"] =
Enum.find(decoded, fn frame -> List.first(frame) == "CLOSED" end)
end
test "valid EVENT stores event and returns accepted OK" do
state = connection_state()