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  }