You've already forked tribes-plugin-aether
forked from tribes/tribes-plugin-template
c1f4339dde
Use TribeOne.TribesPlugin.Aether modules throughout the plugin and expose chat@1 from the entry module for capability-based consumers.
245 lines
7.6 KiB
Elixir
245 lines
7.6 KiB
Elixir
defmodule TribeOne.TribesPlugin.Aether.ChatBackendTest do
|
|
use Tribes.PluginTest.PageCase, plugin: TribeOne.TribesPlugin.Aether.Plugin
|
|
|
|
alias TribeOne.TribesPlugin.Aether.Chat
|
|
alias TribeOne.TribesPlugin.Aether.Chat.Nostr.Nak
|
|
alias Parrhesia.API.Events
|
|
alias Parrhesia.API.RequestContext
|
|
|
|
test "NIP-17 DM flow sends a user-to-user message", %{
|
|
signed_in_conn: sender_conn,
|
|
current_user: sender,
|
|
conn: conn
|
|
} do
|
|
recipient = create_user!()
|
|
|
|
{:ok, view, _html} = live(sender_conn, "/aether/chat")
|
|
|
|
assert has_element?(view, "#chat-recipient-picker")
|
|
|
|
view
|
|
|> form("#chat-recipient-search-form", %{"recipient" => %{"query" => recipient.username}})
|
|
|> render_change()
|
|
|
|
view
|
|
|> element("#chat-recipient-#{recipient.id}")
|
|
|> render_click()
|
|
|
|
assert_redirect(
|
|
view,
|
|
Chat.standalone_path(Chat.direct_slug(sender.pubkey_hex, recipient.pubkey_hex))
|
|
)
|
|
|
|
{:ok, sender_view, _html} =
|
|
live(
|
|
sender_conn,
|
|
Chat.standalone_path(Chat.direct_slug(sender.pubkey_hex, recipient.pubkey_hex))
|
|
)
|
|
|
|
sender_view
|
|
|> form("#chat-message-form", %{"message" => %{"body" => "hello recipient"}})
|
|
|> render_submit()
|
|
|
|
assert has_element?(sender_view, "#chat-messages", "hello recipient")
|
|
|
|
recipient_conn = sign_in(conn, recipient.username)
|
|
|
|
{:ok, recipient_view, _html} =
|
|
live(
|
|
recipient_conn,
|
|
Chat.standalone_path(Chat.direct_slug(sender.pubkey_hex, recipient.pubkey_hex))
|
|
)
|
|
|
|
assert has_element?(recipient_view, "#chat-messages", "hello recipient")
|
|
end
|
|
|
|
test "Aether publishes real NIP-17 giftwraps that nak can unwrap" do
|
|
require_executable!("nak")
|
|
|
|
{sender_pubkey, sender_privkey} = Tribes.Keyring.generate_keypair()
|
|
recipient_privkey = nak_key_generate!()
|
|
recipient_pubkey = nak_key_public!(recipient_privkey)
|
|
|
|
{:ok, channel} =
|
|
Chat.ensure_direct_conversation(sender_pubkey, recipient_pubkey, %{
|
|
title: "NIP-17 interop"
|
|
})
|
|
|
|
assert {:ok, message} =
|
|
Chat.send_message(
|
|
channel,
|
|
%{body: "hello nak recipient", author_pubkey: sender_pubkey},
|
|
session_privkey: sender_privkey
|
|
)
|
|
|
|
assert message.body == "hello nak recipient"
|
|
|
|
[giftwrap] = giftwraps_for(recipient_pubkey)
|
|
rumor = nak_gift_unwrap!(recipient_privkey, giftwrap)
|
|
|
|
assert giftwrap["kind"] == 1059
|
|
assert ["p", recipient_pubkey] in Enum.map(giftwrap["tags"], &Enum.take(&1, 2))
|
|
assert rumor["kind"] == 14
|
|
assert rumor["pubkey"] == sender_pubkey
|
|
assert rumor["content"] == "hello nak recipient"
|
|
assert ["p", recipient_pubkey] in Enum.map(rumor["tags"], &Enum.take(&1, 2))
|
|
end
|
|
|
|
test "Aether reads external NIP-17 giftwraps produced by nak" do
|
|
require_executable!("nak")
|
|
|
|
external_sender_privkey = nak_key_generate!()
|
|
external_sender_pubkey = nak_key_public!(external_sender_privkey)
|
|
{recipient_pubkey, recipient_privkey} = Tribes.Keyring.generate_keypair()
|
|
|
|
{:ok, channel} =
|
|
Chat.ensure_direct_conversation(recipient_pubkey, external_sender_pubkey, %{
|
|
title: "External NIP-17"
|
|
})
|
|
|
|
giftwrap =
|
|
nak_nip17_giftwrap!(external_sender_privkey, recipient_pubkey, "hello from external nak")
|
|
|
|
:ok = publish_event!(giftwrap)
|
|
|
|
assert {:ok, [message]} =
|
|
Chat.list_conversation_messages(channel, session_privkey: recipient_privkey)
|
|
|
|
assert message.author_pubkey == external_sender_pubkey
|
|
assert message.body == "hello from external nak"
|
|
end
|
|
|
|
test "NIP-04 backend imports decryptable legacy DMs as read-only" do
|
|
require_executable!("nak")
|
|
|
|
external_sender_privkey = nak_key_generate!()
|
|
external_sender_pubkey = nak_key_public!(external_sender_privkey)
|
|
{recipient_pubkey, recipient_privkey} = Tribes.Keyring.generate_keypair()
|
|
|
|
{:ok, channel} =
|
|
Chat.ensure_direct_conversation(recipient_pubkey, external_sender_pubkey, %{
|
|
title: "Legacy NIP-04",
|
|
backend: :nostr_nip04,
|
|
conversation_kind: :legacy_dm
|
|
})
|
|
|
|
{:ok, ciphertext} =
|
|
Nak.nip04_encrypt(external_sender_privkey, recipient_pubkey, "legacy hello from nak")
|
|
|
|
legacy_event = nak_event!(external_sender_privkey, 4, recipient_pubkey, ciphertext)
|
|
:ok = publish_event!(legacy_event)
|
|
|
|
assert {:ok, [message]} =
|
|
Chat.list_conversation_messages(channel, session_privkey: recipient_privkey)
|
|
|
|
assert message.author_pubkey == external_sender_pubkey
|
|
assert message.body == "legacy hello from nak"
|
|
|
|
assert {:error, :read_only_backend} =
|
|
Chat.send_message(channel, %{body: "nope"}, session_privkey: recipient_privkey)
|
|
end
|
|
|
|
test "backend capabilities prepare Nostr-compatible non-custodial backends" do
|
|
assert %{canonical_store: :parrhesia_events, non_custodial_signing?: true} =
|
|
Chat.backend_capabilities(:nostr_nip17)
|
|
|
|
assert %{read_only?: true, required_protocols: [:nip04]} =
|
|
Chat.backend_capabilities(:nostr_nip04)
|
|
end
|
|
|
|
defp create_user! do
|
|
username = "chat_recipient_#{System.unique_integer([:positive])}"
|
|
|
|
{:ok, user} =
|
|
Ash.create(
|
|
Tribes.Accounts.User,
|
|
%{username: username, password: "password_123", password_confirmation: "password_123"},
|
|
action: :register_with_password,
|
|
domain: Tribes.Accounts
|
|
)
|
|
|
|
Tribes.Plugin.User.from_host(user)
|
|
end
|
|
|
|
defp publish_event!(event) do
|
|
assert {:ok, result} =
|
|
Events.publish(event,
|
|
context: %RequestContext{
|
|
caller: :local,
|
|
authenticated_pubkeys: MapSet.new([event["pubkey"]])
|
|
}
|
|
)
|
|
|
|
assert result.accepted
|
|
:ok
|
|
end
|
|
|
|
defp giftwraps_for(recipient_pubkey) do
|
|
assert {:ok, events} =
|
|
Events.query(
|
|
[%{"kinds" => [1059], "#p" => [recipient_pubkey], "limit" => 10}],
|
|
context: %RequestContext{
|
|
caller: :local,
|
|
authenticated_pubkeys: MapSet.new([recipient_pubkey])
|
|
}
|
|
)
|
|
|
|
events
|
|
end
|
|
|
|
defp nak_nip17_giftwrap!(sender_privkey, recipient_pubkey, body) do
|
|
script = ~S'''
|
|
set -euo pipefail
|
|
nak event --sec "$1" -k 14 -p "$2" -c "$3" </dev/null 2>/dev/null |
|
|
nak gift wrap --sec "$1" -p "$2" 2>/dev/null
|
|
'''
|
|
|
|
script |> run_script!([sender_privkey, recipient_pubkey, body]) |> JSON.decode!()
|
|
end
|
|
|
|
defp nak_event!(sender_privkey, kind, recipient_pubkey, content) do
|
|
script = ~S'''
|
|
set -euo pipefail
|
|
nak event --sec "$1" -k "$2" -p "$3" -c "$4" </dev/null 2>/dev/null
|
|
'''
|
|
|
|
script
|
|
|> run_script!([sender_privkey, to_string(kind), recipient_pubkey, content])
|
|
|> JSON.decode!()
|
|
end
|
|
|
|
defp nak_gift_unwrap!(recipient_privkey, giftwrap) do
|
|
script = ~S'''
|
|
set -euo pipefail
|
|
printf '%s\n' "$2" | nak gift unwrap --sec "$1" 2>/dev/null
|
|
'''
|
|
|
|
script
|
|
|> run_script!([recipient_privkey, JSON.encode!(giftwrap)])
|
|
|> JSON.decode!()
|
|
end
|
|
|
|
defp nak_key_generate!, do: run_script!("nak key generate", [])
|
|
|
|
defp nak_key_public!(privkey), do: run_script!("nak key public \"$1\"", [privkey])
|
|
|
|
defp require_executable!(name) do
|
|
unless System.find_executable(name) do
|
|
flunk("expected #{name} in PATH")
|
|
end
|
|
end
|
|
|
|
defp run_script!(script, args) do
|
|
task =
|
|
Task.async(fn ->
|
|
System.cmd("bash", ["-lc", script, "bash" | args], stderr_to_stdout: true)
|
|
end)
|
|
|
|
case Task.yield(task, 30_000) || Task.shutdown(task, :brutal_kill) do
|
|
{:ok, {output, 0}} -> String.trim(output)
|
|
{:ok, {output, status}} -> flunk("script failed with status #{status}:\n#{output}")
|
|
nil -> flunk("script timed out")
|
|
end
|
|
end
|
|
end
|