Align websocket and admin APIs with shared surfaces

This commit is contained in:
2026-03-17 01:08:41 +01:00
parent e8fd6c7328
commit 02f2584757
6 changed files with 298 additions and 40 deletions

View File

@@ -120,9 +120,16 @@ defmodule Parrhesia.API.SyncTest do
assert sync_stats["sync"]["servers_total"] == 1
assert sync_stats["sync"]["query_runs"] == 1
assert {:ok, execute_stats} = Admin.execute("stats", %{}, manager: manager)
assert execute_stats["sync"]["servers_total"] == 1
assert {:ok, health} = Admin.health(manager: manager)
assert health["status"] == "ok"
assert health["sync"]["servers_total"] == 1
assert {:ok, execute_health} = Admin.execute("health", %{}, manager: manager)
assert execute_health["status"] == "ok"
assert execute_health["sync"]["servers_total"] == 1
end
defp start_sync_manager do

View File

@@ -3,6 +3,8 @@ defmodule Parrhesia.Web.ConnectionTest do
alias Ecto.Adapters.SQL.Sandbox
alias Parrhesia.API.ACL
alias Parrhesia.API.Events
alias Parrhesia.API.RequestContext
alias Parrhesia.Negentropy.Engine
alias Parrhesia.Negentropy.Message
alias Parrhesia.Protocol.EventValidator
@@ -11,6 +13,7 @@ defmodule Parrhesia.Web.ConnectionTest do
setup do
:ok = Sandbox.checkout(Repo)
ensure_stream_runtime_started()
:ok
end
@@ -738,16 +741,47 @@ defmodule Parrhesia.Web.ConnectionTest do
test "CLOSE removes subscription and replies with CLOSED" do
state = subscribed_connection_state([])
subscription = state.subscriptions["sub-1"]
[{stream_pid, _value}] = Registry.lookup(Parrhesia.API.Stream.Registry, subscription.ref)
monitor_ref = Process.monitor(stream_pid)
close_payload = JSON.encode!(["CLOSE", "sub-1"])
assert {:push, {:text, response}, next_state} =
Connection.handle_in({close_payload, [opcode: :text]}, state)
assert_receive {:DOWN, ^monitor_ref, :process, ^stream_pid, :normal}
refute Map.has_key?(next_state.subscriptions, "sub-1")
assert JSON.decode!(response) == ["CLOSED", "sub-1", "error: subscription closed"]
end
test "REQ live delivery is bridged through API.Stream" do
state = subscribed_connection_state([])
subscription = state.subscriptions["sub-1"]
subscription_ref = subscription.ref
event = valid_event(%{"content" => "stream-live"}) |> recalculate_event_id()
assert {:ok, %{accepted: true}} = Events.publish(event, context: %RequestContext{})
assert_receive {:parrhesia, :event, ^subscription_ref, "sub-1", received_event}
assert received_event["id"] == event["id"]
assert {:ok, queued_state} =
Connection.handle_info(
{:parrhesia, :event, subscription_ref, "sub-1", received_event},
state
)
assert queued_state.outbound_queue_size == 1
assert_receive :drain_outbound_queue
assert {:push, [{:text, payload}], drained_state} =
Connection.handle_info(:drain_outbound_queue, queued_state)
assert drained_state.outbound_queue_size == 0
assert JSON.decode!(payload) == ["EVENT", "sub-1", received_event]
end
test "fanout_event enqueues and drains matching events" do
state = subscribed_connection_state([])
event = live_event("event-1", 1)
@@ -841,6 +875,26 @@ defmodule Parrhesia.Web.ConnectionTest do
state
end
defp ensure_stream_runtime_started do
if is_nil(Process.whereis(Parrhesia.Subscriptions.Supervisor)) do
start_supervised!({Parrhesia.Subscriptions.Supervisor, []})
end
if is_nil(Process.whereis(Parrhesia.Subscriptions.Index)) do
start_supervised!({Parrhesia.Subscriptions.Index, name: Parrhesia.Subscriptions.Index})
end
if is_nil(Process.whereis(Parrhesia.API.Stream.Registry)) do
start_supervised!({Registry, keys: :unique, name: Parrhesia.API.Stream.Registry})
end
if is_nil(Process.whereis(Parrhesia.API.Stream.Supervisor)) do
start_supervised!(
{DynamicSupervisor, strategy: :one_for_one, name: Parrhesia.API.Stream.Supervisor}
)
end
end
defp listener(overrides) do
base = %{
id: :test,

View File

@@ -5,6 +5,7 @@ defmodule Parrhesia.Web.RouterTest do
import Plug.Test
alias Ecto.Adapters.SQL.Sandbox
alias Parrhesia.API.Sync
alias Parrhesia.Protocol.EventValidator
alias Parrhesia.Repo
alias Parrhesia.Web.Listener
@@ -334,6 +335,84 @@ defmodule Parrhesia.Web.RouterTest do
assert byte_size(pubkey) == 64
end
test "POST /management stats and health include sync summary" do
management_url = "http://www.example.com/management"
auth_event = nip98_event("POST", management_url)
authorization = "Nostr " <> Base.encode64(JSON.encode!(auth_event))
initial_total = Sync.sync_stats() |> elem(1) |> Map.fetch!("servers_total")
server_id = "router-sync-#{System.unique_integer([:positive, :monotonic])}"
assert {:ok, _server} =
Sync.put_server(%{
"id" => server_id,
"url" => "wss://relay-a.example/relay",
"enabled?" => false,
"auth_pubkey" => String.duplicate("a", 64),
"filters" => [%{"kinds" => [5000], "#r" => ["tribes.accounts.user"]}],
"tls" => %{
"pins" => [
%{
"type" => "spki_sha256",
"value" => "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
}
]
}
})
on_exit(fn ->
_ = Sync.remove_server(server_id)
end)
stats_conn =
conn(
:post,
"/management",
JSON.encode!(%{
"method" => "stats",
"params" => %{}
})
)
|> put_req_header("content-type", "application/json")
|> put_req_header("authorization", authorization)
|> Router.call([])
assert stats_conn.status == 200
assert %{
"ok" => true,
"result" => %{
"sync" => %{"servers_total" => servers_total}
}
} = JSON.decode!(stats_conn.resp_body)
assert servers_total == initial_total + 1
health_conn =
conn(
:post,
"/management",
JSON.encode!(%{
"method" => "health",
"params" => %{}
})
)
|> put_req_header("content-type", "application/json")
|> put_req_header("authorization", authorization)
|> Router.call([])
assert health_conn.status == 200
assert %{
"ok" => true,
"result" => %{
"status" => status,
"sync" => %{"servers_total" => ^servers_total}
}
} = JSON.decode!(health_conn.resp_body)
assert status in ["ok", "degraded"]
end
test "POST /management returns not found when admin feature is disabled on the listener" do
conn =
conn(:post, "/management", JSON.encode!(%{"method" => "ping", "params" => %{}}))