defmodule GitGud.PullRequestsFederatedReviewTest do
@moduledoc """
End-to-end test of federated PR reviews — inbound `Like` / `Dislike`
activities materialize as `pr_reviews` rows.
"""
use GitGud.DataCase, async: false
use Oban.Testing, repo: GitGud.Repo
alias GitGud.Federation
alias GitGud.Federation.Actor
alias GitGud.Federation.InboxDelivery
alias GitGud.Federation.Workers.ProcessInbox
alias GitGud.Git
alias GitGud.PullRequests
alias GitGud.PullRequests.PrReview
alias GitGud.Repo
import GitGud.ForgeFixtures
@moduletag :git_required
setup do
if System.find_executable("git") == nil do
{:skip, "git not on PATH"}
else
{_user, target_repo} = repository_fixture()
{_other, source_repo} = repository_fixture(%{name: "remote-src"})
main_sha = commit_file(source_repo.disk_path, "README.md", "main\n", "init", nil)
:ok = Git.update_ref(source_repo.disk_path, "refs/heads/main", main_sha, nil)
feature_sha =
commit_file(source_repo.disk_path, "feature.txt", "x\n", "feat", main_sha)
:ok = Git.update_ref(source_repo.disk_path, "refs/heads/feature", feature_sha, nil)
{_, 0} =
System.cmd("git",
["-C", target_repo.disk_path, "fetch", "--no-tags", source_repo.disk_path,
"+refs/heads/main:refs/heads/main"],
stderr_to_stdout: true
)
{:ok, _} = Federation.ensure_local_actor(target_repo)
{:ok, _} = Federation.allowlist_instance("remote.example")
remote_actor = insert_remote_actor("https://remote.example/@alice")
{:ok, pr} =
PullRequests.create_federated_pull_request(target_repo, remote_actor, %{
title: "Doomed feature",
body: "",
source_ref: "feature",
target_ref: "main",
head_hex: Git.to_hex(feature_sha),
source_clone_url: source_repo.disk_path,
activity_url: "https://remote.example/activities/offer/feature"
})
{:ok,
target_repo: target_repo,
remote_actor: remote_actor,
pr: pr}
end
end
test "Like → approved review row", %{target_repo: target_repo, remote_actor: remote, pr: pr} do
target_actor = Repo.get_by!(Actor, local_repository_id: target_repo.id)
doc = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://remote.example/activities/like/1",
"type" => "Like",
"actor" => remote.actor_url,
"object" => pr.activity_url,
"content" => "ship it"
}
{:ok, delivery} = insert_delivery(doc, target_actor.id, remote.actor_url)
:ok = ProcessInbox.perform(%Oban.Job{args: %{"delivery_id" => delivery.id}})
[review] = Repo.all(PrReview)
assert review.state == "approved"
assert review.source_actor_id == remote.id
assert review.body == "ship it"
assert PrReview.federated?(review)
end
test "Dislike → changes_requested review row", %{
target_repo: target_repo,
remote_actor: remote,
pr: pr
} do
target_actor = Repo.get_by!(Actor, local_repository_id: target_repo.id)
doc = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://remote.example/activities/dislike/1",
"type" => "Dislike",
"actor" => remote.actor_url,
"object" => pr.activity_url,
"content" => "needs work"
}
{:ok, delivery} = insert_delivery(doc, target_actor.id, remote.actor_url)
:ok = ProcessInbox.perform(%Oban.Job{args: %{"delivery_id" => delivery.id}})
[review] = Repo.all(PrReview)
assert review.state == "changes_requested"
end
test "re-delivery of the same Like is idempotent on activity_url", %{
target_repo: target_repo,
remote_actor: remote,
pr: pr
} do
target_actor = Repo.get_by!(Actor, local_repository_id: target_repo.id)
doc = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://remote.example/activities/like/dup",
"type" => "Like",
"actor" => remote.actor_url,
"object" => pr.activity_url
}
{:ok, d1} = insert_delivery(doc, target_actor.id, remote.actor_url)
:ok = ProcessInbox.perform(%Oban.Job{args: %{"delivery_id" => d1.id}})
{:ok, d2} = insert_delivery(doc, target_actor.id, remote.actor_url)
:ok = ProcessInbox.perform(%Oban.Job{args: %{"delivery_id" => d2.id}})
assert length(Repo.all(PrReview)) == 1
end
# ── helpers ──────────────────────────────────────────────────────────
defp insert_remote_actor(url) do
Repo.insert!(%Actor{
kind: "remote",
actor_type: "Person",
actor_url: url,
inbox_url: url <> "/inbox",
preferred_username: "alice",
name: "alice",
public_key_pem: "stub",
public_key_id: url <> "#main-key",
remote_doc: %{},
last_fetched_at: DateTime.utc_now(:second)
})
end
defp insert_delivery(doc, target_actor_id, source_actor_url) do
%InboxDelivery{}
|> InboxDelivery.changeset(%{
target_actor_id: target_actor_id,
source_actor_url: source_actor_url,
raw_body: Jason.encode!(doc),
status: "accepted"
})
|> Repo.insert()
end
defp commit_file(path, name, contents, message, parent) do
blob_tmp = Path.join(System.tmp_dir!(), "review-blob-#{System.unique_integer([:positive])}")
File.write!(blob_tmp, contents)
{hex, 0} = System.cmd("git", ["-C", path, "hash-object", "-w", blob_tmp])
File.rm(blob_tmp)
{:ok, blob_sha} = Git.from_hex(String.trim(hex))
index = Path.join(System.tmp_dir!(), "review-index-#{System.unique_integer([:positive])}")
env = [{"GIT_INDEX_FILE", index}]
try do
if parent do
{:ok, parent_commit} = Git.commit(path, parent)
{_, 0} =
System.cmd("git", ["-C", path, "read-tree", Git.to_hex(parent_commit.tree_sha)], env: env)
end
{_, 0} =
System.cmd(
"git",
[
"-C",
path,
"update-index",
"--add",
"--cacheinfo",
"100644," <> Git.to_hex(blob_sha) <> "," <> name
],
env: env
)
{tree_hex, 0} = System.cmd("git", ["-C", path, "write-tree"], env: env)
{:ok, tree_sha} = Git.from_hex(String.trim(tree_hex))
parents = if parent, do: [parent], else: []
{:ok, commit_sha} =
Git.commit_tree(path, tree_sha, parents, message,
author: {"t", "t@example", DateTime.utc_now(:second)}
)
commit_sha
after
_ = File.rm(index)
end
end
end
6.5 KiB · text
5af55d9