github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/cat/cat_test.go (about) 1 /* 2 Copyright 2018 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 cat 18 19 import ( 20 "errors" 21 "flag" 22 "fmt" 23 "io" 24 "net/http" 25 "net/http/httptest" 26 "regexp" 27 "strings" 28 "testing" 29 30 "github.com/sirupsen/logrus" 31 32 "k8s.io/test-infra/prow/github" 33 "k8s.io/test-infra/prow/github/fakegithub" 34 ) 35 36 type fakeClowder string 37 38 var human = flag.Bool("human", false, "Enable to run additional manual tests") 39 var category = flag.String("category", "", "Request a particular category if set") 40 var movieCat = flag.Bool("gif", false, "Specifically request a GIF image if set") 41 var keyPath = flag.String("key-path", "", "Path to api key if set") 42 43 func (c fakeClowder) readCat(category string, movieCat bool) (string, error) { 44 if category == "error" { 45 return "", errors.New(string(c)) 46 } 47 return fmt.Sprintf("", c), nil 48 } 49 50 func TestRealCat(t *testing.T) { 51 if !*human { 52 t.Skip("Real cats disabled for automation. Manual users can add --human [--category=foo]") 53 } 54 if *keyPath != "" { 55 meow.setKey(*keyPath, logrus.WithField("plugin", pluginName)) 56 } 57 58 if cat, err := meow.readCat(*category, *movieCat); err != nil { 59 t.Errorf("Could not read cats from %#v: %v", meow, err) 60 } else { 61 fmt.Println(cat) 62 } 63 } 64 65 func TestUrl(t *testing.T) { 66 cases := []struct { 67 name string 68 url string 69 category string 70 key string 71 movie bool 72 require []string 73 deny []string 74 }{ 75 { 76 name: "only url", 77 url: "http://foo", 78 }, 79 { 80 name: "key", 81 url: "http://foo", 82 key: "blah", 83 require: []string{"api_key=blah"}, 84 deny: []string{"category=", "mime_types=gif"}, 85 }, 86 { 87 name: "category", 88 url: "http://foo", 89 category: "bar", 90 require: []string{"category=bar"}, 91 deny: []string{"api_key=", "mime_types=gif"}, 92 }, 93 { 94 name: "movie", 95 url: "http://foo", 96 movie: true, 97 require: []string{"mime_types=gif"}, 98 deny: []string{"category=this", "api_key=that"}, 99 }, 100 { 101 name: "category and movie", 102 url: "http://foo", 103 category: "this", 104 movie: true, 105 require: []string{"mime_types=gif", "category=this", "&"}, 106 deny: []string{"api_key="}, 107 }, 108 { 109 name: "category and key", 110 url: "http://foo", 111 category: "this", 112 key: "that", 113 require: []string{"category=this", "api_key=that", "&"}, 114 deny: []string{"mime_types=gif"}, 115 }, 116 { 117 name: "category, key, and movie", 118 url: "http://foo", 119 category: "this", 120 key: "that", 121 movie: true, 122 require: []string{"category=this", "api_key=that", "&", "mime_types=gif"}, 123 }, 124 } 125 126 for _, tc := range cases { 127 rc := realClowder{ 128 url: tc.url, 129 key: tc.key, 130 } 131 url := rc.URL(tc.category, tc.movie) 132 for _, r := range tc.require { 133 if !strings.Contains(url, r) { 134 t.Errorf("%s: %s does not contain %s", tc.name, url, r) 135 } 136 } 137 for _, d := range tc.deny { 138 if strings.Contains(url, d) { 139 t.Errorf("%s: %s contained unexpected %s", tc.name, url, d) 140 } 141 } 142 } 143 } 144 145 func TestFormat(t *testing.T) { 146 re := regexp.MustCompile(`\[!\[.+\]\(.+\)\]\(.+\)`) 147 basicURL := "http://example.com" 148 testcases := []struct { 149 name string 150 src string 151 img string 152 err bool 153 }{ 154 { 155 name: "basically works", 156 src: basicURL, 157 img: basicURL, 158 err: false, 159 }, 160 { 161 name: "empty source", 162 src: "", 163 img: basicURL, 164 err: true, 165 }, 166 { 167 name: "empty image", 168 src: basicURL, 169 img: "", 170 err: true, 171 }, 172 { 173 name: "bad source", 174 src: "http://this is not a url", 175 img: basicURL, 176 err: true, 177 }, 178 { 179 name: "bad image", 180 src: basicURL, 181 img: "http://still a bad url", 182 err: true, 183 }, 184 } 185 for _, tc := range testcases { 186 ret, err := catResult{ 187 Source: tc.src, 188 Image: tc.img, 189 }.Format() 190 switch { 191 case tc.err: 192 if err == nil { 193 t.Errorf("%s: failed to raise an error", tc.name) 194 } 195 case err != nil: 196 t.Errorf("%s: unexpected error: %v", tc.name, err) 197 case !re.MatchString(ret): 198 t.Errorf("%s: bad return value: %s", tc.name, ret) 199 } 200 } 201 } 202 203 // Medium integration test (depends on ability to open a TCP port) 204 func TestHttpResponse(t *testing.T) { 205 // create test cases for handling content length of images 206 contentLength := make(map[string]string) 207 contentLength["/cat.jpg"] = "717987" 208 contentLength["/bigcat.jpg"] = "12647753" 209 210 // fake server for images 211 ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 212 if s, ok := contentLength[r.URL.Path]; ok { 213 body := "binary image" 214 w.Header().Set("Content-Length", s) 215 io.WriteString(w, body) 216 } else { 217 t.Errorf("Cannot find content length for %s", r.URL.Path) 218 } 219 })) 220 defer ts2.Close() 221 222 // create test cases for handling http responses 223 img := ts2.URL + "/cat.jpg" 224 bigimg := ts2.URL + "/bigcat.jpg" 225 src := "http://localhost?kind=source_url" 226 validResponse := fmt.Sprintf(`[{"id":"valid","url":"%s","source_url":"%s"}]`, img, src) 227 var testcases = []struct { 228 name string 229 path string 230 response string 231 valid bool 232 code int 233 }{ 234 { 235 name: "valid", 236 path: "/valid", 237 response: validResponse, 238 valid: true, 239 }, 240 { 241 name: "image too big", 242 path: "/too-big", 243 response: fmt.Sprintf(`[{"id":"toobig","url":"%s","source_url":"%s"}]`, bigimg, src), 244 }, 245 { 246 name: "return-406", 247 path: "/return-406", 248 code: 406, 249 response: ` 250 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 251 <html><head> 252 <title>406 Not Acceptable</title> 253 </head><body> 254 <h1>Not Acceptable</h1> 255 <p>An appropriate representation of the requested resource /api/images/get could not be found on this server.</p> 256 Available variants: 257 <ul> 258 <li><a href="get.php">get.php</a> , type x-mapp-php5</li> 259 </ul> 260 </body></html>`, 261 }, 262 { 263 name: "no-cats-in-json", 264 path: "/no-cats-in-json", 265 response: "[]", 266 }, 267 { 268 name: "no-image-in-json", 269 path: "/no-image-in-json", 270 response: "[{}]", 271 }, 272 } 273 274 // fake server for image urls 275 pathToResponse := make(map[string]string) 276 for _, testcase := range testcases { 277 pathToResponse[testcase.path] = testcase.response 278 } 279 codes := make(map[string]int) 280 for _, tc := range testcases { 281 codes[tc.path] = tc.code 282 } 283 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 284 code := codes[r.URL.Path] 285 if code > 0 { 286 w.WriteHeader(code) 287 } 288 if r, ok := pathToResponse[r.URL.Path]; ok { 289 io.WriteString(w, r) 290 } else { 291 io.WriteString(w, validResponse) 292 } 293 })) 294 defer ts.Close() 295 296 // github fake client 297 fc := &fakegithub.FakeClient{ 298 IssueComments: make(map[int][]github.IssueComment), 299 } 300 301 // run test for each case 302 for _, testcase := range testcases { 303 fakemeow := &realClowder{url: ts.URL + testcase.path} 304 cat, err := fakemeow.readCat(*category, *movieCat) 305 if testcase.valid && err != nil { 306 t.Errorf("For case %s, didn't expect error: %v", testcase.name, err) 307 } else if !testcase.valid && err == nil { 308 t.Errorf("For case %s, expected error, received cat: %s", testcase.name, cat) 309 } else if testcase.valid && cat == "" { 310 t.Errorf("For case %s, got an empty cat", testcase.name) 311 } 312 } 313 314 // fully test handling a comment 315 comment := "/meowvie space" 316 317 e := &github.GenericCommentEvent{ 318 Action: github.GenericCommentActionCreated, 319 Body: comment, 320 Number: 5, 321 IssueState: "open", 322 } 323 if err := handle(fc, logrus.WithField("plugin", pluginName), e, &realClowder{url: ts.URL + "/?format=json"}, func() {}); err != nil { 324 t.Errorf("didn't expect error: %v", err) 325 return 326 } 327 if len(fc.IssueComments[5]) != 1 { 328 t.Error("should have commented.") 329 return 330 } 331 if c := fc.IssueComments[5][0]; !strings.Contains(c.Body, img) { 332 t.Errorf("missing image url: %s from comment: %v", img, c) 333 } else if !strings.Contains(c.Body, src) { 334 t.Errorf("missing source url: %s from comment: %v", src, c) 335 } 336 337 } 338 339 // Small, unit tests 340 func TestCats(t *testing.T) { 341 var testcases = []struct { 342 name string 343 action github.GenericCommentEventAction 344 body string 345 state string 346 pr bool 347 shouldComment bool 348 shouldError bool 349 }{ 350 { 351 name: "ignore edited comment", 352 state: "open", 353 action: github.GenericCommentActionEdited, 354 body: "/meow", 355 shouldComment: false, 356 shouldError: false, 357 }, 358 { 359 name: "leave cat on pr", 360 state: "open", 361 action: github.GenericCommentActionCreated, 362 body: "/meow", 363 pr: true, 364 shouldComment: true, 365 shouldError: false, 366 }, 367 { 368 name: "leave cat on issue", 369 state: "open", 370 action: github.GenericCommentActionCreated, 371 body: "/meow", 372 shouldComment: true, 373 shouldError: false, 374 }, 375 { 376 name: "leave cat on issue, trailing space", 377 state: "open", 378 action: github.GenericCommentActionCreated, 379 body: "/meow \r", 380 shouldComment: true, 381 shouldError: false, 382 }, 383 { 384 name: "categorical cat", 385 state: "open", 386 action: github.GenericCommentActionCreated, 387 body: "/meow clothes", 388 shouldComment: true, 389 shouldError: false, 390 }, 391 { 392 name: "bad cat", 393 state: "open", 394 action: github.GenericCommentActionCreated, 395 body: "/meow error", 396 shouldComment: true, 397 shouldError: true, 398 }, 399 { 400 name: "movie cat", 401 state: "open", 402 action: github.GenericCommentActionCreated, 403 body: "/meowvie", 404 shouldComment: true, 405 shouldError: false, 406 }, 407 { 408 name: "categorical movie cat", 409 state: "open", 410 action: github.GenericCommentActionCreated, 411 body: "/meowvie space", 412 shouldComment: true, 413 shouldError: false, 414 }, 415 } 416 for _, tc := range testcases { 417 fc := &fakegithub.FakeClient{ 418 IssueComments: make(map[int][]github.IssueComment), 419 } 420 e := &github.GenericCommentEvent{ 421 Action: tc.action, 422 Body: tc.body, 423 Number: 5, 424 IssueState: tc.state, 425 IsPR: tc.pr, 426 } 427 err := handle(fc, logrus.WithField("plugin", pluginName), e, fakeClowder("tubbs"), func() {}) 428 if !tc.shouldError && err != nil { 429 t.Errorf("%s: didn't expect error: %v", tc.name, err) 430 continue 431 } else if tc.shouldError && err == nil { 432 t.Errorf("%s: expected an error to occur", tc.name) 433 continue 434 } 435 if tc.shouldComment && len(fc.IssueComments[5]) != 1 { 436 t.Errorf("%s: should have commented.", tc.name) 437 } else if tc.shouldComment { 438 shouldImage := !tc.shouldError 439 body := fc.IssueComments[5][0].Body 440 hasImage := strings.Contains(body, "![") 441 if hasImage && !shouldImage { 442 t.Errorf("%s: unexpected image in %s", tc.name, body) 443 } else if !hasImage && shouldImage { 444 t.Errorf("%s: no image in %s", tc.name, body) 445 } 446 } else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 { 447 t.Errorf("%s: should not have commented.", tc.name) 448 } 449 } 450 }