github.com/abayer/test-infra@v0.0.5/mungegithub/github/testing/github.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package testing 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "testing" 23 "time" 24 25 "net/http" 26 "net/http/httptest" 27 "net/url" 28 29 "github.com/golang/glog" 30 "github.com/google/go-github/github" 31 ) 32 33 var ( 34 _ = glog.Errorf 35 ) 36 37 func stringPtr(val string) *string { return &val } 38 func intPtr(val int) *int { return &val } 39 func boolPtr(val bool) *bool { return &val } 40 41 func timePtr(val time.Time) *time.Time { return &val } 42 43 // Comment is a helper to create a valid-ish comment for testing 44 func Comment(id int, login string, createdAt time.Time, body string) *github.IssueComment { 45 return &github.IssueComment{ 46 ID: &id, 47 Body: &body, 48 CreatedAt: &createdAt, 49 User: &github.User{ 50 Login: &login, 51 }, 52 } 53 } 54 55 // PullRequest returns a filled out github.PullRequest 56 func PullRequest(user string, merged, mergeDetermined, mergeable bool) *github.PullRequest { 57 pr := &github.PullRequest{ 58 Title: stringPtr("My PR title"), 59 Number: intPtr(1), 60 HTMLURL: stringPtr("PR URL"), 61 Head: &github.PullRequestBranch{ 62 SHA: stringPtr("mysha"), 63 }, 64 User: &github.User{ 65 Login: stringPtr(user), 66 AvatarURL: stringPtr("MyAvatarURL"), 67 }, 68 Merged: boolPtr(merged), 69 Base: &github.PullRequestBranch{ 70 Ref: stringPtr("master"), 71 }, 72 } 73 if mergeDetermined { 74 pr.Mergeable = boolPtr(mergeable) 75 } 76 return pr 77 } 78 79 // Issue returns a filled out github.Issue 80 func Issue(user string, number int, labels []string, isPR bool) *github.Issue { 81 issue := &github.Issue{ 82 Title: stringPtr("My issue title"), 83 Number: intPtr(number), 84 HTMLURL: stringPtr("Issue URL"), 85 User: &github.User{ 86 Login: stringPtr(user), 87 AvatarURL: stringPtr("MyAvatarURL"), 88 }, 89 } 90 if isPR { 91 issue.PullRequestLinks = &github.PullRequestLinks{} 92 } 93 issue.Labels = StringsToLabels(labels) 94 return issue 95 } 96 97 // StringsToLabels converts a slice of label names to a slice of 98 // github.Label instances in non-determinastic order. 99 func StringsToLabels(labelNames []string) []github.Label { 100 // putting it in a map means ordering is non-deterministic 101 lmap := map[int]github.Label{} 102 for i, label := range labelNames { 103 l := github.Label{ 104 Name: stringPtr(label), 105 } 106 lmap[i] = l 107 } 108 labels := []github.Label{} 109 for _, l := range lmap { 110 labels = append(labels, l) 111 } 112 return labels 113 } 114 115 // MultiIssueEvents packages up events for when you have multiple issues in the 116 // test server. 117 func MultiIssueEvents(issueToEvents map[int][]LabelTime, eventName string) (out []*github.IssueEvent) { 118 for issueNum, events := range issueToEvents { 119 for _, l := range events { 120 out = append(out, &github.IssueEvent{ 121 Issue: &github.Issue{Number: intPtr(issueNum)}, 122 Event: stringPtr(eventName), 123 Label: &github.Label{ 124 Name: stringPtr(l.Label), 125 }, 126 CreatedAt: timePtr(time.Unix(l.Time, 0)), 127 Actor: &github.User{ 128 Login: stringPtr(l.User), 129 }, 130 }) 131 } 132 } 133 return out 134 } 135 136 // LabelTime is a struct which can be used to call Events() 137 // It expresses what label the event should be about and what time 138 // the event took place. 139 type LabelTime struct { 140 User string 141 Label string 142 Time int64 143 } 144 145 // Events returns a slice of github.IssueEvent where the specified labels were 146 // applied at the specified times 147 func Events(labels []LabelTime) []*github.IssueEvent { 148 // putting it in a map means ordering is non-deterministic 149 eMap := map[int]*github.IssueEvent{} 150 for i, l := range labels { 151 event := &github.IssueEvent{ 152 Event: stringPtr("labeled"), 153 Label: &github.Label{ 154 Name: stringPtr(l.Label), 155 }, 156 CreatedAt: timePtr(time.Unix(l.Time, 0)), 157 Actor: &github.User{ 158 Login: stringPtr(l.User), 159 }, 160 } 161 eMap[i] = event 162 } 163 out := []*github.IssueEvent{} 164 for _, e := range eMap { 165 out = append(out, e) 166 } 167 return out 168 } 169 170 // Commit returns a filled out github.Commit which happened at time.Unix(t, 0) 171 func Commit(sha string, t int64) *github.Commit { 172 return &github.Commit{ 173 SHA: stringPtr(sha), 174 Committer: &github.CommitAuthor{ 175 Date: timePtr(time.Unix(t, 0)), 176 }, 177 } 178 } 179 180 // IssueComment returns a filled out github.IssueComment which happened at time.Unix(t, 0). 181 func IssueComment(id int, body string, user string, createAt int64) *github.IssueComment { 182 return &github.IssueComment{ 183 ID: intPtr(id), 184 Body: stringPtr(body), 185 User: &github.User{ 186 Login: stringPtr(user), 187 }, 188 CreatedAt: timePtr(time.Unix(createAt, 0)), 189 } 190 } 191 192 // Commits returns an array of github.RepositoryCommits. The first commit 193 // will have happened at time `time`, the next commit `time + 1`, etc 194 func Commits(num int, time int64) []*github.RepositoryCommit { 195 // putting it in a map means ordering is non-deterministic 196 cMap := map[int]*github.RepositoryCommit{} 197 for i := 0; i < num; i++ { 198 sha := fmt.Sprintf("mysha%d", i) 199 t := time + int64(i) 200 commit := &github.RepositoryCommit{ 201 SHA: stringPtr(sha), 202 Commit: Commit(sha, t), 203 } 204 cMap[i] = commit 205 } 206 out := []*github.RepositoryCommit{} 207 for _, c := range cMap { 208 out = append(out, c) 209 } 210 return out 211 } 212 213 func updateStatusState(status *github.CombinedStatus) *github.CombinedStatus { 214 prioMap := map[string]int{ 215 "pending": 4, 216 "error": 3, 217 "failure": 2, 218 "success": 1, 219 "": 0, 220 } 221 222 backMap := map[int]string{ 223 4: "pending", 224 3: "error", 225 2: "failure", 226 1: "success", 227 0: "", 228 } 229 230 sint := 1 231 for _, s := range status.Statuses { 232 newSint := prioMap[*s.State] 233 if newSint > sint { 234 sint = newSint 235 } 236 } 237 status.State = stringPtr(backMap[sint]) 238 return status 239 } 240 241 func fillMap(sMap map[int]github.RepoStatus, contexts []string, state string) { 242 for _, context := range contexts { 243 s := github.RepoStatus{ 244 Context: stringPtr(context), 245 State: stringPtr(state), 246 UpdatedAt: timePtr(time.Unix(0, 0)), 247 CreatedAt: timePtr(time.Unix(0, 0)), 248 } 249 sMap[len(sMap)] = s 250 } 251 } 252 253 // Status returns a github.CombinedStatus 254 func Status(sha string, success []string, fail []string, pending []string, errored []string) *github.CombinedStatus { 255 // putting it in a map means ordering is non-deterministic 256 sMap := map[int]github.RepoStatus{} 257 258 fillMap(sMap, success, "success") 259 fillMap(sMap, fail, "failure") 260 fillMap(sMap, pending, "pending") 261 fillMap(sMap, errored, "error") 262 263 out := &github.CombinedStatus{ 264 SHA: stringPtr(sha), 265 } 266 for _, s := range sMap { 267 out.Statuses = append(out.Statuses, s) 268 } 269 return updateStatusState(out) 270 } 271 272 // ServeIssue is a helper to load additional issues into the test server 273 func ServeIssue(t *testing.T, mux *http.ServeMux, issue *github.Issue) { 274 issueNum := *issue.Number 275 path := fmt.Sprintf("/repos/o/r/issues/%d", issueNum) 276 setMux(t, mux, path, issue) 277 } 278 279 func setMux(t *testing.T, mux *http.ServeMux, path string, thing interface{}) { 280 mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { 281 var data []byte 282 var err error 283 284 switch thing := thing.(type) { 285 default: 286 t.Errorf("Unexpected object type in SetMux: %v", thing) 287 case *github.Issue: 288 data, err = json.Marshal(thing) 289 case *github.PullRequest: 290 data, err = json.Marshal(thing) 291 case []*github.IssueEvent: 292 data, err = json.Marshal(thing) 293 case []*github.RepositoryCommit: 294 data, err = json.Marshal(thing) 295 case github.RepositoryCommit: 296 data, err = json.Marshal(thing) 297 case *github.RepositoryCommit: 298 data, err = json.Marshal(thing) 299 case *github.CombinedStatus: 300 data, err = json.Marshal(thing) 301 case []*github.CommitFile: 302 data, err = json.Marshal(thing) 303 case []*github.User: 304 data, err = json.Marshal(thing) 305 } 306 if err != nil { 307 t.Errorf("%v", err) 308 } 309 if r.Method != "GET" && r.Method != "PATCH" { 310 t.Errorf("Unexpected method: expected: GET got: %s", r.Method) 311 } 312 w.WriteHeader(http.StatusOK) 313 w.Write(data) 314 }) 315 } 316 317 func splitEventsByIssueNumber(defaultNumber int, events []*github.IssueEvent) map[int][]*github.IssueEvent { 318 // The defaultNumber nonsense is to support tests that were assuming only one issue. 319 out := map[int][]*github.IssueEvent{} 320 for _, e := range events { 321 n := defaultNumber 322 if e.Issue != nil && e.Issue.Number != nil { 323 n = *e.Issue.Number 324 } 325 out[n] = append(out[n], e) 326 } 327 return out 328 } 329 330 // InitServer will return a github.Client which will talk to httptest.Server, 331 // to retrieve information from the http.ServeMux. If an issue, pr, events, or 332 // commits are supplied it will repond with those on o/r/ 333 func InitServer(t *testing.T, issue *github.Issue, pr *github.PullRequest, events []*github.IssueEvent, commits []*github.RepositoryCommit, status *github.CombinedStatus, masterCommit *github.RepositoryCommit, files []*github.CommitFile) (*github.Client, *httptest.Server, *http.ServeMux) { 334 // test server 335 mux := http.NewServeMux() 336 server := httptest.NewServer(mux) 337 338 // github client configured to use test server 339 client := github.NewClient(nil) 340 url, _ := url.Parse(server.URL) 341 client.BaseURL = url 342 client.UploadURL = url 343 344 issueNum := 1 345 if issue != nil { 346 issueNum = *issue.Number 347 } else if pr != nil { 348 issueNum = *pr.Number 349 } 350 351 sha := "mysha" 352 if pr != nil { 353 sha = *pr.Head.SHA 354 } 355 356 if issue != nil { 357 path := fmt.Sprintf("/repos/o/r/issues/%d", issueNum) 358 setMux(t, mux, path, issue) 359 } 360 if pr != nil { 361 path := fmt.Sprintf("/repos/o/r/pulls/%d", issueNum) 362 setMux(t, mux, path, pr) 363 } 364 if events != nil { 365 for issueNum, events := range splitEventsByIssueNumber(issueNum, events) { 366 path := fmt.Sprintf("/repos/o/r/issues/%d/events", issueNum) 367 setMux(t, mux, path, events) 368 } 369 } 370 if commits != nil { 371 path := fmt.Sprintf("/repos/o/r/pulls/%d/commits", issueNum) 372 setMux(t, mux, path, commits) 373 for _, c := range commits { 374 path := fmt.Sprintf("/repos/o/r/commits/%s", *c.SHA) 375 setMux(t, mux, path, c) 376 } 377 } 378 if masterCommit != nil { 379 path := "/repos/o/r/commits/master" 380 setMux(t, mux, path, masterCommit) 381 } 382 if files != nil { 383 path := fmt.Sprintf("/repos/o/r/pulls/%d/files", issueNum) 384 setMux(t, mux, path, files) 385 } 386 if status != nil { 387 path := fmt.Sprintf("/repos/o/r/commits/%s/status", sha) 388 setMux(t, mux, path, status) 389 } 390 path := "/repos/o/r/collaborators" 391 setMux(t, mux, path, []github.User{}) 392 return client, server, mux 393 }