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  }