sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/goose/goose_test.go (about) 1 /* 2 Copyright 2019 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 goose 18 19 import ( 20 "flag" 21 "fmt" 22 "io" 23 "net/http" 24 "net/http/httptest" 25 "regexp" 26 "strings" 27 "testing" 28 29 "github.com/sirupsen/logrus" 30 31 "sigs.k8s.io/prow/pkg/github" 32 "sigs.k8s.io/prow/pkg/github/fakegithub" 33 ) 34 35 type fakeGaggle string 36 37 var human = flag.Bool("human", false, "Enable to run additional manual tests") 38 var keyPath = flag.String("key-path", "", "Path to api key if set") 39 40 func (g fakeGaggle) readGoose() (string, error) { 41 return fmt.Sprintf("\n", g), nil 42 } 43 44 func TestRealGoose(t *testing.T) { 45 if !*human { 46 t.Skip("Real geese disabled for automation. Manual users can add --human") 47 } 48 if *keyPath != "" { 49 honk.setKey(*keyPath, logrus.WithField("plugin", pluginName)) 50 } 51 52 if goose, err := honk.readGoose(); err != nil { 53 t.Errorf("Could not read geese from %#v: %v", honk, err) 54 } else { 55 fmt.Println(goose) 56 } 57 } 58 59 func TestUrl(t *testing.T) { 60 cases := []struct { 61 name string 62 url string 63 key string 64 require []string 65 deny []string 66 }{ 67 { 68 name: "only url", 69 url: "http://foo", 70 }, 71 { 72 name: "key", 73 url: "http://foo", 74 key: "blah", 75 require: []string{"client_id=blah"}, 76 }, 77 } 78 79 for _, tc := range cases { 80 rg := realGaggle{ 81 url: tc.url, 82 key: tc.key, 83 } 84 url := rg.URL() 85 for _, r := range tc.require { 86 if !strings.Contains(url, r) { 87 t.Errorf("%s: %s does not contain %s", tc.name, url, r) 88 } 89 } 90 for _, d := range tc.deny { 91 if strings.Contains(url, d) { 92 t.Errorf("%s: %s contained unexpected %s", tc.name, url, d) 93 } 94 } 95 } 96 } 97 98 func TestFormat(t *testing.T) { 99 re := regexp.MustCompile(`!\[.+\]\(.+\)`) 100 basicURL := "http://example.com" 101 testcases := []struct { 102 name string 103 img string 104 err bool 105 }{ 106 { 107 name: "basically works", 108 img: basicURL, 109 err: false, 110 }, 111 { 112 name: "empty image", 113 img: "", 114 err: true, 115 }, 116 { 117 name: "bad image", 118 img: "http://still a bad url", 119 err: true, 120 }, 121 } 122 for _, tc := range testcases { 123 ret, err := gooseResult{ 124 Images: imageSet{ 125 Small: tc.img, 126 }, 127 }.Format() 128 129 switch { 130 case tc.err: 131 if err == nil { 132 t.Errorf("%s: failed to raise an error", tc.name) 133 } 134 case err != nil: 135 t.Errorf("%s: unexpected error: %v", tc.name, err) 136 case !re.MatchString(ret): 137 t.Errorf("%s: bad return value: %s", tc.name, ret) 138 } 139 } 140 } 141 142 // Medium integration test (depends on ability to open a TCP port) 143 func TestHttpResponse(t *testing.T) { 144 // create test cases for handling content length of images 145 contentLength := make(map[string]string) 146 contentLength["/goose.jpg"] = "717987" 147 contentLength["/biggoose.jpg"] = "12647753" 148 149 // fake server for images 150 ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 151 if s, ok := contentLength[r.URL.Path]; ok { 152 body := "binary image" 153 w.Header().Set("Content-Length", s) 154 io.WriteString(w, body) 155 } else { 156 t.Errorf("Cannot find content length for %s", r.URL.Path) 157 } 158 })) 159 defer ts2.Close() 160 161 // create test cases for handling http responses 162 img := ts2.URL + "/goose.jpg" 163 bigimg := ts2.URL + "/biggoose.jpg" 164 validResponse := fmt.Sprintf(`{"id":"valid","urls":{"small":"%s"}}`, img) 165 var testcases = []struct { 166 name string 167 path string 168 response string 169 valid bool 170 code int 171 }{ 172 { 173 name: "valid", 174 path: "/valid", 175 response: validResponse, 176 valid: true, 177 }, 178 { 179 name: "image too big", 180 path: "/too-big", 181 response: fmt.Sprintf(`{"id":"valid","urls":{"small":"%s"}}`, bigimg), 182 }, 183 { 184 name: "return-406", 185 path: "/return-406", 186 code: 406, 187 response: ` 188 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 189 <html><head> 190 <title>406 Not Acceptable</title> 191 </head><body> 192 <h1>Not Acceptable</h1> 193 <p>An appropriate representation of the requested resource /api/images/get could not be found on this server.</p> 194 Available variants: 195 <ul> 196 <li><a href="get.php">get.php</a> , type x-mapp-php5</li> 197 </ul> 198 </body></html>`, 199 }, 200 { 201 name: "no-geese-in-json", 202 path: "/no-geese-in-json", 203 response: "[]", 204 }, 205 { 206 name: "no-image-in-json", 207 path: "/no-image-in-json", 208 response: "[{}]", 209 }, 210 } 211 212 // fake server for image urls 213 pathToResponse := make(map[string]string) 214 for _, testcase := range testcases { 215 pathToResponse[testcase.path] = testcase.response 216 } 217 codes := make(map[string]int) 218 for _, tc := range testcases { 219 codes[tc.path] = tc.code 220 } 221 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 222 code := codes[r.URL.Path] 223 if code > 0 { 224 w.WriteHeader(code) 225 } 226 if r, ok := pathToResponse[r.URL.Path]; ok { 227 io.WriteString(w, r) 228 } else { 229 io.WriteString(w, validResponse) 230 } 231 })) 232 defer ts.Close() 233 234 // github fake client 235 fc := fakegithub.NewFakeClient() 236 fc.IssueComments = make(map[int][]github.IssueComment) 237 238 // run test for each case 239 for _, testcase := range testcases { 240 fakehonk := &realGaggle{url: ts.URL + testcase.path} 241 goose, err := fakehonk.readGoose() 242 if testcase.valid && err != nil { 243 t.Errorf("For case %s, didn't expect error: %v", testcase.name, err) 244 } else if !testcase.valid && err == nil { 245 t.Errorf("For case %s, expected error, received goose: %s", testcase.name, goose) 246 } else if testcase.valid && goose == "" { 247 t.Errorf("For case %s, got an empty goose", testcase.name) 248 } 249 } 250 251 // fully test handling a comment 252 comment := "/honk" 253 254 e := &github.GenericCommentEvent{ 255 Action: github.GenericCommentActionCreated, 256 Body: comment, 257 Number: 5, 258 IssueState: "open", 259 } 260 if err := handle(fc, logrus.WithField("plugin", pluginName), e, &realGaggle{url: ts.URL + "/?format=json"}, func() {}); err != nil { 261 t.Errorf("didn't expect error: %v", err) 262 return 263 } 264 if len(fc.IssueComments[5]) != 1 { 265 t.Error("should have commented.") 266 return 267 } 268 if c := fc.IssueComments[5][0]; !strings.Contains(c.Body, img) { 269 t.Errorf("missing image url: %s from comment: %v", img, c) 270 } 271 272 } 273 274 // Small, unit tests 275 func TestGeese(t *testing.T) { 276 var testcases = []struct { 277 name string 278 action github.GenericCommentEventAction 279 body string 280 state string 281 pr bool 282 shouldComment bool 283 shouldError bool 284 }{ 285 { 286 name: "ignore edited comment", 287 state: "open", 288 action: github.GenericCommentActionEdited, 289 body: "/honk", 290 shouldComment: false, 291 shouldError: false, 292 }, 293 { 294 name: "leave goose on pr", 295 state: "open", 296 action: github.GenericCommentActionCreated, 297 body: "/honk", 298 pr: true, 299 shouldComment: true, 300 shouldError: false, 301 }, 302 { 303 name: "leave goose on issue", 304 state: "open", 305 action: github.GenericCommentActionCreated, 306 body: "/honk", 307 shouldComment: true, 308 shouldError: false, 309 }, 310 { 311 name: "leave goose on issue, trailing space", 312 state: "open", 313 action: github.GenericCommentActionCreated, 314 body: "/honk \r", 315 shouldComment: true, 316 shouldError: false, 317 }, 318 } 319 for _, tc := range testcases { 320 fc := fakegithub.NewFakeClient() 321 fc.IssueComments = make(map[int][]github.IssueComment) 322 e := &github.GenericCommentEvent{ 323 Action: tc.action, 324 Body: tc.body, 325 Number: 5, 326 IssueState: tc.state, 327 IsPR: tc.pr, 328 } 329 err := handle(fc, logrus.WithField("plugin", pluginName), e, fakeGaggle("thegoose"), func() {}) 330 if !tc.shouldError && err != nil { 331 t.Errorf("%s: didn't expect error: %v", tc.name, err) 332 continue 333 } else if tc.shouldError && err == nil { 334 t.Errorf("%s: expected an error to occur", tc.name) 335 continue 336 } 337 if tc.shouldComment && len(fc.IssueComments[5]) != 1 { 338 t.Errorf("%s: should have commented.", tc.name) 339 } else if tc.shouldComment { 340 shouldImage := !tc.shouldError 341 body := fc.IssueComments[5][0].Body 342 hasImage := strings.Contains(body, "![") 343 if hasImage && !shouldImage { 344 t.Errorf("%s: unexpected image in %s", tc.name, body) 345 } else if !hasImage && shouldImage { 346 t.Errorf("%s: no image in %s", tc.name, body) 347 } 348 } else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 { 349 t.Errorf("%s: should not have commented.", tc.name) 350 } 351 } 352 }