defmodule GitGud.Federation.InteractionPolicyTest do
@moduledoc """
End-to-end test of the per-repo interaction policy enforced inside
`ProcessInbox`: stages a delivery as if a remote actor sent a
`Create-Note` (comment) targeting a local repo, runs the worker, and
asserts the delivery's final status flips according to the repo's
`interaction_policy`.
"""
use GitGud.DataCase, async: false
alias GitGud.Federation
alias GitGud.Federation.Activity
alias GitGud.Federation.Actor
alias GitGud.Federation.Follow
alias GitGud.Federation.InboxDelivery
alias GitGud.Federation.Workers.ProcessInbox
alias GitGud.Repo
alias GitGud.Repositories
import GitGud.ForgeFixtures
@moduletag :git_required
setup do
if System.find_executable("git") == nil do
{:skip, "git not on PATH"}
else
{_user, repo} = repository_fixture()
{:ok, repo_actor} = Federation.ensure_local_actor(repo)
remote_actor = remote_follower("https://x.example/users/a")
{:ok, repo: repo, repo_actor: repo_actor, remote_actor: remote_actor}
end
end
test "public policy: comment is processed normally", %{
repo: repo,
repo_actor: repo_actor,
remote_actor: remote_actor
} do
{:ok, _} = Repositories.update_repository(repo, %{"interaction_policy" => "public"})
delivery = stage_create_note(repo_actor, remote_actor)
:ok = ProcessInbox.perform(%Oban.Job{args: %{"delivery_id" => delivery.id}})
refreshed = Repo.get!(InboxDelivery, delivery.id)
assert refreshed.status == "accepted"
assert refreshed.processed_at
# Activity was recorded.
assert Repo.exists?(from a in Activity, where: a.actor_id == ^remote_actor.id)
end
test "followers policy: non-follower comment is rejected", %{
repo: repo,
repo_actor: repo_actor,
remote_actor: remote_actor
} do
{:ok, _} = Repositories.update_repository(repo, %{"interaction_policy" => "followers"})
delivery = stage_create_note(repo_actor, remote_actor)
:ok = ProcessInbox.perform(%Oban.Job{args: %{"delivery_id" => delivery.id}})
refreshed = Repo.get!(InboxDelivery, delivery.id)
assert refreshed.status == "rejected"
assert refreshed.status_reason =~ "policy_followers"
end
test "followers policy: accepted follower comment is processed", %{
repo: repo,
repo_actor: repo_actor,
remote_actor: remote_actor
} do
{:ok, _} = Repositories.update_repository(repo, %{"interaction_policy" => "followers"})
%Follow{}
|> Follow.changeset(%{
follower_actor_id: remote_actor.id,
target_actor_id: repo_actor.id,
accepted_at: DateTime.utc_now(:second)
})
|> Repo.insert!()
delivery = stage_create_note(repo_actor, remote_actor)
:ok = ProcessInbox.perform(%Oban.Job{args: %{"delivery_id" => delivery.id}})
refreshed = Repo.get!(InboxDelivery, delivery.id)
assert refreshed.status == "accepted"
end
test "approved policy: unknown actor comment is quarantined", %{
repo: repo,
repo_actor: repo_actor,
remote_actor: remote_actor
} do
{:ok, _} = Repositories.update_repository(repo, %{"interaction_policy" => "approved"})
delivery = stage_create_note(repo_actor, remote_actor)
:ok = ProcessInbox.perform(%Oban.Job{args: %{"delivery_id" => delivery.id}})
refreshed = Repo.get!(InboxDelivery, delivery.id)
assert refreshed.status == "quarantined"
assert refreshed.status_reason == "policy_approved"
# No activity recorded — quarantine short-circuits before record.
refute Repo.exists?(from a in Activity, where: a.actor_id == ^remote_actor.id)
end
# ── helpers ──────────────────────────────────────────────────────────
defp remote_follower(url) do
{pub, _priv} = Federation.generate_keypair()
{:ok, actor} =
%Actor{kind: "remote"}
|> Actor.remote_changeset(%{
actor_type: "Person",
actor_url: url,
preferred_username: "a",
inbox_url: url <> "/inbox",
public_key_pem: pub,
public_key_id: url <> "#main-key",
last_fetched_at: DateTime.utc_now(:second)
})
|> Repo.insert()
actor
end
defp stage_create_note(repo_actor, remote_actor) do
body =
Jason.encode!(%{
"id" => remote_actor.actor_url <> "/activities/test-comment",
"type" => "Create",
"actor" => remote_actor.actor_url,
"object" => %{
"type" => "Note",
"content" => "hello from elsewhere",
"inReplyTo" => repo_actor.actor_url <> "/issues/1"
}
})
%InboxDelivery{}
|> InboxDelivery.changeset(%{
target_actor_id: repo_actor.id,
source_actor_url: remote_actor.actor_url,
source_host: "x.example",
raw_body: body,
signature_verified: true,
status: "accepted"
})
|> Repo.insert!()
end
end
4.9 KiB · text
5af55d9