github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/github/fakegithub/fakegithub.go (about) 1 /* 2 Copyright 2016 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 fakegithub 18 19 import ( 20 "fmt" 21 "regexp" 22 23 "k8s.io/apimachinery/pkg/util/sets" 24 "k8s.io/test-infra/prow/github" 25 ) 26 27 const botName = "k8s-ci-robot" 28 29 // Bot is the exported botName 30 const Bot = botName 31 32 // FakeClient is like client, but fake. 33 type FakeClient struct { 34 Issues []github.Issue 35 OrgMembers map[string][]string 36 Collaborators []string 37 IssueComments map[int][]github.IssueComment 38 IssueCommentID int 39 PullRequests map[int]*github.PullRequest 40 PullRequestChanges map[int][]github.PullRequestChange 41 PullRequestComments map[int][]github.ReviewComment 42 ReviewID int 43 Reviews map[int][]github.Review 44 CombinedStatuses map[string]*github.CombinedStatus 45 CreatedStatuses map[string][]github.Status 46 IssueEvents map[int][]github.ListedIssueEvent 47 Commits map[string]github.SingleCommit 48 49 //All Labels That Exist In The Repo 50 RepoLabelsExisting []string 51 // org/repo#number:label 52 IssueLabelsAdded []string 53 IssueLabelsExisting []string 54 IssueLabelsRemoved []string 55 56 // org/repo#number:body 57 IssueCommentsAdded []string 58 // org/repo#issuecommentid 59 IssueCommentsDeleted []string 60 61 // org/repo#issuecommentid:reaction 62 IssueReactionsAdded []string 63 CommentReactionsAdded []string 64 65 // org/repo#number:assignee 66 AssigneesAdded []string 67 68 // org/repo#number:milestone (represents the milestone for a specific issue) 69 Milestone int 70 MilestoneMap map[string]int 71 72 // list of commits for each PR 73 // org/repo#number:[]commit 74 CommitMap map[string][]github.RepositoryCommit 75 76 // Fake remote git storage. File name are keys 77 // and values map SHA to content 78 RemoteFiles map[string]map[string]string 79 80 // A list of refs that got deleted via DeleteRef 81 RefsDeleted []struct{ Org, Repo, Ref string } 82 } 83 84 // BotName returns authenticated login. 85 func (f *FakeClient) BotName() (string, error) { 86 return botName, nil 87 } 88 89 // IsMember returns true if user is in org. 90 func (f *FakeClient) IsMember(org, user string) (bool, error) { 91 for _, m := range f.OrgMembers[org] { 92 if m == user { 93 return true, nil 94 } 95 } 96 return false, nil 97 } 98 99 // ListIssueComments returns comments. 100 func (f *FakeClient) ListIssueComments(owner, repo string, number int) ([]github.IssueComment, error) { 101 return append([]github.IssueComment{}, f.IssueComments[number]...), nil 102 } 103 104 // ListPullRequestComments returns review comments. 105 func (f *FakeClient) ListPullRequestComments(owner, repo string, number int) ([]github.ReviewComment, error) { 106 return append([]github.ReviewComment{}, f.PullRequestComments[number]...), nil 107 } 108 109 // ListReviews returns reviews. 110 func (f *FakeClient) ListReviews(owner, repo string, number int) ([]github.Review, error) { 111 return append([]github.Review{}, f.Reviews[number]...), nil 112 } 113 114 // ListIssueEvents returns issue events 115 func (f *FakeClient) ListIssueEvents(owner, repo string, number int) ([]github.ListedIssueEvent, error) { 116 return append([]github.ListedIssueEvent{}, f.IssueEvents[number]...), nil 117 } 118 119 // CreateComment adds a comment to a PR 120 func (f *FakeClient) CreateComment(owner, repo string, number int, comment string) error { 121 f.IssueCommentsAdded = append(f.IssueCommentsAdded, fmt.Sprintf("%s/%s#%d:%s", owner, repo, number, comment)) 122 f.IssueComments[number] = append(f.IssueComments[number], github.IssueComment{ 123 ID: f.IssueCommentID, 124 Body: comment, 125 User: github.User{Login: botName}, 126 }) 127 f.IssueCommentID++ 128 return nil 129 } 130 131 // CreateReview adds a review to a PR 132 func (f *FakeClient) CreateReview(org, repo string, number int, r github.DraftReview) error { 133 f.Reviews[number] = append(f.Reviews[number], github.Review{ 134 ID: f.ReviewID, 135 User: github.User{Login: botName}, 136 Body: r.Body, 137 }) 138 f.ReviewID++ 139 return nil 140 } 141 142 // CreateCommentReaction adds emoji to a comment. 143 func (f *FakeClient) CreateCommentReaction(org, repo string, ID int, reaction string) error { 144 f.CommentReactionsAdded = append(f.CommentReactionsAdded, fmt.Sprintf("%s/%s#%d:%s", org, repo, ID, reaction)) 145 return nil 146 } 147 148 // CreateIssueReaction adds an emoji to an issue. 149 func (f *FakeClient) CreateIssueReaction(org, repo string, ID int, reaction string) error { 150 f.IssueReactionsAdded = append(f.IssueReactionsAdded, fmt.Sprintf("%s/%s#%d:%s", org, repo, ID, reaction)) 151 return nil 152 } 153 154 // DeleteComment deletes a comment. 155 func (f *FakeClient) DeleteComment(owner, repo string, ID int) error { 156 f.IssueCommentsDeleted = append(f.IssueCommentsDeleted, fmt.Sprintf("%s/%s#%d", owner, repo, ID)) 157 for num, ics := range f.IssueComments { 158 for i, ic := range ics { 159 if ic.ID == ID { 160 f.IssueComments[num] = append(ics[:i], ics[i+1:]...) 161 return nil 162 } 163 } 164 } 165 return fmt.Errorf("could not find issue comment %d", ID) 166 } 167 168 // DeleteStaleComments deletes comments flagged by isStale. 169 func (f *FakeClient) DeleteStaleComments(org, repo string, number int, comments []github.IssueComment, isStale func(github.IssueComment) bool) error { 170 if comments == nil { 171 comments, _ = f.ListIssueComments(org, repo, number) 172 } 173 for _, comment := range comments { 174 if isStale(comment) { 175 if err := f.DeleteComment(org, repo, comment.ID); err != nil { 176 return fmt.Errorf("failed to delete stale comment with ID '%d'", comment.ID) 177 } 178 } 179 } 180 return nil 181 } 182 183 // GetPullRequest returns details about the PR. 184 func (f *FakeClient) GetPullRequest(owner, repo string, number int) (*github.PullRequest, error) { 185 return f.PullRequests[number], nil 186 } 187 188 // GetPullRequestChanges returns the file modifications in a PR. 189 func (f *FakeClient) GetPullRequestChanges(org, repo string, number int) ([]github.PullRequestChange, error) { 190 return f.PullRequestChanges[number], nil 191 } 192 193 // GetRef returns the hash of a ref. 194 func (f *FakeClient) GetRef(owner, repo, ref string) (string, error) { 195 return "abcde", nil 196 } 197 198 // DeleteRef returns an error indicating if deletion of the given ref was successful 199 func (f *FakeClient) DeleteRef(owner, repo, ref string) error { 200 f.RefsDeleted = append(f.RefsDeleted, struct{ Org, Repo, Ref string }{Org: owner, Repo: repo, Ref: ref}) 201 return nil 202 } 203 204 // GetSingleCommit returns a single commit. 205 func (f *FakeClient) GetSingleCommit(org, repo, SHA string) (github.SingleCommit, error) { 206 return f.Commits[SHA], nil 207 } 208 209 // CreateStatus adds a status context to a commit. 210 func (f *FakeClient) CreateStatus(owner, repo, SHA string, s github.Status) error { 211 if f.CreatedStatuses == nil { 212 f.CreatedStatuses = make(map[string][]github.Status) 213 } 214 statuses := f.CreatedStatuses[SHA] 215 var updated bool 216 for i := range statuses { 217 if statuses[i].Context == s.Context { 218 statuses[i] = s 219 updated = true 220 } 221 } 222 if !updated { 223 statuses = append(statuses, s) 224 } 225 f.CreatedStatuses[SHA] = statuses 226 return nil 227 } 228 229 // ListStatuses returns individual status contexts on a commit. 230 func (f *FakeClient) ListStatuses(org, repo, ref string) ([]github.Status, error) { 231 return f.CreatedStatuses[ref], nil 232 } 233 234 // GetCombinedStatus returns the overall status for a commit. 235 func (f *FakeClient) GetCombinedStatus(owner, repo, ref string) (*github.CombinedStatus, error) { 236 return f.CombinedStatuses[ref], nil 237 } 238 239 // GetRepoLabels gets labels in a repo. 240 func (f *FakeClient) GetRepoLabels(owner, repo string) ([]github.Label, error) { 241 la := []github.Label{} 242 for _, l := range f.RepoLabelsExisting { 243 la = append(la, github.Label{Name: l}) 244 } 245 return la, nil 246 } 247 248 // GetIssueLabels gets labels on an issue 249 func (f *FakeClient) GetIssueLabels(owner, repo string, number int) ([]github.Label, error) { 250 re := regexp.MustCompile(fmt.Sprintf(`^%s/%s#%d:(.*)$`, owner, repo, number)) 251 la := []github.Label{} 252 allLabels := sets.NewString(f.IssueLabelsExisting...) 253 allLabels.Insert(f.IssueLabelsAdded...) 254 allLabels.Delete(f.IssueLabelsRemoved...) 255 for _, l := range allLabels.List() { 256 groups := re.FindStringSubmatch(l) 257 if groups != nil { 258 la = append(la, github.Label{Name: groups[1]}) 259 } 260 } 261 return la, nil 262 } 263 264 // AddLabel adds a label 265 func (f *FakeClient) AddLabel(owner, repo string, number int, label string) error { 266 labelString := fmt.Sprintf("%s/%s#%d:%s", owner, repo, number, label) 267 if sets.NewString(f.IssueLabelsAdded...).Has(labelString) { 268 return fmt.Errorf("cannot add %v to %s/%s/#%d", label, owner, repo, number) 269 } 270 if f.RepoLabelsExisting == nil { 271 f.IssueLabelsAdded = append(f.IssueLabelsAdded, labelString) 272 return nil 273 } 274 for _, l := range f.RepoLabelsExisting { 275 if label == l { 276 f.IssueLabelsAdded = append(f.IssueLabelsAdded, labelString) 277 return nil 278 } 279 } 280 return fmt.Errorf("cannot add %v to %s/%s/#%d", label, owner, repo, number) 281 } 282 283 // RemoveLabel removes a label 284 func (f *FakeClient) RemoveLabel(owner, repo string, number int, label string) error { 285 labelString := fmt.Sprintf("%s/%s#%d:%s", owner, repo, number, label) 286 if !sets.NewString(f.IssueLabelsRemoved...).Has(labelString) { 287 f.IssueLabelsRemoved = append(f.IssueLabelsRemoved, labelString) 288 return nil 289 } 290 return fmt.Errorf("cannot remove %v from %s/%s/#%d", label, owner, repo, number) 291 } 292 293 // FindIssues returns f.Issues 294 func (f *FakeClient) FindIssues(query, sort string, asc bool) ([]github.Issue, error) { 295 return f.Issues, nil 296 } 297 298 // AssignIssue adds assignees. 299 func (f *FakeClient) AssignIssue(owner, repo string, number int, assignees []string) error { 300 var m github.MissingUsers 301 for _, a := range assignees { 302 if a == "not-in-the-org" { 303 m.Users = append(m.Users, a) 304 continue 305 } 306 f.AssigneesAdded = append(f.AssigneesAdded, fmt.Sprintf("%s/%s#%d:%s", owner, repo, number, a)) 307 } 308 if m.Users == nil { 309 return nil 310 } 311 return m 312 } 313 314 // GetFile returns the bytes of the file. 315 func (f *FakeClient) GetFile(org, repo, file, commit string) ([]byte, error) { 316 contents, ok := f.RemoteFiles[file] 317 if !ok { 318 return nil, fmt.Errorf("could not find file %s", file) 319 } 320 if commit == "" { 321 if master, ok := contents["master"]; ok { 322 return []byte(master), nil 323 } 324 325 return nil, fmt.Errorf("could not find file %s in master", file) 326 } 327 328 if content, ok := contents[commit]; ok { 329 return []byte(content), nil 330 } 331 332 return nil, fmt.Errorf("could not find file %s with ref %s", file, commit) 333 } 334 335 // ListTeams return a list of fake teams that correspond to the fake team members returned by ListTeamMembers 336 func (f *FakeClient) ListTeams(org string) ([]github.Team, error) { 337 return []github.Team{ 338 { 339 ID: 0, 340 Name: "Admins", 341 }, 342 { 343 ID: 42, 344 Name: "Leads", 345 }, 346 }, nil 347 } 348 349 // ListTeamMembers return a fake team with a single "sig-lead" Github teammember 350 func (f *FakeClient) ListTeamMembers(teamID int, role string) ([]github.TeamMember, error) { 351 if role != github.RoleAll { 352 return nil, fmt.Errorf("unsupported role %v (only all supported)", role) 353 } 354 teams := map[int][]github.TeamMember{ 355 0: {{Login: "default-sig-lead"}}, 356 42: {{Login: "sig-lead"}}, 357 } 358 members, ok := teams[teamID] 359 if !ok { 360 return []github.TeamMember{}, nil 361 } 362 return members, nil 363 } 364 365 // IsCollaborator returns true if the user is a collaborator of the repo. 366 func (f *FakeClient) IsCollaborator(org, repo, login string) (bool, error) { 367 normed := github.NormLogin(login) 368 for _, collab := range f.Collaborators { 369 if github.NormLogin(collab) == normed { 370 return true, nil 371 } 372 } 373 return false, nil 374 } 375 376 // ListCollaborators lists the collaborators. 377 func (f *FakeClient) ListCollaborators(org, repo string) ([]github.User, error) { 378 result := make([]github.User, 0, len(f.Collaborators)) 379 for _, login := range f.Collaborators { 380 result = append(result, github.User{Login: login}) 381 } 382 return result, nil 383 } 384 385 // ClearMilestone removes the milestone 386 func (f *FakeClient) ClearMilestone(org, repo string, issueNum int) error { 387 f.Milestone = 0 388 return nil 389 } 390 391 // SetMilestone sets the milestone. 392 func (f *FakeClient) SetMilestone(org, repo string, issueNum, milestoneNum int) error { 393 if milestoneNum < 0 { 394 return fmt.Errorf("Milestone Numbers Cannot Be Negative") 395 } 396 f.Milestone = milestoneNum 397 return nil 398 } 399 400 // ListMilestones lists milestones. 401 func (f *FakeClient) ListMilestones(org, repo string) ([]github.Milestone, error) { 402 milestones := []github.Milestone{} 403 for k, v := range f.MilestoneMap { 404 milestones = append(milestones, github.Milestone{Title: k, Number: v}) 405 } 406 return milestones, nil 407 } 408 409 // ListPRCommits lists commits for a given PR. 410 func (f *FakeClient) ListPRCommits(org, repo string, prNumber int) ([]github.RepositoryCommit, error) { 411 k := fmt.Sprintf("%s/%s#%d", org, repo, prNumber) 412 return f.CommitMap[k], nil 413 }