11package github
22
3- import "testing"
3+ import (
4+ "context"
5+ "errors"
6+ "net/http"
7+ "net/http/httptest"
8+ "net/url"
9+ "strings"
10+ "testing"
11+ "time"
12+
13+ "github.com/google/go-github/v70/github"
14+ )
415
516func TestClone (t * testing.T ) {
617 // Test Case 1: Invalid repository URL
@@ -16,3 +27,187 @@ func TestClone(t *testing.T) {
1627 t .Errorf ("Expected error for invalid repository URL" )
1728 }
1829}
30+
31+ // newTestClient returns a *github.Client whose requests are directed at the
32+ // given handler instead of the real GitHub API.
33+ func newTestClient (t * testing.T , handler http.Handler ) * github.Client {
34+ t .Helper ()
35+ server := httptest .NewServer (handler )
36+ t .Cleanup (server .Close )
37+
38+ client := github .NewClient (nil )
39+ baseURL , err := url .Parse (server .URL + "/" )
40+ if err != nil {
41+ t .Fatalf ("failed to parse test server URL: %v" , err )
42+ }
43+ client .BaseURL = baseURL
44+ return client
45+ }
46+
47+ // fastChecksPolling shrinks the polling interval/timeout for the duration of a
48+ // test so wait loops complete in milliseconds, restoring the originals after.
49+ func fastChecksPolling (t * testing.T , interval , timeout time.Duration ) {
50+ t .Helper ()
51+ origInterval , origTimeout := checksPollInterval , checksWaitTimeout
52+ checksPollInterval , checksWaitTimeout = interval , timeout
53+ t .Cleanup (func () {
54+ checksPollInterval , checksWaitTimeout = origInterval , origTimeout
55+ })
56+ }
57+
58+ func TestWaitForChecksToPass (t * testing.T ) {
59+ tests := []struct {
60+ name string
61+ body string
62+ status int
63+ timeout time.Duration
64+ wantErr bool
65+ wantTimeout bool // error must wrap context.DeadlineExceeded
66+ errContains string
67+ }{
68+ {
69+ name : "all checks succeed" ,
70+ body : `{"total_count":2,"check_runs":[{"name":"build","status":"completed","conclusion":"success"},{"name":"lint","status":"completed","conclusion":"success"}]}` ,
71+ status : http .StatusOK ,
72+ },
73+ {
74+ name : "skipped and neutral count as passed" ,
75+ body : `{"total_count":2,"check_runs":[{"name":"opt","status":"completed","conclusion":"skipped"},{"name":"info","status":"completed","conclusion":"neutral"}]}` ,
76+ status : http .StatusOK ,
77+ },
78+ {
79+ name : "a failed check returns an error" ,
80+ body : `{"total_count":1,"check_runs":[{"name":"build","status":"completed","conclusion":"failure"}]}` ,
81+ status : http .StatusOK ,
82+ wantErr : true ,
83+ errContains : `check "build" did not pass` ,
84+ },
85+ {
86+ // A timeout can surface either from the select branch or mid-flight
87+ // in the HTTP call, depending on where the deadline lands; both wrap
88+ // context.DeadlineExceeded.
89+ name : "still-pending checks time out" ,
90+ body : `{"total_count":1,"check_runs":[{"name":"build","status":"in_progress"}]}` ,
91+ status : http .StatusOK ,
92+ timeout : 30 * time .Millisecond ,
93+ wantErr : true ,
94+ wantTimeout : true ,
95+ },
96+ {
97+ name : "no checks registered times out" ,
98+ body : `{"total_count":0,"check_runs":[]}` ,
99+ status : http .StatusOK ,
100+ timeout : 30 * time .Millisecond ,
101+ wantErr : true ,
102+ wantTimeout : true ,
103+ },
104+ {
105+ name : "API error is propagated" ,
106+ body : `{"message":"boom"}` ,
107+ status : http .StatusInternalServerError ,
108+ wantErr : true ,
109+ errContains : "listing check runs" ,
110+ },
111+ }
112+
113+ for _ , tt := range tests {
114+ t .Run (tt .name , func (t * testing.T ) {
115+ timeout := tt .timeout
116+ if timeout == 0 {
117+ timeout = time .Second
118+ }
119+ fastChecksPolling (t , time .Millisecond , timeout )
120+
121+ handler := http .NewServeMux ()
122+ handler .HandleFunc ("GET /repos/kununu/test-repo/commits/{ref}/check-runs" , func (w http.ResponseWriter , r * http.Request ) {
123+ w .WriteHeader (tt .status )
124+ w .Write ([]byte (tt .body ))
125+ })
126+
127+ client := newTestClient (t , handler )
128+ err := waitForChecksToPass (context .Background (), client , "kununu" , "test-repo" , "deadbeef" )
129+
130+ if tt .wantErr {
131+ if err == nil {
132+ t .Fatalf ("expected error, got nil" )
133+ }
134+ if tt .wantTimeout && ! errors .Is (err , context .DeadlineExceeded ) {
135+ t .Errorf ("error %q does not wrap context.DeadlineExceeded" , err .Error ())
136+ }
137+ if tt .errContains != "" && ! strings .Contains (err .Error (), tt .errContains ) {
138+ t .Errorf ("error %q does not contain %q" , err .Error (), tt .errContains )
139+ }
140+ return
141+ }
142+ if err != nil {
143+ t .Fatalf ("unexpected error: %v" , err )
144+ }
145+ })
146+ }
147+ }
148+
149+ func TestNewPullRequestSkipsChecksWhenDisabled (t * testing.T ) {
150+ var checksCalled , mergeCalled bool
151+
152+ handler := http .NewServeMux ()
153+ handler .HandleFunc ("POST /repos/kununu/test-repo/pulls" , func (w http.ResponseWriter , r * http.Request ) {
154+ w .Write ([]byte (`{"number":7,"head":{"sha":"abc123"}}` ))
155+ })
156+ handler .HandleFunc ("GET /repos/kununu/test-repo/commits/{ref}/check-runs" , func (w http.ResponseWriter , r * http.Request ) {
157+ checksCalled = true
158+ w .Write ([]byte (`{"total_count":1,"check_runs":[{"name":"build","status":"completed","conclusion":"success"}]}` ))
159+ })
160+ handler .HandleFunc ("PUT /repos/kununu/test-repo/pulls/7/merge" , func (w http.ResponseWriter , r * http.Request ) {
161+ mergeCalled = true
162+ w .Write ([]byte (`{"merged":true}` ))
163+ })
164+
165+ ghApp := & GitHubApp {
166+ Config : & GitHubAppConfig {repoName : "test-repo" , WaitForChecksToPass : false },
167+ githubClient : newTestClient (t , handler ),
168+ }
169+
170+ if err := ghApp .NewPullRequest ("feature" , "main" , "title" , "body" ); err != nil {
171+ t .Fatalf ("unexpected error: %v" , err )
172+ }
173+ if checksCalled {
174+ t .Errorf ("check runs were polled even though WaitForChecksToPass is false" )
175+ }
176+ if ! mergeCalled {
177+ t .Errorf ("PR was not merged" )
178+ }
179+ }
180+
181+ func TestNewPullRequestWaitsForChecksWhenEnabled (t * testing.T ) {
182+ var checksCalled , mergeCalled bool
183+
184+ handler := http .NewServeMux ()
185+ handler .HandleFunc ("POST /repos/kununu/test-repo/pulls" , func (w http.ResponseWriter , r * http.Request ) {
186+ w .Write ([]byte (`{"number":7,"head":{"sha":"abc123"}}` ))
187+ })
188+ handler .HandleFunc ("GET /repos/kununu/test-repo/commits/{ref}/check-runs" , func (w http.ResponseWriter , r * http.Request ) {
189+ checksCalled = true
190+ w .Write ([]byte (`{"total_count":1,"check_runs":[{"name":"build","status":"completed","conclusion":"success"}]}` ))
191+ })
192+ handler .HandleFunc ("PUT /repos/kununu/test-repo/pulls/7/merge" , func (w http.ResponseWriter , r * http.Request ) {
193+ mergeCalled = true
194+ w .Write ([]byte (`{"merged":true}` ))
195+ })
196+
197+ fastChecksPolling (t , time .Millisecond , time .Second )
198+
199+ ghApp := & GitHubApp {
200+ Config : & GitHubAppConfig {repoName : "test-repo" , WaitForChecksToPass : true },
201+ githubClient : newTestClient (t , handler ),
202+ }
203+
204+ if err := ghApp .NewPullRequest ("feature" , "main" , "title" , "body" ); err != nil {
205+ t .Fatalf ("unexpected error: %v" , err )
206+ }
207+ if ! checksCalled {
208+ t .Errorf ("check runs were not polled even though WaitForChecksToPass is true" )
209+ }
210+ if ! mergeCalled {
211+ t .Errorf ("PR was not merged" )
212+ }
213+ }
0 commit comments