github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/deck/main_test.go (about) 1 /* 2 Copyright 2016 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 "bufio" 21 "bytes" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 "io/ioutil" 27 "net/http" 28 "net/http/httptest" 29 "net/url" 30 "reflect" 31 "strconv" 32 "testing" 33 "time" 34 35 "sigs.k8s.io/yaml" 36 37 "k8s.io/test-infra/prow/config" 38 "k8s.io/test-infra/prow/kube" 39 "k8s.io/test-infra/prow/pluginhelp" 40 "k8s.io/test-infra/prow/tide" 41 "k8s.io/test-infra/prow/tide/history" 42 ) 43 44 func TestOptions_Validate(t *testing.T) { 45 var testCases = []struct { 46 name string 47 input options 48 expectedErr bool 49 }{ 50 { 51 name: "minimal set ok", 52 input: options{ 53 configPath: "test", 54 }, 55 expectedErr: false, 56 }, 57 { 58 name: "missing configpath", 59 input: options{}, 60 expectedErr: true, 61 }, 62 { 63 name: "ok with oauth", 64 input: options{ 65 configPath: "test", 66 oauthURL: "website", 67 githubOAuthConfigFile: "something", 68 cookieSecretFile: "yum", 69 }, 70 expectedErr: false, 71 }, 72 { 73 name: "missing github config with oauth", 74 input: options{ 75 configPath: "test", 76 oauthURL: "website", 77 cookieSecretFile: "yum", 78 }, 79 expectedErr: true, 80 }, 81 { 82 name: "missing cookie with oauth", 83 input: options{ 84 configPath: "test", 85 oauthURL: "website", 86 githubOAuthConfigFile: "something", 87 }, 88 expectedErr: true, 89 }, 90 } 91 92 for _, testCase := range testCases { 93 err := testCase.input.Validate() 94 if testCase.expectedErr && err == nil { 95 t.Errorf("%s: expected an error but got none", testCase.name) 96 } 97 if !testCase.expectedErr && err != nil { 98 t.Errorf("%s: expected no error but got one: %v", testCase.name, err) 99 } 100 } 101 } 102 103 type flc int 104 105 func (f flc) GetJobLog(job, id string) ([]byte, error) { 106 if job == "job" && id == "123" { 107 return []byte("hello"), nil 108 } 109 return nil, errors.New("muahaha") 110 } 111 112 func TestHandleLog(t *testing.T) { 113 var testcases = []struct { 114 name string 115 path string 116 code int 117 }{ 118 { 119 name: "no job name", 120 path: "", 121 code: http.StatusBadRequest, 122 }, 123 { 124 name: "job but no id", 125 path: "?job=job", 126 code: http.StatusBadRequest, 127 }, 128 { 129 name: "id but no job", 130 path: "?id=123", 131 code: http.StatusBadRequest, 132 }, 133 { 134 name: "id and job, found", 135 path: "?job=job&id=123", 136 code: http.StatusOK, 137 }, 138 { 139 name: "id and job, not found", 140 path: "?job=ohno&id=123", 141 code: http.StatusNotFound, 142 }, 143 } 144 handler := handleLog(flc(0)) 145 for _, tc := range testcases { 146 req, err := http.NewRequest(http.MethodGet, "", nil) 147 if err != nil { 148 t.Fatalf("Error making request: %v", err) 149 } 150 u, err := url.Parse(tc.path) 151 if err != nil { 152 t.Fatalf("Error parsing URL: %v", err) 153 } 154 var follow = false 155 if ok, _ := strconv.ParseBool(u.Query().Get("follow")); ok { 156 follow = true 157 } 158 req.URL = u 159 rr := httptest.NewRecorder() 160 handler.ServeHTTP(rr, req) 161 if rr.Code != tc.code { 162 t.Errorf("Wrong error code. Got %v, want %v", rr.Code, tc.code) 163 } else if rr.Code == http.StatusOK { 164 if follow { 165 //wait a little to get the chunks 166 time.Sleep(2 * time.Millisecond) 167 reader := bufio.NewReader(rr.Body) 168 var buf bytes.Buffer 169 for { 170 line, err := reader.ReadBytes('\n') 171 if err == io.EOF { 172 break 173 } 174 if err != nil { 175 t.Fatalf("Expecting reply with content but got error: %v", err) 176 } 177 buf.Write(line) 178 } 179 if !bytes.Contains(buf.Bytes(), []byte("hello")) { 180 t.Errorf("Unexpected body: got %s.", buf.String()) 181 } 182 } else { 183 resp := rr.Result() 184 defer resp.Body.Close() 185 if body, err := ioutil.ReadAll(resp.Body); err != nil { 186 t.Errorf("Error reading response body: %v", err) 187 } else if string(body) != "hello" { 188 t.Errorf("Unexpected body: got %s.", string(body)) 189 } 190 } 191 } 192 } 193 } 194 195 type fpjc kube.ProwJob 196 197 func (fc *fpjc) GetProwJob(name string) (kube.ProwJob, error) { 198 return kube.ProwJob(*fc), nil 199 } 200 201 // TestRerun just checks that the result can be unmarshaled properly, has an 202 // updated status, and has equal spec. 203 func TestRerun(t *testing.T) { 204 fc := fpjc(kube.ProwJob{ 205 Spec: kube.ProwJobSpec{ 206 Job: "whoa", 207 Type: kube.PresubmitJob, 208 Refs: &kube.Refs{ 209 Org: "org", 210 Repo: "repo", 211 Pulls: []kube.Pull{ 212 {Number: 1}, 213 }, 214 }, 215 }, 216 Status: kube.ProwJobStatus{ 217 State: kube.PendingState, 218 }, 219 }) 220 handler := handleRerun(&fc) 221 req, err := http.NewRequest(http.MethodGet, "/rerun?prowjob=wowsuch", nil) 222 if err != nil { 223 t.Fatalf("Error making request: %v", err) 224 } 225 rr := httptest.NewRecorder() 226 handler.ServeHTTP(rr, req) 227 if rr.Code != http.StatusOK { 228 t.Fatalf("Bad error code: %d", rr.Code) 229 } 230 resp := rr.Result() 231 defer resp.Body.Close() 232 body, err := ioutil.ReadAll(resp.Body) 233 if err != nil { 234 t.Fatalf("Error reading response body: %v", err) 235 } 236 var res kube.ProwJob 237 if err := yaml.Unmarshal(body, &res); err != nil { 238 t.Fatalf("Error unmarshaling: %v", err) 239 } 240 if res.Spec.Job != "whoa" { 241 t.Errorf("Wrong job, expected \"whoa\", got \"%s\"", res.Spec.Job) 242 } 243 if res.Status.State != kube.TriggeredState { 244 t.Errorf("Wrong state, expected \"%v\", got \"%v\"", kube.TriggeredState, res.Status.State) 245 } 246 } 247 248 func TestTide(t *testing.T) { 249 s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 250 pools := []tide.Pool{ 251 { 252 Org: "o", 253 }, 254 } 255 b, err := json.Marshal(pools) 256 if err != nil { 257 t.Fatalf("Marshaling: %v", err) 258 } 259 fmt.Fprintf(w, string(b)) 260 })) 261 ca := &config.Agent{} 262 ca.Set(&config.Config{ 263 ProwConfig: config.ProwConfig{ 264 Tide: config.Tide{ 265 Queries: []config.TideQuery{ 266 {Repos: []string{"kubernetes/test-infra"}}, 267 }, 268 }, 269 }, 270 }) 271 ta := tideAgent{ 272 path: s.URL, 273 updatePeriod: func() time.Duration { return time.Minute }, 274 } 275 if err := ta.updatePools(); err != nil { 276 t.Fatalf("Updating: %v", err) 277 } 278 if len(ta.pools) != 1 { 279 t.Fatalf("Wrong number of pools. Got %d, expected 1 in %v", len(ta.pools), ta.pools) 280 } 281 if ta.pools[0].Org != "o" { 282 t.Errorf("Wrong org in pool. Got %s, expected o in %v", ta.pools[0].Org, ta.pools) 283 } 284 handler := handleTidePools(ca, &ta) 285 req, err := http.NewRequest(http.MethodGet, "/tide.js", nil) 286 if err != nil { 287 t.Fatalf("Error making request: %v", err) 288 } 289 rr := httptest.NewRecorder() 290 handler.ServeHTTP(rr, req) 291 if rr.Code != http.StatusOK { 292 t.Fatalf("Bad error code: %d", rr.Code) 293 } 294 resp := rr.Result() 295 defer resp.Body.Close() 296 body, err := ioutil.ReadAll(resp.Body) 297 if err != nil { 298 t.Fatalf("Error reading response body: %v", err) 299 } 300 res := tidePools{} 301 if err := json.Unmarshal(body, &res); err != nil { 302 t.Fatalf("Error unmarshaling: %v", err) 303 } 304 if len(res.Pools) != 1 { 305 t.Fatalf("Wrong number of pools. Got %d, expected 1 in %v", len(res.Pools), res.Pools) 306 } 307 if res.Pools[0].Org != "o" { 308 t.Errorf("Wrong org in pool. Got %s, expected o in %v", res.Pools[0].Org, res.Pools) 309 } 310 if len(res.Queries) != 1 { 311 t.Fatalf("Wrong number of pools. Got %d, expected 1 in %v", len(res.Queries), res.Queries) 312 } 313 if expected := "is:pr state:open repo:\"kubernetes/test-infra\""; res.Queries[0] != expected { 314 t.Errorf("Wrong query. Got %s, expected %s", res.Queries[0], expected) 315 } 316 } 317 318 func TestTideHistory(t *testing.T) { 319 testHist := map[string][]history.Record{ 320 "o/r:b": { 321 {Action: "MERGE"}, {Action: "TRIGGER"}, 322 }, 323 } 324 s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 325 b, err := json.Marshal(testHist) 326 if err != nil { 327 t.Fatalf("Marshaling: %v", err) 328 } 329 fmt.Fprintf(w, string(b)) 330 })) 331 332 ta := tideAgent{ 333 path: s.URL, 334 updatePeriod: func() time.Duration { return time.Minute }, 335 } 336 if err := ta.updateHistory(); err != nil { 337 t.Fatalf("Updating: %v", err) 338 } 339 if !reflect.DeepEqual(ta.history, testHist) { 340 t.Fatalf("Expected tideAgent history:\n%#v\n,but got:\n%#v\n", testHist, ta.history) 341 } 342 343 handler := handleTideHistory(&ta) 344 req, err := http.NewRequest(http.MethodGet, "/tide-history.js", nil) 345 if err != nil { 346 t.Fatalf("Error making request: %v", err) 347 } 348 rr := httptest.NewRecorder() 349 handler.ServeHTTP(rr, req) 350 if rr.Code != http.StatusOK { 351 t.Fatalf("Bad error code: %d", rr.Code) 352 } 353 resp := rr.Result() 354 defer resp.Body.Close() 355 body, err := ioutil.ReadAll(resp.Body) 356 if err != nil { 357 t.Fatalf("Error reading response body: %v", err) 358 } 359 var res tideHistory 360 if err := json.Unmarshal(body, &res); err != nil { 361 t.Fatalf("Error unmarshaling: %v", err) 362 } 363 if !reflect.DeepEqual(res.History, testHist) { 364 t.Fatalf("Expected /tide-history.js:\n%#v\n,but got:\n%#v\n", testHist, res.History) 365 } 366 } 367 368 func TestHelp(t *testing.T) { 369 hitCount := 0 370 help := pluginhelp.Help{ 371 AllRepos: []string{"org/repo"}, 372 RepoPlugins: map[string][]string{"org": {"plugin"}}, 373 RepoExternalPlugins: map[string][]string{"org/repo": {"external-plugin"}}, 374 PluginHelp: map[string]pluginhelp.PluginHelp{"plugin": {Description: "plugin"}}, 375 ExternalPluginHelp: map[string]pluginhelp.PluginHelp{"external-plugin": {Description: "external-plugin"}}, 376 } 377 s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 378 hitCount++ 379 b, err := json.Marshal(help) 380 if err != nil { 381 t.Fatalf("Marshaling: %v", err) 382 } 383 fmt.Fprintf(w, string(b)) 384 })) 385 ha := &helpAgent{ 386 path: s.URL, 387 } 388 handler := handlePluginHelp(ha) 389 handleAndCheck := func() { 390 req, err := http.NewRequest(http.MethodGet, "/plugin-help.js", nil) 391 if err != nil { 392 t.Fatalf("Error making request: %v", err) 393 } 394 rr := httptest.NewRecorder() 395 handler.ServeHTTP(rr, req) 396 if rr.Code != http.StatusOK { 397 t.Fatalf("Bad error code: %d", rr.Code) 398 } 399 resp := rr.Result() 400 defer resp.Body.Close() 401 body, err := ioutil.ReadAll(resp.Body) 402 if err != nil { 403 t.Fatalf("Error reading response body: %v", err) 404 } 405 var res pluginhelp.Help 406 if err := yaml.Unmarshal(body, &res); err != nil { 407 t.Fatalf("Error unmarshaling: %v", err) 408 } 409 if !reflect.DeepEqual(help, res) { 410 t.Errorf("Invalid plugin help. Got %v, expected %v", res, help) 411 } 412 if hitCount != 1 { 413 t.Errorf("Expected fake hook endpoint to be hit once, but endpoint was hit %d times.", hitCount) 414 } 415 } 416 handleAndCheck() 417 handleAndCheck() 418 }