github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/network/gitlab_test.go (about) 1 package network 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "net/http/httptest" 10 "os" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "testing" 15 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 19 . "gitlab.com/gitlab-org/gitlab-runner/common" 20 ) 21 22 var brokenCredentials = RunnerCredentials{ 23 URL: "broken", 24 } 25 26 var brokenConfig = RunnerConfig{ 27 RunnerCredentials: brokenCredentials, 28 } 29 30 func TestClients(t *testing.T) { 31 c := NewGitLabClient() 32 c1, _ := c.getClient(&RunnerCredentials{ 33 URL: "http://test/", 34 }) 35 c2, _ := c.getClient(&RunnerCredentials{ 36 URL: "http://test2/", 37 }) 38 c4, _ := c.getClient(&RunnerCredentials{ 39 URL: "http://test/", 40 TLSCAFile: "ca_file", 41 }) 42 c5, _ := c.getClient(&RunnerCredentials{ 43 URL: "http://test/", 44 TLSCAFile: "ca_file", 45 }) 46 c6, _ := c.getClient(&RunnerCredentials{ 47 URL: "http://test/", 48 TLSCAFile: "ca_file", 49 TLSCertFile: "cert_file", 50 TLSKeyFile: "key_file", 51 }) 52 c7, _ := c.getClient(&RunnerCredentials{ 53 URL: "http://test/", 54 TLSCAFile: "ca_file", 55 TLSCertFile: "cert_file", 56 TLSKeyFile: "key_file2", 57 }) 58 c8, c8err := c.getClient(&brokenCredentials) 59 assert.NotEqual(t, c1, c2) 60 assert.NotEqual(t, c1, c4) 61 assert.Equal(t, c4, c5) 62 assert.NotEqual(t, c5, c6) 63 assert.Equal(t, c6, c7) 64 assert.Nil(t, c8) 65 assert.Error(t, c8err) 66 } 67 68 func testRegisterRunnerHandler(w http.ResponseWriter, r *http.Request, t *testing.T) { 69 if r.URL.Path != "/api/v4/runners" { 70 w.WriteHeader(http.StatusNotFound) 71 return 72 } 73 74 if r.Method != "POST" { 75 w.WriteHeader(http.StatusNotAcceptable) 76 return 77 } 78 79 body, err := ioutil.ReadAll(r.Body) 80 assert.NoError(t, err) 81 82 var req map[string]interface{} 83 err = json.Unmarshal(body, &req) 84 assert.NoError(t, err) 85 86 res := make(map[string]interface{}) 87 88 switch req["token"].(string) { 89 case "valid": 90 if req["description"].(string) != "test" { 91 w.WriteHeader(http.StatusBadRequest) 92 return 93 } 94 95 res["token"] = req["token"].(string) 96 case "invalid": 97 w.WriteHeader(http.StatusForbidden) 98 return 99 default: 100 w.WriteHeader(http.StatusBadRequest) 101 return 102 } 103 104 if r.Header.Get("Accept") != "application/json" { 105 w.WriteHeader(http.StatusBadRequest) 106 return 107 } 108 109 output, err := json.Marshal(res) 110 if err != nil { 111 w.WriteHeader(http.StatusInternalServerError) 112 return 113 } 114 115 w.Header().Set("Content-Type", "application/json") 116 w.WriteHeader(http.StatusCreated) 117 w.Write(output) 118 } 119 120 func TestRegisterRunner(t *testing.T) { 121 s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 122 testRegisterRunnerHandler(w, r, t) 123 })) 124 defer s.Close() 125 126 validToken := RunnerCredentials{ 127 URL: s.URL, 128 Token: "valid", 129 } 130 131 invalidToken := RunnerCredentials{ 132 URL: s.URL, 133 Token: "invalid", 134 } 135 136 otherToken := RunnerCredentials{ 137 URL: s.URL, 138 Token: "other", 139 } 140 141 c := NewGitLabClient() 142 143 res := c.RegisterRunner(validToken, "test", "tags", true, true) 144 if assert.NotNil(t, res) { 145 assert.Equal(t, validToken.Token, res.Token) 146 } 147 148 res = c.RegisterRunner(validToken, "invalid description", "tags", true, true) 149 assert.Nil(t, res) 150 151 res = c.RegisterRunner(invalidToken, "test", "tags", true, true) 152 assert.Nil(t, res) 153 154 res = c.RegisterRunner(otherToken, "test", "tags", true, true) 155 assert.Nil(t, res) 156 157 res = c.RegisterRunner(brokenCredentials, "test", "tags", true, true) 158 assert.Nil(t, res) 159 } 160 161 func testUnregisterRunnerHandler(w http.ResponseWriter, r *http.Request, t *testing.T) { 162 if r.URL.Path != "/api/v4/runners" { 163 w.WriteHeader(http.StatusNotFound) 164 return 165 } 166 167 if r.Method != "DELETE" { 168 w.WriteHeader(http.StatusNotAcceptable) 169 return 170 } 171 172 body, err := ioutil.ReadAll(r.Body) 173 assert.NoError(t, err) 174 175 var req map[string]interface{} 176 err = json.Unmarshal(body, &req) 177 assert.NoError(t, err) 178 179 switch req["token"].(string) { 180 case "valid": 181 w.WriteHeader(http.StatusNoContent) 182 case "invalid": 183 w.WriteHeader(http.StatusForbidden) 184 default: 185 w.WriteHeader(http.StatusBadRequest) 186 } 187 } 188 189 func TestUnregisterRunner(t *testing.T) { 190 handler := func(w http.ResponseWriter, r *http.Request) { 191 testUnregisterRunnerHandler(w, r, t) 192 } 193 194 s := httptest.NewServer(http.HandlerFunc(handler)) 195 defer s.Close() 196 197 validToken := RunnerCredentials{ 198 URL: s.URL, 199 Token: "valid", 200 } 201 202 invalidToken := RunnerCredentials{ 203 URL: s.URL, 204 Token: "invalid", 205 } 206 207 otherToken := RunnerCredentials{ 208 URL: s.URL, 209 Token: "other", 210 } 211 212 c := NewGitLabClient() 213 214 state := c.UnregisterRunner(validToken) 215 assert.True(t, state) 216 217 state = c.UnregisterRunner(invalidToken) 218 assert.False(t, state) 219 220 state = c.UnregisterRunner(otherToken) 221 assert.False(t, state) 222 223 state = c.UnregisterRunner(brokenCredentials) 224 assert.False(t, state) 225 } 226 227 func testVerifyRunnerHandler(w http.ResponseWriter, r *http.Request, t *testing.T) { 228 if r.URL.Path != "/api/v4/runners/verify" { 229 w.WriteHeader(http.StatusNotFound) 230 return 231 } 232 233 if r.Method != "POST" { 234 w.WriteHeader(http.StatusNotAcceptable) 235 return 236 } 237 238 body, err := ioutil.ReadAll(r.Body) 239 assert.NoError(t, err) 240 241 var req map[string]interface{} 242 err = json.Unmarshal(body, &req) 243 assert.NoError(t, err) 244 245 switch req["token"].(string) { 246 case "valid": 247 w.WriteHeader(http.StatusOK) // since the job id is broken, we should not find this job 248 case "invalid": 249 w.WriteHeader(http.StatusForbidden) 250 default: 251 w.WriteHeader(http.StatusBadRequest) 252 } 253 } 254 255 func TestVerifyRunner(t *testing.T) { 256 handler := func(w http.ResponseWriter, r *http.Request) { 257 testVerifyRunnerHandler(w, r, t) 258 } 259 260 s := httptest.NewServer(http.HandlerFunc(handler)) 261 defer s.Close() 262 263 validToken := RunnerCredentials{ 264 URL: s.URL, 265 Token: "valid", 266 } 267 268 invalidToken := RunnerCredentials{ 269 URL: s.URL, 270 Token: "invalid", 271 } 272 273 otherToken := RunnerCredentials{ 274 URL: s.URL, 275 Token: "other", 276 } 277 278 c := NewGitLabClient() 279 280 state := c.VerifyRunner(validToken) 281 assert.True(t, state) 282 283 state = c.VerifyRunner(invalidToken) 284 assert.False(t, state) 285 286 state = c.VerifyRunner(otherToken) 287 assert.True(t, state, "in other cases where we can't explicitly say that runner is valid we say that it's") 288 289 state = c.VerifyRunner(brokenCredentials) 290 assert.True(t, state, "in other cases where we can't explicitly say that runner is valid we say that it's") 291 } 292 293 func getRequestJobResponse() (res map[string]interface{}) { 294 jobToken := "job-token" 295 296 res = make(map[string]interface{}) 297 res["id"] = 10 298 res["token"] = jobToken 299 res["allow_git_fetch"] = false 300 301 jobInfo := make(map[string]interface{}) 302 jobInfo["name"] = "test-job" 303 jobInfo["stage"] = "test" 304 jobInfo["project_id"] = 123 305 jobInfo["project_name"] = "test-project" 306 res["job_info"] = jobInfo 307 308 gitInfo := make(map[string]interface{}) 309 gitInfo["repo_url"] = "https://gitlab-ci-token:testTokenHere1234@gitlab.example.com/test/test-project.git" 310 gitInfo["ref"] = "master" 311 gitInfo["sha"] = "abcdef123456" 312 gitInfo["before_sha"] = "654321fedcba" 313 gitInfo["ref_type"] = "branch" 314 res["git_info"] = gitInfo 315 316 runnerInfo := make(map[string]interface{}) 317 runnerInfo["timeout"] = 3600 318 res["runner_info"] = runnerInfo 319 320 variables := make([]map[string]interface{}, 1) 321 variables[0] = make(map[string]interface{}) 322 variables[0]["key"] = "CI_REF_NAME" 323 variables[0]["value"] = "master" 324 variables[0]["public"] = true 325 variables[0]["file"] = true 326 res["variables"] = variables 327 328 steps := make([]map[string]interface{}, 2) 329 steps[0] = make(map[string]interface{}) 330 steps[0]["name"] = "script" 331 steps[0]["script"] = []string{"date", "ls -ls"} 332 steps[0]["timeout"] = 3600 333 steps[0]["when"] = "on_success" 334 steps[0]["allow_failure"] = false 335 steps[1] = make(map[string]interface{}) 336 steps[1]["name"] = "after_script" 337 steps[1]["script"] = []string{"ls -ls"} 338 steps[1]["timeout"] = 3600 339 steps[1]["when"] = "always" 340 steps[1]["allow_failure"] = true 341 res["steps"] = steps 342 343 image := make(map[string]interface{}) 344 image["name"] = "ruby:2.0" 345 image["entrypoint"] = []string{"/bin/sh"} 346 res["image"] = image 347 348 services := make([]map[string]interface{}, 2) 349 services[0] = make(map[string]interface{}) 350 services[0]["name"] = "postgresql:9.5" 351 services[0]["entrypoint"] = []string{"/bin/sh"} 352 services[0]["command"] = []string{"sleep", "30"} 353 services[0]["alias"] = "db-pg" 354 services[1] = make(map[string]interface{}) 355 services[1]["name"] = "mysql:5.6" 356 services[1]["alias"] = "db-mysql" 357 res["services"] = services 358 359 artifacts := make([]map[string]interface{}, 1) 360 artifacts[0] = make(map[string]interface{}) 361 artifacts[0]["name"] = "artifact.zip" 362 artifacts[0]["untracked"] = false 363 artifacts[0]["paths"] = []string{"out/*"} 364 artifacts[0]["when"] = "always" 365 artifacts[0]["expire_in"] = "7d" 366 res["artifacts"] = artifacts 367 368 cache := make([]map[string]interface{}, 1) 369 cache[0] = make(map[string]interface{}) 370 cache[0]["key"] = "$CI_COMMIT_REF" 371 cache[0]["untracked"] = false 372 cache[0]["paths"] = []string{"vendor/*"} 373 cache[0]["policy"] = "push" 374 res["cache"] = cache 375 376 credentials := make([]map[string]interface{}, 1) 377 credentials[0] = make(map[string]interface{}) 378 credentials[0]["type"] = "Registry" 379 credentials[0]["url"] = "http://registry.gitlab.example.com/" 380 credentials[0]["username"] = "gitlab-ci-token" 381 credentials[0]["password"] = jobToken 382 res["credentials"] = credentials 383 384 dependencies := make([]map[string]interface{}, 1) 385 dependencies[0] = make(map[string]interface{}) 386 dependencies[0]["id"] = 9 387 dependencies[0]["name"] = "other-job" 388 dependencies[0]["token"] = "other-job-token" 389 artifactsFile0 := make(map[string]interface{}) 390 artifactsFile0["filename"] = "binaries.zip" 391 artifactsFile0["size"] = 13631488 392 dependencies[0]["artifacts_file"] = artifactsFile0 393 res["dependencies"] = dependencies 394 395 return 396 } 397 398 func testRequestJobHandler(w http.ResponseWriter, r *http.Request, t *testing.T) { 399 if r.URL.Path != "/api/v4/jobs/request" { 400 w.WriteHeader(http.StatusNotFound) 401 return 402 } 403 404 if r.Method != "POST" { 405 w.WriteHeader(http.StatusNotAcceptable) 406 return 407 } 408 409 body, err := ioutil.ReadAll(r.Body) 410 assert.NoError(t, err) 411 412 var req map[string]interface{} 413 err = json.Unmarshal(body, &req) 414 assert.NoError(t, err) 415 416 switch req["token"].(string) { 417 case "valid": 418 case "no-jobs": 419 w.Header().Add("X-GitLab-Last-Update", "a nice timestamp") 420 w.WriteHeader(http.StatusNoContent) 421 return 422 case "invalid": 423 w.WriteHeader(http.StatusForbidden) 424 return 425 default: 426 w.WriteHeader(http.StatusBadRequest) 427 return 428 } 429 430 if r.Header.Get("Accept") != "application/json" { 431 w.WriteHeader(http.StatusBadRequest) 432 return 433 } 434 435 output, err := json.Marshal(getRequestJobResponse()) 436 if err != nil { 437 w.WriteHeader(http.StatusInternalServerError) 438 return 439 } 440 441 w.Header().Set("Content-Type", "application/json") 442 w.WriteHeader(http.StatusCreated) 443 w.Write(output) 444 t.Logf("JobRequest response: %s\n", output) 445 } 446 447 func TestRequestJob(t *testing.T) { 448 s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 449 testRequestJobHandler(w, r, t) 450 })) 451 defer s.Close() 452 453 validToken := RunnerConfig{ 454 RunnerCredentials: RunnerCredentials{ 455 URL: s.URL, 456 Token: "valid", 457 }, 458 } 459 460 noJobsToken := RunnerConfig{ 461 RunnerCredentials: RunnerCredentials{ 462 URL: s.URL, 463 Token: "no-jobs", 464 }, 465 } 466 467 invalidToken := RunnerConfig{ 468 RunnerCredentials: RunnerCredentials{ 469 URL: s.URL, 470 Token: "invalid", 471 }, 472 } 473 474 c := NewGitLabClient() 475 476 res, ok := c.RequestJob(validToken) 477 if assert.NotNil(t, res) { 478 assert.NotEmpty(t, res.ID) 479 } 480 assert.True(t, ok) 481 482 assert.Equal(t, "ruby:2.0", res.Image.Name) 483 assert.Equal(t, []string{"/bin/sh"}, res.Image.Entrypoint) 484 require.Len(t, res.Services, 2) 485 assert.Equal(t, "postgresql:9.5", res.Services[0].Name) 486 assert.Equal(t, []string{"/bin/sh"}, res.Services[0].Entrypoint) 487 assert.Equal(t, []string{"sleep", "30"}, res.Services[0].Command) 488 assert.Equal(t, "db-pg", res.Services[0].Alias) 489 assert.Equal(t, "mysql:5.6", res.Services[1].Name) 490 assert.Equal(t, "db-mysql", res.Services[1].Alias) 491 492 assert.Empty(t, c.getLastUpdate(&noJobsToken.RunnerCredentials), "Last-Update should not be set") 493 res, ok = c.RequestJob(noJobsToken) 494 assert.Nil(t, res) 495 assert.True(t, ok, "If no jobs, runner is healthy") 496 assert.Equal(t, c.getLastUpdate(&noJobsToken.RunnerCredentials), "a nice timestamp", "Last-Update should be set") 497 498 res, ok = c.RequestJob(invalidToken) 499 assert.Nil(t, res) 500 assert.False(t, ok, "If token is invalid, the runner is unhealthy") 501 502 res, ok = c.RequestJob(brokenConfig) 503 assert.Nil(t, res) 504 assert.False(t, ok) 505 } 506 507 func setStateForUpdateJobHandlerResponse(w http.ResponseWriter, req map[string]interface{}) { 508 switch req["state"].(string) { 509 case "running": 510 w.WriteHeader(http.StatusOK) 511 case "failed": 512 failureReason, ok := req["failure_reason"].(string) 513 if ok && (JobFailureReason(failureReason) == ScriptFailure || 514 JobFailureReason(failureReason) == RunnerSystemFailure) { 515 w.WriteHeader(http.StatusOK) 516 } else { 517 w.WriteHeader(http.StatusBadRequest) 518 } 519 case "forbidden": 520 w.WriteHeader(http.StatusForbidden) 521 default: 522 w.WriteHeader(http.StatusBadRequest) 523 } 524 } 525 526 func testUpdateJobHandler(w http.ResponseWriter, r *http.Request, t *testing.T) { 527 if r.URL.Path != "/api/v4/jobs/10" { 528 w.WriteHeader(http.StatusNotFound) 529 return 530 } 531 532 if r.Method != "PUT" { 533 w.WriteHeader(http.StatusNotAcceptable) 534 return 535 } 536 537 body, err := ioutil.ReadAll(r.Body) 538 assert.NoError(t, err) 539 540 var req map[string]interface{} 541 err = json.Unmarshal(body, &req) 542 assert.NoError(t, err) 543 544 assert.Equal(t, "token", req["token"]) 545 assert.Equal(t, "trace", req["trace"]) 546 547 setStateForUpdateJobHandlerResponse(w, req) 548 } 549 550 func TestUpdateJob(t *testing.T) { 551 handler := func(w http.ResponseWriter, r *http.Request) { 552 testUpdateJobHandler(w, r, t) 553 } 554 555 s := httptest.NewServer(http.HandlerFunc(handler)) 556 defer s.Close() 557 558 config := RunnerConfig{ 559 RunnerCredentials: RunnerCredentials{ 560 URL: s.URL, 561 }, 562 } 563 564 jobCredentials := &JobCredentials{ 565 Token: "token", 566 } 567 568 trace := "trace" 569 c := NewGitLabClient() 570 571 var state UpdateState 572 573 state = c.UpdateJob(config, jobCredentials, UpdateJobInfo{ID: 10, State: "running", Trace: &trace, FailureReason: ""}) 574 assert.Equal(t, UpdateSucceeded, state, "Update should continue when running") 575 576 state = c.UpdateJob(config, jobCredentials, UpdateJobInfo{ID: 10, State: "forbidden", Trace: &trace, FailureReason: ""}) 577 assert.Equal(t, UpdateAbort, state, "Update should be aborted if the state is forbidden") 578 579 state = c.UpdateJob(config, jobCredentials, UpdateJobInfo{ID: 10, State: "other", Trace: &trace, FailureReason: ""}) 580 assert.Equal(t, UpdateFailed, state, "Update should fail for badly formatted request") 581 582 state = c.UpdateJob(config, jobCredentials, UpdateJobInfo{ID: 4, State: "state", Trace: &trace, FailureReason: ""}) 583 assert.Equal(t, UpdateAbort, state, "Update should abort for unknown job") 584 585 state = c.UpdateJob(brokenConfig, jobCredentials, UpdateJobInfo{ID: 4, State: "state", Trace: &trace, FailureReason: ""}) 586 assert.Equal(t, UpdateAbort, state) 587 588 state = c.UpdateJob(config, jobCredentials, UpdateJobInfo{ID: 10, State: "failed", Trace: &trace, FailureReason: "script_failure"}) 589 assert.Equal(t, UpdateSucceeded, state, "Update should continue when running") 590 591 state = c.UpdateJob(config, jobCredentials, UpdateJobInfo{ID: 10, State: "failed", Trace: &trace, FailureReason: "unknown_failure_reason"}) 592 assert.Equal(t, UpdateFailed, state, "Update should fail for badly formatted request") 593 594 state = c.UpdateJob(config, jobCredentials, UpdateJobInfo{ID: 10, State: "failed", Trace: &trace, FailureReason: ""}) 595 assert.Equal(t, UpdateFailed, state, "Update should fail for badly formatted request") 596 } 597 598 var patchToken = "token" 599 var patchTraceString = "trace trace trace" 600 601 func getPatchServer(t *testing.T, handler func(w http.ResponseWriter, r *http.Request, body string, offset, limit int)) (*httptest.Server, *GitLabClient, RunnerConfig) { 602 patchHandler := func(w http.ResponseWriter, r *http.Request) { 603 if r.URL.Path != "/api/v4/jobs/1/trace" { 604 w.WriteHeader(http.StatusNotFound) 605 return 606 } 607 608 if r.Method != "PATCH" { 609 w.WriteHeader(http.StatusNotAcceptable) 610 return 611 } 612 613 assert.Equal(t, patchToken, r.Header.Get("JOB-TOKEN")) 614 615 body, err := ioutil.ReadAll(r.Body) 616 assert.NoError(t, err) 617 618 contentRange := r.Header.Get("Content-Range") 619 ranges := strings.Split(contentRange, "-") 620 621 offset, err := strconv.Atoi(ranges[0]) 622 assert.NoError(t, err) 623 624 limit, err := strconv.Atoi(ranges[1]) 625 assert.NoError(t, err) 626 627 handler(w, r, string(body), offset, limit) 628 } 629 630 server := httptest.NewServer(http.HandlerFunc(patchHandler)) 631 632 config := RunnerConfig{ 633 RunnerCredentials: RunnerCredentials{ 634 URL: server.URL, 635 }, 636 } 637 638 return server, NewGitLabClient(), config 639 } 640 641 func getTracePatch(traceString string, offset int) *tracePatch { 642 trace := bytes.Buffer{} 643 trace.WriteString(traceString) 644 tracePatch, _ := newTracePatch(trace, offset) 645 646 return tracePatch 647 } 648 649 func TestUnknownPatchTrace(t *testing.T) { 650 handler := func(w http.ResponseWriter, r *http.Request, body string, offset, limit int) { 651 w.WriteHeader(http.StatusNotFound) 652 } 653 654 server, client, config := getPatchServer(t, handler) 655 defer server.Close() 656 657 tracePatch := getTracePatch(patchTraceString, 0) 658 state := client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 659 assert.Equal(t, UpdateNotFound, state) 660 } 661 662 func TestForbiddenPatchTrace(t *testing.T) { 663 handler := func(w http.ResponseWriter, r *http.Request, body string, offset, limit int) { 664 w.WriteHeader(http.StatusForbidden) 665 } 666 667 server, client, config := getPatchServer(t, handler) 668 defer server.Close() 669 670 tracePatch := getTracePatch(patchTraceString, 0) 671 state := client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 672 assert.Equal(t, UpdateAbort, state) 673 } 674 675 func TestPatchTrace(t *testing.T) { 676 handler := func(w http.ResponseWriter, r *http.Request, body string, offset, limit int) { 677 assert.Equal(t, patchTraceString[offset:limit], body) 678 679 w.WriteHeader(http.StatusAccepted) 680 } 681 682 server, client, config := getPatchServer(t, handler) 683 defer server.Close() 684 685 tracePatch := getTracePatch(patchTraceString, 0) 686 state := client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 687 assert.Equal(t, UpdateSucceeded, state) 688 689 tracePatch = getTracePatch(patchTraceString, 3) 690 state = client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 691 assert.Equal(t, UpdateSucceeded, state) 692 693 tracePatch = getTracePatch(patchTraceString[:10], 3) 694 state = client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 695 assert.Equal(t, UpdateSucceeded, state) 696 } 697 698 func TestRangeMismatchPatchTrace(t *testing.T) { 699 handler := func(w http.ResponseWriter, r *http.Request, body string, offset, limit int) { 700 if offset > 10 { 701 w.Header().Set("Range", "0-10") 702 w.WriteHeader(http.StatusRequestedRangeNotSatisfiable) 703 } 704 705 w.WriteHeader(http.StatusAccepted) 706 } 707 708 server, client, config := getPatchServer(t, handler) 709 defer server.Close() 710 711 tracePatch := getTracePatch(patchTraceString, 11) 712 state := client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 713 assert.Equal(t, UpdateRangeMismatch, state) 714 715 tracePatch = getTracePatch(patchTraceString, 15) 716 state = client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 717 assert.Equal(t, UpdateRangeMismatch, state) 718 719 tracePatch = getTracePatch(patchTraceString, 5) 720 state = client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 721 assert.Equal(t, UpdateSucceeded, state) 722 } 723 724 func TestResendPatchTrace(t *testing.T) { 725 handler := func(w http.ResponseWriter, r *http.Request, body string, offset, limit int) { 726 if offset > 10 { 727 w.Header().Set("Range", "0-10") 728 w.WriteHeader(http.StatusRequestedRangeNotSatisfiable) 729 } 730 731 w.WriteHeader(http.StatusAccepted) 732 } 733 734 server, client, config := getPatchServer(t, handler) 735 defer server.Close() 736 737 tracePatch := getTracePatch(patchTraceString, 11) 738 state := client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 739 assert.Equal(t, UpdateRangeMismatch, state) 740 741 state = client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 742 assert.Equal(t, UpdateSucceeded, state) 743 } 744 745 // We've had a situation where the same job was triggered second time by GItLab. In GitLab the job trace 746 // was 17041 bytes long while the repeated job trace was only 66 bytes long. We've had a `RangeMismatch` 747 // response, so the offset was updated (to 17041) and `client.PatchTrace` was repeated, at it was planned. 748 // Unfortunately the `tracePatch` struct was not resistant to a situation when the offset is set to a 749 // value bigger than trace's length. This test simulates such situation. 750 func TestResendDoubledJobPatchTrace(t *testing.T) { 751 handler := func(w http.ResponseWriter, r *http.Request, body string, offset, limit int) { 752 if offset > 10 { 753 w.Header().Set("Range", "0-100") 754 w.WriteHeader(http.StatusRequestedRangeNotSatisfiable) 755 } 756 757 w.WriteHeader(http.StatusAccepted) 758 } 759 760 server, client, config := getPatchServer(t, handler) 761 defer server.Close() 762 763 tracePatch := getTracePatch(patchTraceString, 11) 764 state := client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 765 assert.Equal(t, UpdateRangeMismatch, state) 766 assert.False(t, tracePatch.ValidateRange()) 767 } 768 769 func TestJobFailedStatePatchTrace(t *testing.T) { 770 handler := func(w http.ResponseWriter, r *http.Request, body string, offset, limit int) { 771 w.Header().Set("Job-Status", "failed") 772 w.WriteHeader(http.StatusAccepted) 773 } 774 775 server, client, config := getPatchServer(t, handler) 776 defer server.Close() 777 778 tracePatch := getTracePatch(patchTraceString, 0) 779 state := client.PatchTrace(config, &JobCredentials{ID: 1, Token: patchToken}, tracePatch) 780 assert.Equal(t, UpdateAbort, state) 781 } 782 783 func testArtifactsUploadHandler(w http.ResponseWriter, r *http.Request, t *testing.T) { 784 if r.URL.Path != "/api/v4/jobs/10/artifacts" { 785 w.WriteHeader(http.StatusNotFound) 786 return 787 } 788 789 if r.Method != "POST" { 790 w.WriteHeader(http.StatusNotAcceptable) 791 return 792 } 793 794 if r.Header.Get("JOB-TOKEN") != "token" { 795 w.WriteHeader(http.StatusForbidden) 796 return 797 } 798 799 file, _, err := r.FormFile("file") 800 if err != nil { 801 w.WriteHeader(http.StatusBadRequest) 802 return 803 } 804 805 body, err := ioutil.ReadAll(file) 806 assert.NoError(t, err) 807 808 if string(body) != "content" { 809 w.WriteHeader(http.StatusRequestEntityTooLarge) 810 } else { 811 w.WriteHeader(http.StatusCreated) 812 } 813 } 814 815 func TestArtifactsUpload(t *testing.T) { 816 handler := func(w http.ResponseWriter, r *http.Request) { 817 testArtifactsUploadHandler(w, r, t) 818 } 819 820 s := httptest.NewServer(http.HandlerFunc(handler)) 821 defer s.Close() 822 823 config := JobCredentials{ 824 ID: 10, 825 URL: s.URL, 826 Token: "token", 827 } 828 invalidToken := JobCredentials{ 829 ID: 10, 830 URL: s.URL, 831 Token: "invalid-token", 832 } 833 834 tempFile, err := ioutil.TempFile("", "artifacts") 835 assert.NoError(t, err) 836 defer tempFile.Close() 837 defer os.Remove(tempFile.Name()) 838 839 c := NewGitLabClient() 840 841 fmt.Fprint(tempFile, "content") 842 state := c.UploadArtifacts(config, tempFile.Name()) 843 assert.Equal(t, UploadSucceeded, state, "Artifacts should be uploaded") 844 845 fmt.Fprint(tempFile, "too large") 846 state = c.UploadArtifacts(config, tempFile.Name()) 847 assert.Equal(t, UploadTooLarge, state, "Artifacts should be not uploaded, because of too large archive") 848 849 state = c.UploadArtifacts(config, "not/existing/file") 850 assert.Equal(t, UploadFailed, state, "Artifacts should fail to be uploaded") 851 852 state = c.UploadArtifacts(invalidToken, tempFile.Name()) 853 assert.Equal(t, UploadForbidden, state, "Artifacts should be rejected if invalid token") 854 } 855 856 func testArtifactsDownloadHandler(w http.ResponseWriter, r *http.Request, t *testing.T) { 857 if r.URL.Path != "/api/v4/jobs/10/artifacts" { 858 w.WriteHeader(http.StatusNotFound) 859 return 860 } 861 862 if r.Method != "GET" { 863 w.WriteHeader(http.StatusNotAcceptable) 864 return 865 } 866 867 if r.Header.Get("JOB-TOKEN") != "token" { 868 w.WriteHeader(http.StatusForbidden) 869 return 870 } 871 872 w.WriteHeader(http.StatusOK) 873 w.Write(bytes.NewBufferString("Test artifact file content").Bytes()) 874 } 875 876 func TestArtifactsDownload(t *testing.T) { 877 handler := func(w http.ResponseWriter, r *http.Request) { 878 testArtifactsDownloadHandler(w, r, t) 879 } 880 881 s := httptest.NewServer(http.HandlerFunc(handler)) 882 defer s.Close() 883 884 credentials := JobCredentials{ 885 ID: 10, 886 URL: s.URL, 887 Token: "token", 888 } 889 invalidTokenCredentials := JobCredentials{ 890 ID: 10, 891 URL: s.URL, 892 Token: "invalid-token", 893 } 894 fileNotFoundTokenCredentials := JobCredentials{ 895 ID: 11, 896 URL: s.URL, 897 Token: "token", 898 } 899 900 c := NewGitLabClient() 901 902 tempDir, err := ioutil.TempDir("", "artifacts") 903 assert.NoError(t, err) 904 905 artifactsFileName := filepath.Join(tempDir, "downloaded-artifact") 906 defer os.Remove(artifactsFileName) 907 908 state := c.DownloadArtifacts(credentials, artifactsFileName) 909 assert.Equal(t, DownloadSucceeded, state, "Artifacts should be downloaded") 910 911 state = c.DownloadArtifacts(invalidTokenCredentials, artifactsFileName) 912 assert.Equal(t, DownloadForbidden, state, "Artifacts should be not downloaded if invalid token is used") 913 914 state = c.DownloadArtifacts(fileNotFoundTokenCredentials, artifactsFileName) 915 assert.Equal(t, DownloadNotFound, state, "Artifacts should be bit downloaded if it's not found") 916 }