defmodule GitGud.Federation.PublishingTest do
use GitGud.DataCase, async: false
use Oban.Testing, repo: GitGud.Repo
alias GitGud.Federation
alias GitGud.Federation.Activity
alias GitGud.Federation.Actor
alias GitGud.Federation.Follow
alias GitGud.Federation.Publishing
alias GitGud.Federation.Workers.DeliverOutbound
import GitGud.ForgeFixtures
@moduletag :git_required
setup do
if System.find_executable("git") == nil do
{:skip, "git not on PATH"}
else
:ok
end
end
test "publish/2 records the activity and enqueues delivery to every accepted follower" do
{_user, repo} = repository_fixture()
{:ok, repo_actor} = Federation.ensure_local_actor(repo)
# Plant two remote followers that look like Mastodon-style actors
# with sharedInbox. We expect ONE delivery to the shared inbox, not
# two.
follower_a = remote_follower("https://m.example/users/a", "https://m.example/inbox")
follower_b = remote_follower("https://m.example/users/b", "https://m.example/inbox")
follower_c = remote_follower("https://other.example/users/c", nil)
Enum.each([follower_a, follower_b, follower_c], fn f ->
%Follow{}
|> Follow.changeset(%{
follower_actor_id: f.id,
target_actor_id: repo_actor.id,
accepted_at: DateTime.utc_now(:second)
})
|> GitGud.Repo.insert!()
end)
activity_doc = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => repo_actor.actor_url <> "/activities/test/1",
"type" => "Push",
"actor" => repo_actor.actor_url,
"object" => %{"type" => "OrderedCollection", "totalItems" => 0, "orderedItems" => []}
}
assert {:ok, %Activity{} = activity, inbox_urls} =
Publishing.publish(repo_actor, activity_doc)
assert activity.is_local
assert activity.actor_id == repo_actor.id
# Deduplicated to two unique inbox URLs.
assert Enum.sort(inbox_urls) ==
Enum.sort(["https://m.example/inbox", follower_c.inbox_url])
# Two jobs, one per unique inbox.
assert [_, _] = all_enqueued(worker: DeliverOutbound)
end
test "publish_issue_opened produces a Create Ticket and enqueues delivery" do
{user, repo} = repository_fixture()
{:ok, _repo_actor} = Federation.ensure_local_actor(repo)
follower =
remote_follower("https://elsewhere.example/users/f", "https://elsewhere.example/inbox")
{:ok, repo_actor} = Federation.ensure_local_actor(repo)
{:ok, _author_actor} = Federation.ensure_local_actor(user)
%Follow{}
|> Follow.changeset(%{
follower_actor_id: follower.id,
target_actor_id: repo_actor.id,
accepted_at: DateTime.utc_now(:second)
})
|> GitGud.Repo.insert!()
issue =
issue_fixture(repo, user, %{
"title" => "federate me",
"body" => "with details"
})
# `create_issue` already publishes via the context; this call fires
# again to exercise Publishing directly.
{:ok, activity, [inbox]} = Publishing.publish_issue_opened(repo, issue, user)
assert activity.type == "Create"
# ForgeFed convention: Create-Ticket is owned by the repo actor so
# that the repo's followers receive it; the human is on attributedTo.
assert activity.actor_id == repo_actor.id
assert activity.raw["attributedTo"] =~ "@"
assert inbox == "https://elsewhere.example/inbox"
raw = activity.raw
assert raw["object"]["type"] == "Ticket"
assert raw["object"]["name"] =~ "##{issue.number}"
assert raw["context"] == repo_actor.actor_url
end
test "create_issue (context-level) fires a Create activity" do
{user, repo} = repository_fixture()
# A follower must exist for delivery jobs to be enqueued, but the
# Activity row is recorded unconditionally.
{:ok, repo_actor} = Federation.ensure_local_actor(repo)
Federation.ensure_local_actor(user)
{:ok, issue} = GitGud.Issues.create_issue(repo, user, %{"title" => "ff"})
assert [%Activity{type: "Create"}] =
GitGud.Repo.all(Activity)
|> Enum.filter(&(&1.is_local and &1.type == "Create"))
|> Enum.filter(fn a -> a.raw["object"]["name"] =~ "##{issue.number}" end)
_ = repo_actor
end
defp remote_follower(actor_url, shared_inbox) do
{pub, _priv} = Federation.generate_keypair()
{:ok, actor} =
%Actor{kind: "remote"}
|> Actor.remote_changeset(%{
actor_type: "Person",
actor_url: actor_url,
preferred_username: actor_url |> URI.parse() |> Map.get(:path) |> Path.basename(),
inbox_url: actor_url <> "/inbox",
shared_inbox_url: shared_inbox,
public_key_pem: pub,
public_key_id: actor_url <> "#main-key",
last_fetched_at: DateTime.utc_now(:second)
})
|> GitGud.Repo.insert()
actor
end
end
4.8 KiB · text
5af55d9