phase1: add websocket edge and nostr message codec

This commit is contained in:
2026-03-13 19:00:41 +01:00
parent 5e478cd305
commit 953ccb60f4
11 changed files with 360 additions and 4 deletions

View File

@@ -11,5 +11,10 @@ defmodule Parrhesia.ApplicationTest do
assert is_pid(Process.whereis(Parrhesia.Policy.Supervisor))
assert is_pid(Process.whereis(Parrhesia.Web.Endpoint))
assert is_pid(Process.whereis(Parrhesia.Tasks.Supervisor))
assert Enum.any?(Supervisor.which_children(Parrhesia.Web.Endpoint), fn {_id, pid, _type,
modules} ->
is_pid(pid) and modules == [Bandit]
end)
end
end

View File

@@ -0,0 +1,48 @@
defmodule Parrhesia.ProtocolTest do
use ExUnit.Case, async: true
alias Parrhesia.Protocol
test "decodes valid EVENT frame" do
payload =
Jason.encode!([
"EVENT",
%{
"id" => String.duplicate("0", 64),
"pubkey" => String.duplicate("1", 64),
"created_at" => 1_715_000_000,
"kind" => 1,
"tags" => [["p", String.duplicate("2", 64)]],
"content" => "hello",
"sig" => String.duplicate("3", 128)
}
])
assert {:ok, {:event, event}} = Protocol.decode_client(payload)
assert event["kind"] == 1
assert event["content"] == "hello"
end
test "decodes valid REQ and CLOSE frames" do
req_payload = Jason.encode!(["REQ", "sub-1", %{"authors" => [String.duplicate("a", 64)]}])
close_payload = Jason.encode!(["CLOSE", "sub-1"])
assert {:ok, {:req, "sub-1", [%{"authors" => [_author]}]}} =
Protocol.decode_client(req_payload)
assert {:ok, {:close, "sub-1"}} = Protocol.decode_client(close_payload)
end
test "returns decode errors for malformed messages" do
assert {:error, :invalid_json} = Protocol.decode_client("not-json")
assert {:error, :invalid_filters} = Protocol.decode_client(Jason.encode!(["REQ", "sub-1"]))
assert {:error, :invalid_event} =
Protocol.decode_client(Jason.encode!(["EVENT", %{"id" => "nope"}]))
end
test "encodes relay messages" do
frame = Protocol.encode_relay({:closed, "sub-1", "closed: subscription closed"})
assert Jason.decode!(frame) == ["CLOSED", "sub-1", "closed: subscription closed"]
end
end

View File

@@ -0,0 +1,69 @@
defmodule Parrhesia.Web.ConnectionTest do
use ExUnit.Case, async: true
alias Parrhesia.Web.Connection
test "REQ registers subscription and replies with EOSE" do
{:ok, state} = Connection.init(%{})
req_payload = Jason.encode!(["REQ", "sub-123", %{"kinds" => [1]}])
assert {:push, {:text, response}, next_state} =
Connection.handle_in({req_payload, [opcode: :text]}, state)
assert MapSet.member?(next_state.subscriptions, "sub-123")
assert Jason.decode!(response) == ["EOSE", "sub-123"]
end
test "CLOSE removes subscription and replies with CLOSED" do
{:ok, state} = Connection.init(%{})
req_payload = Jason.encode!(["REQ", "sub-123", %{"kinds" => [1]}])
{:push, _, subscribed_state} = Connection.handle_in({req_payload, [opcode: :text]}, state)
close_payload = Jason.encode!(["CLOSE", "sub-123"])
assert {:push, {:text, response}, next_state} =
Connection.handle_in({close_payload, [opcode: :text]}, subscribed_state)
refute MapSet.member?(next_state.subscriptions, "sub-123")
assert Jason.decode!(response) == ["CLOSED", "sub-123", "closed: subscription closed"]
end
test "invalid input returns NOTICE" do
{:ok, state} = Connection.init(%{})
assert {:push, {:text, response}, ^state} =
Connection.handle_in({"not-json", [opcode: :text]}, state)
assert Jason.decode!(response) == ["NOTICE", "error:invalid: malformed JSON"]
end
test "EVENT currently replies with unsupported OK" do
{:ok, state} = Connection.init(%{})
payload =
Jason.encode!([
"EVENT",
%{
"id" => String.duplicate("0", 64),
"pubkey" => String.duplicate("1", 64),
"created_at" => 1_715_000_000,
"kind" => 1,
"tags" => [],
"content" => "hello",
"sig" => String.duplicate("3", 128)
}
])
assert {:push, {:text, response}, ^state} =
Connection.handle_in({payload, [opcode: :text]}, state)
assert Jason.decode!(response) == [
"OK",
String.duplicate("0", 64),
false,
"error:unsupported: EVENT ingest not implemented"
]
end
end