Files
Sameen Karim d2a390f2ff link: block merged, closed, queued, and auto-merge-enabled PRs (#112)
The `link` command previously allowed PRs in any state to be added to a
stack, including PRs that had already been merged, were closed, were
sitting in a merge queue, or had auto-merge enabled. Adding such PRs to
a stack is invalid because they have already been or will soon be merged,
which breaks the stacked PR workflow.

Add a new validation phase (Phase 2b) to `runLink` that checks the
eligibility of every existing PR found during lookup, before any new PRs
are created or stack operations are performed. Only open/draft PRs
without auto-merge enabled are eligible. All ineligible PRs are reported
at once with a clear per-PR error message indicating the specific reason
(merged, closed, in merge queue, or auto-merge enabled).

Changes:

  internal/github/github.go:
  - Add AutoMergeRequest struct and field on PullRequest
  - Add IsAutoMergeEnabled() method on *PullRequest
  - Update FindPRByNumber and FindPRForBranch GraphQL queries to fetch
    the autoMergeRequest field

  internal/github/github_test.go:
  - Add TestPullRequest_IsAutoMergeEnabled (nil, non-nil, nil receiver)

  cmd/link.go:
  - Add pr field to resolvedArg to retain full PR data from lookup
  - Add validatePREligibility() that rejects merged/closed/queued/
    auto-merge-enabled PRs with descriptive error messages
  - Wire validation into runLink between PR lookup and stack operations

  cmd/link_test.go:
  - Add 7 tests covering each disallowed state by PR number and branch
    name, plus a multi-invalid-PR reporting test
2026-06-01 15:54:30 -07:00

83 lines
2.1 KiB
Go

package github
import (
"testing"
graphql "github.com/cli/shurcooL-graphql"
"github.com/stretchr/testify/assert"
)
func TestPRURL(t *testing.T) {
tests := []struct {
name string
host string
owner string
repo string
number int
want string
}{
{"github.com", "github.com", "owner", "repo", 42, "https://github.com/owner/repo/pull/42"},
{"GHES host", "ghes.example.com", "myorg", "myrepo", 99, "https://ghes.example.com/myorg/myrepo/pull/99"},
{"empty host defaults to github.com", "", "owner", "repo", 1, "https://github.com/owner/repo/pull/1"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PRURL(tt.host, tt.owner, tt.repo, tt.number)
assert.Equal(t, tt.want, got)
})
}
}
func TestPullRequest_IsQueued(t *testing.T) {
t.Run("not queued when MergeQueueEntry is nil", func(t *testing.T) {
pr := &PullRequest{Number: 1}
assert.False(t, pr.IsQueued())
})
t.Run("queued when MergeQueueEntry has ID", func(t *testing.T) {
pr := &PullRequest{
Number: 1,
MergeQueueEntry: &MergeQueueEntry{ID: "MQE_123"},
}
assert.True(t, pr.IsQueued())
})
t.Run("nil receiver is safe", func(t *testing.T) {
var pr *PullRequest
assert.False(t, pr.IsQueued())
})
}
func TestPullRequest_IsAutoMergeEnabled(t *testing.T) {
t.Run("not enabled when AutoMergeRequest is nil", func(t *testing.T) {
pr := &PullRequest{Number: 1}
assert.False(t, pr.IsAutoMergeEnabled())
})
t.Run("enabled when AutoMergeRequest is present", func(t *testing.T) {
pr := &PullRequest{
Number: 1,
AutoMergeRequest: &AutoMergeRequest{EnabledAt: "2024-01-01T00:00:00Z"},
}
assert.True(t, pr.IsAutoMergeEnabled())
})
t.Run("nil receiver is safe", func(t *testing.T) {
var pr *PullRequest
assert.False(t, pr.IsAutoMergeEnabled())
})
}
func TestToGraphQLInt(t *testing.T) {
t.Run("in range", func(t *testing.T) {
got, err := toGraphQLInt(123)
assert.NoError(t, err)
assert.Equal(t, graphql.Int(123), got)
})
t.Run("out of range", func(t *testing.T) {
_, err := toGraphQLInt(1 << 40)
assert.Error(t, err)
})
}