k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/robots/commenter/main_test.go (about) 1 /* 2 Copyright 2017 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 main 18 19 import ( 20 "errors" 21 "fmt" 22 "strconv" 23 "strings" 24 "testing" 25 "time" 26 27 "sigs.k8s.io/prow/pkg/github" 28 ) 29 30 func TestParseHTMLURL(t *testing.T) { 31 cases := []struct { 32 name string 33 url string 34 org string 35 repo string 36 num int 37 fail bool 38 }{ 39 { 40 name: "normal issue", 41 url: "https://github.com/org/repo/issues/1234", 42 org: "org", 43 repo: "repo", 44 num: 1234, 45 }, 46 { 47 name: "normal pull", 48 url: "https://github.com/pull-org/pull-repo/pull/5555", 49 org: "pull-org", 50 repo: "pull-repo", 51 num: 5555, 52 }, 53 { 54 name: "different host", 55 url: "ftp://gitlab.whatever/org/repo/issues/6666", 56 org: "org", 57 repo: "repo", 58 num: 6666, 59 }, 60 { 61 name: "string issue", 62 url: "https://github.com/org/repo/issues/future", 63 fail: true, 64 }, 65 { 66 name: "weird issue", 67 url: "https://gubernator.k8s.io/build/kubernetes-jenkins/logs/ci-kubernetes-e2e-gci-gce/11947/", 68 fail: true, 69 }, 70 } 71 72 for _, tc := range cases { 73 org, repo, num, err := parseHTMLURL(tc.url) 74 if err != nil && !tc.fail { 75 t.Errorf("%s: should not have produced error: %v", tc.name, err) 76 } else if err == nil && tc.fail { 77 t.Errorf("%s: failed to produce an error", tc.name) 78 } else { 79 if org != tc.org { 80 t.Errorf("%s: org %s != expected %s", tc.name, org, tc.org) 81 } 82 if repo != tc.repo { 83 t.Errorf("%s: repo %s != expected %s", tc.name, repo, tc.repo) 84 } 85 if num != tc.num { 86 t.Errorf("%s: num %d != expected %d", tc.name, num, tc.num) 87 } 88 } 89 } 90 } 91 92 func TestMakeQuery(t *testing.T) { 93 cases := []struct { 94 name string 95 query string 96 archived bool 97 closed bool 98 locked bool 99 dur time.Duration 100 expected []string 101 unexpected []string 102 err bool 103 }{ 104 { 105 name: "basic query", 106 query: "hello world", 107 expected: []string{"hello world", "is:open", "archived:false"}, 108 unexpected: []string{"updated:", "openhello", "worldis"}, 109 }, 110 { 111 name: "basic archived", 112 query: "hello world", 113 archived: true, 114 expected: []string{"hello world", "is:open", "is:unlocked"}, 115 unexpected: []string{"archived:false"}, 116 }, 117 { 118 name: "basic closed", 119 query: "hello world", 120 closed: true, 121 expected: []string{"hello world", "archived:false", "is:unlocked"}, 122 unexpected: []string{"is:open"}, 123 }, 124 { 125 name: "basic locked", 126 query: "hello world", 127 locked: true, 128 expected: []string{"hello world", "is:open", "archived:false"}, 129 unexpected: []string{"is:unlocked"}, 130 }, 131 { 132 name: "basic duration", 133 query: "hello", 134 dur: 1 * time.Hour, 135 expected: []string{"hello", "updated:<"}, 136 }, 137 { 138 name: "weird characters not escaped", 139 query: "oh yeah!@#$&*()", 140 expected: []string{"!", "@", "#", " "}, 141 unexpected: []string{"%", "+"}, 142 }, 143 { 144 name: "linebreaks are replaced by whitespaces", 145 query: "label:foo\nlabel:bar", 146 expected: []string{"label:foo label:bar"}, 147 }, 148 { 149 name: "include closed with is:open query errors", 150 query: "hello is:open", 151 closed: true, 152 err: true, 153 }, 154 { 155 name: "archived:false with include-archived errors", 156 query: "hello archived:false", 157 archived: true, 158 err: true, 159 }, 160 { 161 name: "archived:true without includeArchived errors", 162 query: "hello archived:true", 163 err: true, 164 }, 165 { 166 name: "is:closed without includeClosed errors", 167 query: "hello is:closed", 168 err: true, 169 }, 170 { 171 name: "is:locked without includeLocked errors", 172 query: "hello is:locked", 173 err: true, 174 }, 175 { 176 name: "is:unlocked with includeLocked errors", 177 query: "hello is:unlocked", 178 locked: true, 179 err: true, 180 }, 181 } 182 183 for _, tc := range cases { 184 actual, err := makeQuery(tc.query, tc.archived, tc.closed, tc.locked, tc.dur) 185 if err != nil && !tc.err { 186 t.Errorf("%s: unexpected error: %v", tc.name, err) 187 } else if err == nil && tc.err { 188 t.Errorf("%s: failed to raise an error", tc.name) 189 } 190 for _, e := range tc.expected { 191 if !strings.Contains(actual, e) { 192 t.Errorf("%s: could not find %s in %s", tc.name, e, actual) 193 } 194 } 195 for _, u := range tc.unexpected { 196 if strings.Contains(actual, u) { 197 t.Errorf("%s: should not have found %s in %s", tc.name, u, actual) 198 } 199 } 200 } 201 } 202 203 func makeIssue(owner, repo string, number int, title string) github.Issue { 204 return github.Issue{ 205 HTMLURL: fmt.Sprintf("fake://localhost/%s/%s/pull/%d", owner, repo, number), 206 Title: title, 207 } 208 } 209 210 type fakeClient struct { 211 comments []int 212 issues []github.Issue 213 } 214 215 // Fakes Creating a client, using the same signature as github.Client 216 func (c *fakeClient) CreateComment(owner, repo string, number int, comment string) error { 217 if strings.Contains(comment, "error") || repo == "error" { 218 return errors.New(comment) 219 } 220 c.comments = append(c.comments, number) 221 return nil 222 } 223 224 // Fakes searching for issues, using the same signature as github.Client 225 func (c *fakeClient) FindIssues(query, sort string, asc bool) ([]github.Issue, error) { 226 if strings.Contains(query, "error") { 227 return nil, errors.New(query) 228 } 229 ret := []github.Issue{} 230 for _, i := range c.issues { 231 if strings.Contains(i.Title, query) { 232 ret = append(ret, i) 233 } 234 } 235 return ret, nil 236 } 237 238 func TestRun(t *testing.T) { 239 manyIssues := []github.Issue{} 240 manyComments := []int{} 241 for i := 0; i < 100; i++ { 242 manyIssues = append(manyIssues, makeIssue("o", "r", i, "many "+strconv.Itoa(i))) 243 manyComments = append(manyComments, i) 244 } 245 246 cases := []struct { 247 name string 248 query string 249 comment string 250 template bool 251 ceiling int 252 client fakeClient 253 expected []int 254 err bool 255 }{ 256 { 257 name: "find all", 258 query: "many", 259 comment: "found you", 260 client: fakeClient{issues: manyIssues}, 261 expected: manyComments, 262 }, 263 { 264 name: "find first 10", 265 query: "many", 266 ceiling: 10, 267 comment: "hey", 268 client: fakeClient{issues: manyIssues}, 269 expected: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 270 }, 271 { 272 name: "find none", 273 query: "none", 274 comment: "this should not happen", 275 client: fakeClient{issues: manyIssues}, 276 }, 277 { 278 name: "search error", 279 query: "this search should error", 280 comment: "comment", 281 client: fakeClient{issues: manyIssues}, 282 err: true, 283 }, 284 { 285 name: "comment error", 286 query: "problematic", 287 comment: "rolo tomassi", 288 client: fakeClient{issues: []github.Issue{ 289 makeIssue("o", "r", 1, "problematic this should work"), 290 makeIssue("o", "error", 2, "problematic expect an error"), 291 makeIssue("o", "r", 3, "problematic works as well"), 292 }}, 293 err: true, 294 expected: []int{1, 3}, 295 }, 296 { 297 name: "template comment", 298 query: "67", 299 client: fakeClient{issues: manyIssues}, 300 comment: "https://gubernator.k8s.io/pr/{{.Org}}/{{.Repo}}/{{.Number}}", 301 template: true, 302 expected: []int{67}, 303 }, 304 { 305 name: "bad template errors", 306 query: "67", 307 client: fakeClient{issues: manyIssues}, 308 comment: "Bad {{.UnknownField}}", 309 template: true, 310 err: true, 311 }, 312 } 313 314 for _, tc := range cases { 315 ignoreSorting := "" 316 ignoreOrder := false 317 err := run(&tc.client, tc.query, ignoreSorting, ignoreOrder, false, makeCommenter(tc.comment, tc.template), tc.ceiling) 318 if tc.err && err == nil { 319 t.Errorf("%s: failed to received an error", tc.name) 320 continue 321 } 322 if !tc.err && err != nil { 323 t.Errorf("%s: unexpected error: %v", tc.name, err) 324 continue 325 } 326 if len(tc.expected) != len(tc.client.comments) { 327 t.Errorf("%s: expected comments %v != actual %v", tc.name, tc.expected, tc.client.comments) 328 continue 329 } 330 missing := []int{} 331 for _, e := range tc.expected { 332 found := false 333 for _, cmt := range tc.client.comments { 334 if cmt == e { 335 found = true 336 break 337 } 338 } 339 if !found { 340 missing = append(missing, e) 341 } 342 } 343 if len(missing) > 0 { 344 t.Errorf("%s: missing %v from actual comments %v", tc.name, missing, tc.client.comments) 345 } 346 } 347 } 348 349 func TestMakeCommenter(t *testing.T) { 350 m := meta{ 351 Number: 10, 352 Org: "org", 353 Repo: "repo", 354 Issue: github.Issue{ 355 Number: 10, 356 HTMLURL: "url", 357 Title: "title", 358 }, 359 } 360 cases := []struct { 361 name string 362 comment string 363 template bool 364 expected string 365 err bool 366 }{ 367 { 368 name: "string works", 369 comment: "hello world {{.Number}} {{.Invalid}}", 370 expected: "hello world {{.Number}} {{.Invalid}}", 371 }, 372 { 373 name: "template works", 374 comment: "N={{.Number}} R={{.Repo}} O={{.Org}} U={{.Issue.HTMLURL}} T={{.Issue.Title}}", 375 template: true, 376 expected: "N=10 R=repo O=org U=url T=title", 377 }, 378 { 379 name: "bad template errors", 380 comment: "Bad {{.UnknownField}} Template", 381 expected: "Bad ", 382 template: true, 383 err: true, 384 }, 385 } 386 387 for _, tc := range cases { 388 c := makeCommenter(tc.comment, tc.template) 389 actual, err := c(m) 390 if actual != tc.expected { 391 t.Errorf("%s: expected '%s' != actual '%s'", tc.name, tc.expected, actual) 392 } 393 if err != nil && !tc.err { 394 t.Errorf("%s: unexpected err: %v", tc.name, err) 395 } 396 if err == nil && tc.err { 397 t.Errorf("%s: failed to raise an exception", tc.name) 398 } 399 } 400 }