github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/transfer-issue/transfer-issue_test.go (about)

     1  /*
     2  Copyright 2021 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 transferissue
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"strings"
    27  	"testing"
    28  	"unicode"
    29  
    30  	"github.com/shurcooL/githubv4"
    31  	"github.com/sirupsen/logrus"
    32  
    33  	"sigs.k8s.io/prow/pkg/github"
    34  	"sigs.k8s.io/prow/pkg/github/fakegithub"
    35  )
    36  
    37  const issuerNum = 1
    38  
    39  func Test_handleTransfer(t *testing.T) {
    40  	ts := []struct {
    41  		name         string
    42  		event        github.GenericCommentEvent
    43  		expectError  bool
    44  		errorMessage string
    45  		comment      string
    46  		fcFunc       func(client *fakegithub.FakeClient)
    47  		tcFunc       func(client *testClient)
    48  	}{
    49  		{
    50  			name:  "is a pr",
    51  			event: github.GenericCommentEvent{IsPR: true},
    52  		},
    53  		{
    54  			name:  "is not comment added",
    55  			event: github.GenericCommentEvent{Action: github.GenericCommentActionDeleted},
    56  		},
    57  		{
    58  			name: "multiple matches",
    59  			event: github.GenericCommentEvent{
    60  				Action: github.GenericCommentActionCreated,
    61  				Body: `/transfer-issue kubectl
    62  /transfer test-infra`,
    63  				HTMLURL: fmt.Sprintf("https://github.com/kubernetes/fake/issues/%d", issuerNum),
    64  				Number:  issuerNum,
    65  				Repo:    github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
    66  				User:    github.User{Login: "user"},
    67  			},
    68  			comment: "single destination",
    69  		},
    70  		{
    71  			name: "no destination",
    72  			event: github.GenericCommentEvent{
    73  				Action:  github.GenericCommentActionCreated,
    74  				Body:    "/transfer",
    75  				HTMLURL: fmt.Sprintf("https://github.com/kubernetes/fake/issues/%d", issuerNum),
    76  				Number:  issuerNum,
    77  				Repo:    github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
    78  				User:    github.User{Login: "user"},
    79  			},
    80  			comment: "single destination",
    81  		},
    82  		{
    83  			name: "dest repo does not exist",
    84  			event: github.GenericCommentEvent{
    85  				Action:  github.GenericCommentActionCreated,
    86  				Body:    "/transfer-issue fake",
    87  				HTMLURL: fmt.Sprintf("https://github.com/kubernetes/fake/issues/%d", issuerNum),
    88  				Number:  issuerNum,
    89  				Repo:    github.Repo{Owner: github.User{Login: "kubernetes"}, Name: "kubectl"},
    90  				User:    github.User{Login: "user"},
    91  			},
    92  			comment: "does not exist",
    93  			fcFunc: func(fc *fakegithub.FakeClient) {
    94  				fc.GetRepoError = errors.New("stub")
    95  			},
    96  		},
    97  		{
    98  			name: "not collaborator",
    99  			event: github.GenericCommentEvent{
   100  				Action:  github.GenericCommentActionCreated,
   101  				Body:    "/transfer-issue test-infra",
   102  				HTMLURL: fmt.Sprintf("https://github.com/kubernetes/fake/issues/%d", issuerNum),
   103  				Number:  issuerNum,
   104  				Repo:    github.Repo{Owner: github.User{Login: "kubernetes"}, Name: "kubectl"},
   105  				User:    github.User{Login: "user"},
   106  			},
   107  			comment: "must be an org member",
   108  			fcFunc: func(fc *fakegithub.FakeClient) {
   109  				fc.OrgMembers["kubernetes"] = []string{}
   110  			},
   111  		},
   112  		{
   113  			name: "trims whitespace from user input",
   114  			event: github.GenericCommentEvent{
   115  				Action: github.GenericCommentActionCreated,
   116  				Body:   "/transfer-issue test-infra\r",
   117  				Number: issuerNum,
   118  				Repo:   github.Repo{Owner: github.User{Login: "kubernetes"}, Name: "kubectl"},
   119  				User:   github.User{Login: "user"},
   120  				NodeID: "fakeIssueNodeID",
   121  			},
   122  			fcFunc: func(fc *fakegithub.FakeClient) {
   123  				fc.OrgMembers["kubernetes"] = []string{"user"}
   124  			},
   125  			tcFunc: func(c *testClient) {
   126  				c.repoNodeID = "fakeRepoNodeID"
   127  			},
   128  		},
   129  		{
   130  			name: "happy path",
   131  			event: github.GenericCommentEvent{
   132  				Action: github.GenericCommentActionCreated,
   133  				Body: `This belongs elsewhere
   134  /transfer-issue test-infra
   135  Thanks!`,
   136  				Number: issuerNum,
   137  				Repo:   github.Repo{Owner: github.User{Login: "kubernetes"}, Name: "kubectl"},
   138  				User:   github.User{Login: "user"},
   139  				NodeID: "fakeIssueNodeID",
   140  			},
   141  			fcFunc: func(fc *fakegithub.FakeClient) {
   142  				fc.OrgMembers["kubernetes"] = []string{"user"}
   143  			},
   144  			tcFunc: func(c *testClient) {
   145  				c.repoNodeID = "fakeRepoNodeID"
   146  			},
   147  		},
   148  	}
   149  
   150  	for _, tc := range ts {
   151  		t.Run(tc.name, func(t *testing.T) {
   152  			fc := fakegithub.NewFakeClient()
   153  			c := &testClient{fc: fc}
   154  			if tc.tcFunc != nil {
   155  				tc.tcFunc(c)
   156  			}
   157  			if tc.fcFunc != nil {
   158  				tc.fcFunc(fc)
   159  			}
   160  			log := logrus.WithField("plugin", pluginName)
   161  			err := handleTransfer(c, log, tc.event)
   162  			if err != nil {
   163  				if !tc.expectError {
   164  					t.Fatalf("unexpected error: %v", err)
   165  				}
   166  				if m := err.Error(); !strings.Contains(m, tc.errorMessage) {
   167  					t.Fatalf("expected error to contain: %s got: %v", tc.errorMessage, m)
   168  				}
   169  			}
   170  			if err == nil && tc.expectError {
   171  				t.Fatalf("expected error but did not produce")
   172  			}
   173  			if len(tc.comment) != 0 {
   174  				if cm, ok := fc.IssueComments[tc.event.Number]; ok {
   175  					if !strings.Contains(cm[0].Body, tc.comment) {
   176  						t.Errorf("expected comment to contain: %s got: %s", tc.comment, cm[0].Body)
   177  					}
   178  				} else {
   179  					t.Errorf("expected comment to contain: %s but no comment", tc.comment)
   180  				}
   181  			}
   182  			if len(tc.comment) == 0 && len(fc.IssueComments[issuerNum]) != 0 {
   183  				t.Errorf("unexpected comment: %v", fc.IssueComments[issuerNum])
   184  			}
   185  		})
   186  	}
   187  }
   188  
   189  type testRoundTripper struct {
   190  	rt func(*http.Request) (*http.Response, error)
   191  }
   192  
   193  func (rt testRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
   194  	return rt.rt(r)
   195  }
   196  
   197  type testClient struct {
   198  	fc         *fakegithub.FakeClient
   199  	repoNodeID string
   200  }
   201  
   202  func (t *testClient) GetRepo(org, name string) (github.FullRepo, error) {
   203  	r := []rune(name)
   204  	if lastChar := r[len(r)-1]; unicode.IsSpace(lastChar) {
   205  		return github.FullRepo{}, fmt.Errorf("failed creating new request: parse \"https://api.github.com/repos/%s/%s\\r\": net/url: invalid control character in URL", org, name)
   206  	}
   207  	repo, err := t.fc.GetRepo(org, name)
   208  	if len(t.repoNodeID) != 0 {
   209  		repo.NodeID = t.repoNodeID
   210  	}
   211  	return repo, err
   212  }
   213  
   214  func (t *testClient) CreateComment(org, repo string, number int, comment string) error {
   215  	return t.fc.CreateComment(org, repo, number, comment)
   216  }
   217  
   218  func (t *testClient) IsMember(org, user string) (bool, error) {
   219  	return t.fc.IsMember(org, user)
   220  }
   221  
   222  func (t *testClient) MutateWithGitHubAppsSupport(ctx context.Context, m interface{}, input githubv4.Input, vars map[string]interface{}, org string) error {
   223  	mr := `{"data": { "transferIssue": { "issue": { "url": "https://kubernetes.io/fake" } } } }`
   224  
   225  	gqlc := githubv4.NewClient(&http.Client{
   226  		Transport: testRoundTripper{rt: func(r *http.Request) (*http.Response, error) {
   227  			defer r.Body.Close()
   228  			body, err := io.ReadAll(r.Body)
   229  			if err != nil {
   230  				return nil, err
   231  			}
   232  			s := string(body)
   233  			if !(strings.Contains(s, "fakeRepoNodeID") && strings.Contains(s, "fakeIssueNodeID")) {
   234  				return nil, fmt.Errorf("unexpected request body: %s", s)
   235  			}
   236  			return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString(mr))}, nil
   237  		}},
   238  	})
   239  
   240  	return gqlc.Mutate(ctx, m, input, vars)
   241  }
   242  
   243  func Test_transferIssue(t *testing.T) {
   244  	c := &testClient{}
   245  	issue, err := transferIssue(c, "k8s", "fakeRepoNodeID", "fakeIssueNodeID")
   246  	if err != nil {
   247  		t.Fatalf("unexpected error: %v", err)
   248  	}
   249  	if got := issue.TransferIssue.Issue.URL.String(); got != "https://kubernetes.io/fake" {
   250  		t.Fatalf("unexpected url: %v", got)
   251  	}
   252  }