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 }