Files
github__gh-stack/internal/github/github_test.go
Sameen Karim 1d9a20bd60 checkout: detect remote stack from current branch (#475)
* detect remote stack from current branch

* clean up phrasing
2026-08-27 10:56:31 -04:00

202 lines
6.1 KiB
Go

package github
import (
"bytes"
"encoding/json"
"io"
"net/http"
"strconv"
"testing"
"github.com/cli/go-gh/v2/pkg/api"
graphql "github.com/cli/shurcooL-graphql"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
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)
})
}
// TestRemoteStack_UnmarshalJSON verifies the custom decoder that flattens the
// Stacks REST API's pull_requests object array into ordered PullRequests
// numbers while preserving the full entries in PRDetails. This is the only path
// that populates those fields from the wire, so a shape regression here would
// silently break every stack endpoint.
func TestRemoteStack_UnmarshalJSON(t *testing.T) {
payload := `{
"id": 6154,
"number": 360,
"node_id": "S_kwABCD",
"url": "https://api.github.com/repos/o/r/stacks/360",
"base": {"ref": "main", "sha": "basesha"},
"open": true,
"created_at": "2026-01-01T00:00:00Z",
"pull_requests": [
{"number": 12, "state": "open", "draft": true, "merged_at": null, "head": {"ref": "feat-1", "sha": "sha1"}},
{"number": 15, "state": "closed", "draft": false, "merged_at": "2026-01-02T00:00:00Z", "head": {"ref": "feat-2", "sha": "sha2"}}
]
}`
var s RemoteStack
require.NoError(t, json.Unmarshal([]byte(payload), &s))
// Top-level metadata.
assert.Equal(t, 6154, s.ID)
assert.Equal(t, 360, s.Number)
assert.Equal(t, "S_kwABCD", s.NodeID)
assert.Equal(t, "https://api.github.com/repos/o/r/stacks/360", s.URL)
assert.Equal(t, "main", s.Base.Ref)
assert.Equal(t, "basesha", s.Base.Sha)
assert.True(t, s.Open)
assert.Equal(t, "2026-01-01T00:00:00Z", s.CreatedAt)
// Ordered PR numbers (bottom to top) derived from the object array.
assert.Equal(t, []int{12, 15}, s.PullRequests)
assert.Equal(t, []int{12, 15}, s.PRNumbers())
// Full PR entries preserved in PRDetails, including nullable merged_at.
require.Len(t, s.PRDetails, 2)
assert.Equal(t, 12, s.PRDetails[0].Number)
assert.Equal(t, "open", s.PRDetails[0].State)
assert.True(t, s.PRDetails[0].Draft)
assert.Nil(t, s.PRDetails[0].MergedAt)
assert.False(t, s.PRDetails[0].IsMerged())
assert.Equal(t, "feat-1", s.PRDetails[0].Head.Ref)
assert.Equal(t, "sha1", s.PRDetails[0].Head.Sha)
assert.Equal(t, 15, s.PRDetails[1].Number)
assert.Equal(t, "closed", s.PRDetails[1].State)
require.NotNil(t, s.PRDetails[1].MergedAt)
assert.Equal(t, "2026-01-02T00:00:00Z", *s.PRDetails[1].MergedAt)
assert.True(t, s.PRDetails[1].IsMerged())
assert.Equal(t, "feat-2", s.PRDetails[1].Head.Ref)
}
// TestRemoteStack_UnmarshalJSON_EmptyPRs ensures a stack with no pull_requests
// decodes to empty (non-nil) slices rather than panicking.
func TestRemoteStack_UnmarshalJSON_EmptyPRs(t *testing.T) {
var s RemoteStack
require.NoError(t, json.Unmarshal([]byte(`{"id": 1, "number": 2, "pull_requests": []}`), &s))
assert.Equal(t, 1, s.ID)
assert.Equal(t, 2, s.Number)
assert.Empty(t, s.PullRequests)
assert.Empty(t, s.PRDetails)
}
func TestListStacks_Paginates(t *testing.T) {
firstPage := make([]RemoteStack, 100)
for i := range firstPage {
firstPage[i] = RemoteStack{ID: i + 1, Number: 101 - i}
}
secondPage := []RemoteStack{{ID: 101, Number: 1}}
firstBody, err := json.Marshal(firstPage)
require.NoError(t, err)
secondBody, err := json.Marshal(secondPage)
require.NoError(t, err)
var requestedPages []int
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
assert.Equal(t, "100", r.URL.Query().Get("per_page"))
page, parseErr := strconv.Atoi(r.URL.Query().Get("page"))
require.NoError(t, parseErr)
requestedPages = append(requestedPages, page)
body := firstBody
if page == 2 {
body = secondBody
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(body)),
Header: http.Header{"Content-Type": []string{"application/json"}},
Request: r,
}, nil
})
rest, err := api.NewRESTClient(api.ClientOptions{
Host: "github.com",
AuthToken: "x",
Transport: transport,
})
require.NoError(t, err)
client := &Client{rest: rest, owner: "o", repo: "r"}
stacks, err := client.ListStacks()
require.NoError(t, err)
require.Len(t, stacks, 101)
assert.Equal(t, []int{1, 2}, requestedPages)
assert.Equal(t, 101, stacks[0].Number)
assert.Equal(t, 1, stacks[100].Number)
}