defmodule GitGud.BranchProtections.StatusChecksTest do
@moduledoc """
Required status checks: a PR merges into a protected branch only when
every required pattern matches a status on its head, and every status
a pattern matches is `success` or `warning`.
"""
use GitGud.DataCase, async: false
import GitGud.AccountsFixtures
import GitGud.ForgeFixtures
alias GitGud.BranchProtections
alias GitGud.PullRequests
alias GitGud.Repositories
alias GitGud.Workflows.CommitStatus
@moduletag :git_required
setup do
{owner, repo} = repository_fixture()
pr = pr_with_branches(repo, owner)
{:ok, main_sha} = Repositories.resolve(repo, "main")
{:ok, owner: owner, repo: repo, pr: pr, main_sha: main_sha}
end
defp require_checks!(repo, checks, extra \\ %{}) do
{:ok, rule} =
BranchProtections.create_rule(
repo,
Map.merge(
%{"pattern" => "main", "require_status_checks" => true, "required_checks" => checks},
extra
)
)
rule
end
defp status!(repo, sha, context, state) do
Repo.insert!(%CommitStatus{repository_id: repo.id, sha: sha, context: context, state: state})
end
defp merge_check(repo, pr, main_sha, opts \\ []) do
BranchProtections.check_ref_update(
repo,
"main",
main_sha,
pr.head_sha,
[pull_request_id: pr.id, head_sha: pr.head_sha] ++ opts
)
end
test "a passing matching status lets the merge through", ctx do
require_checks!(ctx.repo, ["ci / test *"])
status!(ctx.repo, ctx.pr.head_sha, "ci / test (push)", "success")
assert :ok = merge_check(ctx.repo, ctx.pr, ctx.main_sha)
end
test "a warning passes, as success does", ctx do
require_checks!(ctx.repo, ["ci / *"])
status!(ctx.repo, ctx.pr.head_sha, "ci / test (push)", "success")
status!(ctx.repo, ctx.pr.head_sha, "ci / audit (push)", "warning")
assert :ok = merge_check(ctx.repo, ctx.pr, ctx.main_sha)
end
test "a failing matching status blocks, by context", ctx do
require_checks!(ctx.repo, ["ci / test *"])
status!(ctx.repo, ctx.pr.head_sha, "ci / test (push)", "failure")
status!(ctx.repo, ctx.pr.head_sha, "ci / lint (push)", "failure")
assert {:error, {:status_checks, [], ["ci / test (push)"]}} =
merge_check(ctx.repo, ctx.pr, ctx.main_sha)
end
test "every matching status must pass, pending included", ctx do
require_checks!(ctx.repo, ["ci / test *"])
status!(ctx.repo, ctx.pr.head_sha, "ci / test (push)", "success")
status!(ctx.repo, ctx.pr.head_sha, "ci / test (pull_request)", "pending")
assert {:error, {:status_checks, [], ["ci / test (pull_request)"]}} =
merge_check(ctx.repo, ctx.pr, ctx.main_sha)
end
test "a pattern with no status is missing", ctx do
require_checks!(ctx.repo, ["ci / test *"])
status!(ctx.repo, ctx.pr.head_sha, "ci / lint (push)", "success")
assert {:error, {:status_checks, ["ci / test *"], []}} =
merge_check(ctx.repo, ctx.pr, ctx.main_sha)
end
test "`*` crosses slashes and spaces, the rest is literal", ctx do
require_checks!(ctx.repo, ["*/ build (arm64, 28) (push)"])
status!(ctx.repo, ctx.pr.head_sha, "release / ci / build (arm64, 28) (push)", "success")
assert :ok = merge_check(ctx.repo, ctx.pr, ctx.main_sha)
end
test "an empty list requires every status, and at least one", ctx do
require_checks!(ctx.repo, [])
assert {:error, {:status_checks, ["*"], []}} = merge_check(ctx.repo, ctx.pr, ctx.main_sha)
status!(ctx.repo, ctx.pr.head_sha, "ci / test (push)", "success")
assert :ok = merge_check(ctx.repo, ctx.pr, ctx.main_sha)
status!(ctx.repo, ctx.pr.head_sha, "deploy / deploy (push)", "cancelled")
assert {:error, {:status_checks, [], ["deploy / deploy (push)"]}} =
merge_check(ctx.repo, ctx.pr, ctx.main_sha)
end
test "a rule that doesn't require checks ignores them", ctx do
{:ok, _} = BranchProtections.create_rule(ctx.repo, %{"pattern" => "main"})
status!(ctx.repo, ctx.pr.head_sha, "ci / test (push)", "failure")
assert :ok = merge_check(ctx.repo, ctx.pr, ctx.main_sha)
end
test "approvals and checks are both enforced", ctx do
require_checks!(ctx.repo, ["ci / test *"], %{"required_approvals" => 1})
status!(ctx.repo, ctx.pr.head_sha, "ci / test (push)", "failure")
assert {:error, {:approvals_required, 1, 0}} = merge_check(ctx.repo, ctx.pr, ctx.main_sha)
{:ok, _} =
PullRequests.add_review(ctx.pr, grant_repo!(ctx.repo, user_fixture()), %{
"state" => "approved"
})
assert {:error, {:status_checks, [], ["ci / test (push)"]}} =
merge_check(ctx.repo, ctx.pr, ctx.main_sha)
end
test "a direct push isn't blocked by checks", ctx do
require_checks!(ctx.repo, ["ci / test *"])
assert :ok =
BranchProtections.check_ref_update(ctx.repo, "main", ctx.main_sha, ctx.pr.head_sha)
end
test "merge! refuses on a failing check, and an override merges anyway", ctx do
require_checks!(ctx.repo, ["ci / test *"])
status!(ctx.repo, ctx.pr.head_sha, "ci / test (push)", "failure")
assert PullRequests.status_check_blockers(ctx.repo, ctx.pr) == [
{:failing_check, "ci / test (push)"}
]
assert {:error, {:status_checks, [], ["ci / test (push)"]}} =
PullRequests.merge!(ctx.pr, ctx.owner)
assert {:ok, %{state: "merged"}} = PullRequests.merge!(ctx.pr, ctx.owner, override: true)
end
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
5.4 KiB · text
History
b868907