Add more QoL and high prio features
This commit is contained in:
190
github_test.go
190
github_test.go
@@ -73,6 +73,40 @@ func TestListPullRequestsRejectsGlobalShowAll(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPullRequestsPaginatesToConfiguredLimit(t *testing.T) {
|
||||
requests := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
var request graphQLRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cursor := request.Variables["after"]
|
||||
if requests == 1 {
|
||||
if cursor != nil || request.Variables["first"] != float64(100) {
|
||||
t.Fatalf("first page variables = %#v", request.Variables)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"data":{"viewer":{"login":"me"},"search":{
|
||||
"pageInfo":{"hasNextPage":true,"endCursor":"next"},"nodes":[]}}}`))
|
||||
return
|
||||
}
|
||||
if cursor != "next" || request.Variables["first"] != float64(50) {
|
||||
t.Fatalf("second page variables = %#v", request.Variables)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"data":{"viewer":{"login":"me"},"search":{
|
||||
"pageInfo":{"hasNextPage":false},"nodes":[]}}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewGitHubClient(server.URL, "secret")
|
||||
if _, err := client.ListPullRequests(context.Background(), "", "", 150, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if requests != 2 {
|
||||
t.Fatalf("requests = %d, want 2", requests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraphQLErrorsAreReturned(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(`{"errors":[{"message":"no access"}]}`))
|
||||
@@ -85,26 +119,165 @@ func TestGraphQLErrorsAreReturned(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckContextsAndAnnotationsArePaginated(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var request graphQLRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
switch {
|
||||
case strings.Contains(request.Query, "query CheckContextsPage"):
|
||||
_, _ = w.Write([]byte(`{"data":{"node":{"contexts":{
|
||||
"pageInfo":{"hasNextPage":false},"nodes":[{
|
||||
"id":"check-2","name":"lint","conclusion":"FAILURE"
|
||||
}]}}}}`))
|
||||
case strings.Contains(request.Query, "query CheckAnnotationsPage"):
|
||||
if request.Variables["after"] == nil {
|
||||
_, _ = w.Write([]byte(`{"data":{"node":{"annotations":{
|
||||
"pageInfo":{"hasNextPage":true,"endCursor":"annotation-next"},
|
||||
"nodes":[{"path":"a.go","location":{"start":{"line":4},"end":{"line":4}},
|
||||
"annotationLevel":"FAILURE","message":"first"}]
|
||||
}}}}`))
|
||||
} else {
|
||||
_, _ = w.Write([]byte(`{"data":{"node":{"annotations":{
|
||||
"pageInfo":{"hasNextPage":false},
|
||||
"nodes":[{"path":"b.go","location":{"start":{"line":8},"end":{"line":8}},
|
||||
"annotationLevel":"WARNING","message":"second"}]
|
||||
}}}}`))
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unexpected query: %s", request.Query)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewGitHubClient(server.URL, "secret")
|
||||
nodes, err := client.allCheckContexts(context.Background(), githubCheckContextConnection{
|
||||
PageInfo: githubPageInfo{HasNextPage: true, EndCursor: "context-next"},
|
||||
Nodes: []githubCheckContext{{ID: "check-1", Name: "tests", Conclusion: "SUCCESS"}},
|
||||
}, "rollup")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(nodes) != 2 || len(nodes[1].Annotations.Nodes) != 2 ||
|
||||
nodes[1].Annotations.Nodes[0].Location.Start.Line != 4 ||
|
||||
nodes[1].Annotations.Nodes[1].Location.End.Line != 8 {
|
||||
t.Fatalf("paginated checks = %#v", nodes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckQueriesUseCurrentGitHubSchemaShape(t *testing.T) {
|
||||
for name, query := range map[string]string{"annotations": checkAnnotationsPageQuery} {
|
||||
if strings.Contains(query, "output {") ||
|
||||
strings.Contains(query, "nodes { path startLine endLine annotationLevel") ||
|
||||
!strings.Contains(query, "location { start { line column } end { line column } }") {
|
||||
t.Fatalf("%s query uses obsolete CheckRun/CheckAnnotation fields:\n%s", name, query)
|
||||
}
|
||||
}
|
||||
for name, query := range map[string]string{
|
||||
"details": detailsQuery, "context page": checkContextsPageQuery,
|
||||
} {
|
||||
if strings.Contains(query, "annotations(first:") {
|
||||
t.Fatalf("%s query eagerly loads annotations:\n%s", name, query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPullRequestPaginatesThreadsCommentsConversationAndReviews(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var request graphQLRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
switch {
|
||||
case strings.Contains(request.Query, "query ReviewThreadsPage"):
|
||||
_, _ = w.Write([]byte(`{"data":{"repository":{"pullRequest":{"reviewThreads":{
|
||||
"pageInfo":{"hasNextPage":false,"endCursor":null},
|
||||
"nodes":[{"id":"thread-2","path":"b.go","line":20,"diffSide":"RIGHT",
|
||||
"isResolved":false,"isOutdated":false,"viewerCanResolve":true,
|
||||
"comments":{"pageInfo":{"hasNextPage":true,"endCursor":"comment-cursor"},
|
||||
"nodes":[{"id":"review-comment-2a","body":"first","createdAt":"2026-01-02T00:00:00Z"}]}}]
|
||||
}}}}}`))
|
||||
case strings.Contains(request.Query, "query ReviewCommentsPage"):
|
||||
_, _ = w.Write([]byte(`{"data":{"node":{"comments":{
|
||||
"pageInfo":{"hasNextPage":false,"endCursor":null},
|
||||
"nodes":[{"id":"review-comment-2b","body":"second","createdAt":"2026-01-03T00:00:00Z"}]
|
||||
}}}}`))
|
||||
case strings.Contains(request.Query, "query ConversationPage"):
|
||||
_, _ = w.Write([]byte(`{"data":{"repository":{"pullRequest":{"comments":{
|
||||
"totalCount":2,"pageInfo":{"hasNextPage":false,"endCursor":null},
|
||||
"nodes":[{"id":"conversation-2","body":"reply","createdAt":"2026-01-03T00:00:00Z"}]
|
||||
}}}}}`))
|
||||
case strings.Contains(request.Query, "query ReviewsPage"):
|
||||
_, _ = w.Write([]byte(`{"data":{"repository":{"pullRequest":{"reviews":{
|
||||
"pageInfo":{"hasNextPage":false,"endCursor":null},
|
||||
"nodes":[{"id":"review-2","body":"approved","state":"APPROVED","submittedAt":"2026-01-03T00:00:00Z"}]
|
||||
}}}}}`))
|
||||
default:
|
||||
_, _ = w.Write([]byte(`{"data":{"repository":{"viewerPermission":"WRITE","pullRequest":{
|
||||
"id":"pr","number":1,"title":"PR","url":"u","createdAt":"2026-01-01T00:00:00Z",
|
||||
"updatedAt":"2026-01-01T00:00:00Z","author":{"login":"alice"},
|
||||
"assignees":{"nodes":[]},"labels":{"nodes":[]},"reviewRequests":{"nodes":[]},
|
||||
"latestReviews":{"nodes":[]},"commits":{"totalCount":1,"nodes":[]},
|
||||
"comments":{"totalCount":2,"pageInfo":{"hasNextPage":true,"endCursor":"conversation-cursor"},
|
||||
"nodes":[{"id":"conversation-1","body":"start","createdAt":"2026-01-01T00:00:00Z"}]},
|
||||
"reviews":{"pageInfo":{"hasNextPage":true,"endCursor":"review-cursor"},
|
||||
"nodes":[{"id":"review-1","body":"changes","state":"CHANGES_REQUESTED","submittedAt":"2026-01-02T00:00:00Z"}]},
|
||||
"reviewThreads":{"pageInfo":{"hasNextPage":true,"endCursor":"thread-cursor"},
|
||||
"nodes":[{"id":"thread-1","path":"a.go","line":10,"diffSide":"RIGHT",
|
||||
"isResolved":false,"isOutdated":false,
|
||||
"comments":{"pageInfo":{"hasNextPage":false},
|
||||
"nodes":[{"id":"review-comment-1","body":"fix","createdAt":"2026-01-01T00:00:00Z"}]}}]}
|
||||
}}}}`))
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewGitHubClient(server.URL, "secret")
|
||||
got, err := client.GetPullRequest(context.Background(), "o", "r", 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got.Threads) != 2 || len(got.Threads[1].Comments) != 2 ||
|
||||
len(got.Conversation) != 2 || len(got.Reviews) != 2 {
|
||||
t.Fatalf("paginated details were incomplete: %#v", got)
|
||||
}
|
||||
if !got.Permissions.CanResolveAny || got.Permissions.Repository != "WRITE" {
|
||||
t.Fatalf("permissions = %#v", got.Permissions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPullRequestUsesOriginalLineAndMetadata(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(`{"data":{"repository":{"pullRequest":{
|
||||
_, _ = w.Write([]byte(`{"data":{"repository":{"viewerPermission":"WRITE","pullRequest":{
|
||||
"id":"pr","number":9,"title":"Fix","url":"u","body":"body","isDraft":false,
|
||||
"createdAt":"2025-12-01T00:00:00Z","updatedAt":"2026-01-01T00:00:00Z",
|
||||
"mergeable":"MERGEABLE","mergeStateStatus":"CLEAN","reviewDecision":"APPROVED",
|
||||
"additions":12,"deletions":4,"changedFiles":3,
|
||||
"baseRefName":"main","headRefName":"fix","author":{"login":"zam"},
|
||||
"baseRefName":"main","headRefName":"fix","headRefOid":"abcdef0123456789",
|
||||
"viewerCanUpdate":true,"viewerCanReact":true,"viewerCanSubscribe":true,
|
||||
"viewerCanEnableAutoMerge":true,
|
||||
"baseRef":{"branchProtectionRule":{"requiresApprovingReviews":true,
|
||||
"requiredApprovingReviewCount":2,"requiresStatusChecks":true,
|
||||
"requiresConversationResolution":true,"requiresCodeOwnerReviews":true}},
|
||||
"author":{"login":"zam"},
|
||||
"assignees":{"nodes":[{"login":"sam"}]},
|
||||
"labels":{"nodes":[{"name":"bug"},{"name":"backend"}]},
|
||||
"milestone":{"title":"v2"},
|
||||
"comments":{"totalCount":5},
|
||||
"reviewRequests":{"nodes":[{"requestedReviewer":{"login":"lee"}}]},
|
||||
"latestReviews":{"nodes":[{"state":"CHANGES_REQUESTED","author":{"login":"pat"}}]},
|
||||
"commits":{"totalCount":7,"nodes":[{"commit":{"statusCheckRollup":{"state":"FAILURE"}}}]},
|
||||
"commits":{"totalCount":7,"nodes":[{"commit":{"oid":"abcdef0123456789",
|
||||
"statusCheckRollup":{"state":"FAILURE","contexts":{"nodes":[
|
||||
{"name":"tests","status":"COMPLETED","conclusion":"FAILURE","detailsUrl":"https://checks/tests"},
|
||||
{"context":"legacy","state":"SUCCESS","targetUrl":"https://checks/legacy"}
|
||||
]}}}}]},
|
||||
"reviewThreads":{"pageInfo":{"hasNextPage":false},"nodes":[{
|
||||
"id":"t","isResolved":false,"isOutdated":true,"path":"main.go",
|
||||
"viewerCanResolve":true,
|
||||
"line":null,"originalLine":42,"diffSide":"RIGHT",
|
||||
"startLine":null,"originalStartLine":40,"startDiffSide":"RIGHT",
|
||||
"comments":{"pageInfo":{"hasNextPage":true},"nodes":[{
|
||||
"comments":{"pageInfo":{"hasNextPage":false},"nodes":[{
|
||||
"id":"c","body":"change this","diffHunk":"@@ -1 +1 @@","createdAt":"2026-01-01T00:00:00Z",
|
||||
"url":"cu","author":{"login":"reviewer"},"outdated":true,
|
||||
"line":100,"startLine":99,"originalLine":42,"originalStartLine":40,
|
||||
@@ -129,8 +302,15 @@ func TestGetPullRequestUsesOriginalLineAndMetadata(t *testing.T) {
|
||||
got.CreatedAt.IsZero() {
|
||||
t.Fatalf("unexpected dashboard metadata: %#v", got)
|
||||
}
|
||||
if got.HeadOID != "abcdef0123456789" || len(got.Checks) != 2 ||
|
||||
got.Checks[0].Name != "tests" || got.Checks[1].Name != "legacy" ||
|
||||
got.Permissions.Repository != "WRITE" || !got.Permissions.CanUpdatePR ||
|
||||
!got.Permissions.CanResolveAny || got.Requirements.ApprovalsRequired != 2 ||
|
||||
!got.Requirements.RequiresConversation || !got.Requirements.RequiresCodeOwnerReview {
|
||||
t.Fatalf("unexpected read capabilities: %#v", got)
|
||||
}
|
||||
if len(got.Threads) != 1 || got.Threads[0].Line != 42 || got.Threads[0].StartLine != 40 ||
|
||||
got.Threads[0].DiffSide != "RIGHT" || !got.Threads[0].IsTruncated {
|
||||
got.Threads[0].DiffSide != "RIGHT" || got.Threads[0].IsTruncated {
|
||||
t.Fatalf("unexpected thread: %#v", got.Threads)
|
||||
}
|
||||
comment := got.Threads[0].Comments[0]
|
||||
|
||||
Reference in New Issue
Block a user