github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/orchestrator/jenkins_test.go (about) 1 //go:build unit 2 // +build unit 3 4 package orchestrator 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "os" 10 "testing" 11 "time" 12 13 "github.com/pkg/errors" 14 15 "net/http" 16 17 piperhttp "github.com/SAP/jenkins-library/pkg/http" 18 "github.com/jarcoal/httpmock" 19 "github.com/stretchr/testify/assert" 20 ) 21 22 func TestJenkins(t *testing.T) { 23 t.Run("BranchBuild", func(t *testing.T) { 24 defer resetEnv(os.Environ()) 25 os.Clearenv() 26 os.Setenv("JENKINS_URL", "FOO BAR BAZ") 27 os.Setenv("BUILD_URL", "https://jaas.url/job/foo/job/bar/job/main/1234/") 28 os.Setenv("BRANCH_NAME", "main") 29 os.Setenv("GIT_COMMIT", "abcdef42713") 30 os.Setenv("GIT_URL", "github.com/foo/bar") 31 32 p, _ := NewOrchestratorSpecificConfigProvider() 33 34 assert.False(t, p.IsPullRequest()) 35 assert.Equal(t, "https://jaas.url/job/foo/job/bar/job/main/1234/", p.GetBuildURL()) 36 assert.Equal(t, "main", p.GetBranch()) 37 assert.Equal(t, "refs/heads/main", p.GetReference()) 38 assert.Equal(t, "abcdef42713", p.GetCommit()) 39 assert.Equal(t, "github.com/foo/bar", p.GetRepoURL()) 40 assert.Equal(t, "Jenkins", p.OrchestratorType()) 41 }) 42 43 t.Run("PR", func(t *testing.T) { 44 defer resetEnv(os.Environ()) 45 os.Clearenv() 46 os.Setenv("BRANCH_NAME", "PR-42") 47 os.Setenv("CHANGE_BRANCH", "feat/test-jenkins") 48 os.Setenv("CHANGE_TARGET", "main") 49 os.Setenv("CHANGE_ID", "42") 50 51 p := JenkinsConfigProvider{} 52 c := p.GetPullRequestConfig() 53 54 assert.True(t, p.IsPullRequest()) 55 assert.Equal(t, "refs/pull/42/head", p.GetReference()) 56 assert.Equal(t, "feat/test-jenkins", c.Branch) 57 assert.Equal(t, "main", c.Base) 58 assert.Equal(t, "42", c.Key) 59 }) 60 61 t.Run("env variables", func(t *testing.T) { 62 defer resetEnv(os.Environ()) 63 os.Clearenv() 64 os.Setenv("JENKINS_HOME", "/var/lib/jenkins") 65 os.Setenv("BUILD_ID", "1234") 66 os.Setenv("JOB_URL", "https://jaas.url/job/foo/job/bar/job/main") 67 os.Setenv("JENKINS_VERSION", "42") 68 os.Setenv("JOB_NAME", "foo/bar/BRANCH") 69 os.Setenv("STAGE_NAME", "Promote") 70 os.Setenv("BUILD_URL", "https://jaas.url/job/foo/job/bar/job/main/1234/") 71 os.Setenv("STAGE_NAME", "Promote") 72 73 p := JenkinsConfigProvider{} 74 75 assert.Equal(t, "/var/lib/jenkins", p.getJenkinsHome()) 76 assert.Equal(t, "1234", p.GetBuildID()) 77 assert.Equal(t, "https://jaas.url/job/foo/job/bar/job/main", p.GetJobURL()) 78 assert.Equal(t, "42", p.OrchestratorVersion()) 79 assert.Equal(t, "Jenkins", p.OrchestratorType()) 80 assert.Equal(t, "foo/bar/BRANCH", p.GetJobName()) 81 assert.Equal(t, "Promote", p.GetStageName()) 82 assert.Equal(t, "https://jaas.url/job/foo/job/bar/job/main/1234/", p.GetBuildURL()) 83 84 }) 85 } 86 87 func TestJenkinsConfigProvider_GetPipelineStartTime(t *testing.T) { 88 type fields struct { 89 client piperhttp.Client 90 options piperhttp.ClientOptions 91 } 92 tests := []struct { 93 name string 94 fields fields 95 want time.Time 96 wantHTTPErr bool 97 wantHTTPStatusCodeError bool 98 wantHTTPJSONParseError bool 99 }{ 100 { 101 name: "Retrieve correct time", 102 want: time.Date(2022, time.March, 21, 22, 30, 0, 0, time.UTC), 103 wantHTTPErr: false, 104 wantHTTPStatusCodeError: false, 105 }, 106 { 107 name: "ParseHTTPResponseBodyJSON error", 108 want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), 109 wantHTTPErr: false, 110 wantHTTPStatusCodeError: false, 111 }, 112 { 113 name: "GetRequest fails", 114 want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), 115 wantHTTPErr: true, 116 wantHTTPStatusCodeError: false, 117 }, 118 { 119 name: "response code != 200 http.StatusNoContent", 120 want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), 121 wantHTTPErr: false, 122 wantHTTPStatusCodeError: true, 123 }, 124 { 125 name: "parseResponseBodyJson fails", 126 want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), 127 wantHTTPErr: false, 128 wantHTTPStatusCodeError: false, 129 wantHTTPJSONParseError: true, 130 }, 131 } 132 133 j := &JenkinsConfigProvider{ 134 client: piperhttp.Client{}, 135 } 136 j.client.SetOptions(piperhttp.ClientOptions{ 137 MaxRequestDuration: 5 * time.Second, 138 Token: "TOKEN", 139 TransportSkipVerification: true, 140 UseDefaultTransport: true, 141 MaxRetries: -1, 142 }) 143 httpmock.Activate() 144 145 for _, tt := range tests { 146 t.Run(tt.name, func(t *testing.T) { 147 defer resetEnv(os.Environ()) 148 os.Clearenv() 149 buildURl := "https://jaas.url/job/foo/job/bar/job/main/1234/" 150 os.Setenv("BUILD_URL", buildURl) 151 152 fakeUrl := buildURl + "api/json" 153 defer httpmock.DeactivateAndReset() 154 httpmock.RegisterResponder("GET", fakeUrl, 155 func(req *http.Request) (*http.Response, error) { 156 if tt.wantHTTPErr { 157 return nil, errors.New("this error shows up") 158 } 159 if tt.wantHTTPStatusCodeError { 160 return &http.Response{ 161 Status: "204", 162 StatusCode: http.StatusNoContent, 163 Request: req, 164 }, nil 165 } 166 if tt.wantHTTPJSONParseError { 167 // Intentionally malformed JSON response 168 return httpmock.NewJsonResponse(200, "timestamp:asdffd") 169 } 170 return httpmock.NewStringResponse(200, "{\"timestamp\":1647901800932,\"url\":\"https://jaas.url/view/piperpipelines/job/foo/job/bar/job/main/3731/\"}"), nil 171 }, 172 ) 173 174 assert.Equalf(t, tt.want, j.GetPipelineStartTime(), "GetPipelineStartTime()") 175 }) 176 } 177 } 178 179 func TestJenkinsConfigProvider_GetBuildStatus(t *testing.T) { 180 apiSuccess := []byte(`{ "queueId":376475, 181 "result":"SUCCESS", 182 "timestamp":1647946800925 183 }`) 184 apiFailure := []byte(`{ "queueId":376475, 185 "result":"FAILURE", 186 "timestamp":1647946800925 187 }`) 188 apiAborted := []byte(`{ "queueId":376475, 189 "result":"ABORTED", 190 "timestamp":1647946800925 191 }`) 192 193 apiOTHER := []byte(`{ "queueId":376475, 194 "result":"SOMETHING", 195 "timestamp":1647946800925 196 }`) 197 198 tests := []struct { 199 name string 200 want string 201 apiInformation []byte 202 }{ 203 { 204 name: "SUCCESS", 205 apiInformation: apiSuccess, 206 want: "SUCCESS", 207 }, 208 { 209 name: "ABORTED", 210 apiInformation: apiAborted, 211 want: "ABORTED", 212 }, 213 { 214 name: "FAILURE", 215 apiInformation: apiFailure, 216 want: "FAILURE", 217 }, 218 { 219 name: "Unknown FAILURE", 220 apiInformation: apiOTHER, 221 want: "FAILURE", 222 }, 223 } 224 for _, tt := range tests { 225 t.Run(tt.name, func(t *testing.T) { 226 var apiInformation map[string]interface{} 227 err := json.Unmarshal(tt.apiInformation, &apiInformation) 228 if err != nil { 229 t.Fatal("could not parse json:", err) 230 } 231 j := &JenkinsConfigProvider{ 232 apiInformation: apiInformation, 233 } 234 assert.Equalf(t, tt.want, j.GetBuildStatus(), "GetBuildStatus()") 235 }) 236 } 237 } 238 239 func TestJenkinsConfigProvider_GetBuildReason(t *testing.T) { 240 apiJsonSchedule := []byte(`{ 241 "_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun", 242 "actions": [{ 243 "_class": "hudson.model.CauseAction", 244 "causes": [{ 245 "_class": "hudson.triggers.TimerTrigger$TimerTriggerCause", 246 "shortDescription": "Started by timer" 247 }] 248 }, 249 { 250 "_class": "jenkins.metrics.impl.TimeInQueueAction", 251 "blockedDurationMillis": "0" 252 } 253 ] 254 }`) 255 256 apiJSONManual := []byte(`{ 257 "_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun", 258 "actions": [{ 259 "_class": "hudson.model.CauseAction", 260 "causes": [{ 261 "_class": "hudson.model.Cause$UserIdCause", 262 "shortDescription": "Started by user John Doe", 263 "userId": "i12345", 264 "userName": "John Doe" 265 }] 266 }, 267 { 268 "_class": "jenkins.metrics.impl.TimeInQueueAction", 269 "blockedDurationMillis": "0" 270 } 271 ] 272 }`) 273 274 apiJSONPullRequest := []byte(`{ 275 "_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun", 276 "actions": [ { 277 "_class": "hudson.model.CauseAction", 278 "causes": [ 279 { 280 "_class": "jenkins.branch.BranchEventCause", 281 "shortDescription": "Pull request #1511 opened" 282 } 283 ] 284 }] 285 }`) 286 287 apiJSONResourceTrigger := []byte(`{ 288 "_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun", 289 "actions": [ { 290 "_class": "hudson.model.CauseAction", 291 "causes": [ 292 { 293 "_class": "org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause", 294 "shortDescription": "Started by upstream project \"dummy/dummy/PR-1234\" build number 42", 295 "upstreamBuild": 24, 296 "upstreamProject": "dummy/dummy/PR-1234", 297 "upstreamUrl": "job/dummy/job/dummy/job/PR-1234/" 298 } 299 ] 300 }] 301 }`) 302 303 apiJSONUnknown := []byte(`{ 304 "_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun", 305 "actions": [{ 306 "_class": "hudson.model.CauseAction", 307 "causes": [{ 308 "_class": "hudson.model.RandomThingHere", 309 "shortDescription": "Something" 310 }] 311 }, 312 { 313 "_class": "jenkins.metrics.impl.TimeInQueueAction", 314 "blockedDurationMillis": "0" 315 } 316 ] 317 }`) 318 319 tests := []struct { 320 name string 321 apiInformation []byte 322 want string 323 }{ 324 { 325 name: "Manual trigger", 326 apiInformation: apiJSONManual, 327 want: "Manual", 328 }, 329 { 330 name: "Scheduled trigger", 331 apiInformation: apiJsonSchedule, 332 want: "Schedule", 333 }, 334 { 335 name: "PullRequest trigger", 336 apiInformation: apiJSONPullRequest, 337 want: "PullRequest", 338 }, 339 { 340 name: "ResourceTrigger trigger", 341 apiInformation: apiJSONResourceTrigger, 342 want: "ResourceTrigger", 343 }, 344 { 345 name: "Unknown", 346 apiInformation: apiJSONUnknown, 347 want: "Unknown", 348 }, 349 { 350 name: "Empty api", 351 apiInformation: []byte(`{}`), 352 want: "Unknown", 353 }, 354 { 355 name: "Empty action api", 356 apiInformation: []byte(`{ 357 "actions": [{}] 358 }`), 359 want: "Unknown", 360 }, 361 } 362 for _, tt := range tests { 363 t.Run(tt.name, func(t *testing.T) { 364 365 var apiInformation map[string]interface{} 366 err := json.Unmarshal(tt.apiInformation, &apiInformation) 367 if err != nil { 368 t.Fatal("could not parse json:", err) 369 } 370 j := &JenkinsConfigProvider{apiInformation: apiInformation} 371 372 assert.Equalf(t, tt.want, j.GetBuildReason(), "GetBuildReason()") 373 }) 374 } 375 } 376 377 func TestJenkinsConfigProvider_getAPIInformation(t *testing.T) { 378 379 tests := []struct { 380 name string 381 wantHTTPErr bool 382 wantHTTPStatusCodeError bool 383 wantHTTPJSONParseError bool 384 apiInformation map[string]interface{} 385 wantAPIInformation map[string]interface{} 386 }{ 387 { 388 name: "success case", 389 apiInformation: map[string]interface{}{}, 390 wantAPIInformation: map[string]interface{}{"Success": "Case"}, 391 }, 392 { 393 name: "apiInformation already set", 394 apiInformation: map[string]interface{}{"API info": "set"}, 395 wantAPIInformation: map[string]interface{}{"API info": "set"}, 396 }, 397 { 398 name: "failed to get response", 399 apiInformation: map[string]interface{}{}, 400 wantHTTPErr: true, 401 wantAPIInformation: map[string]interface{}{}, 402 }, 403 { 404 name: "response code != 200 http.StatusNoContent", 405 wantHTTPStatusCodeError: true, 406 apiInformation: map[string]interface{}{}, 407 wantAPIInformation: map[string]interface{}{}, 408 }, 409 { 410 name: "parseResponseBodyJson fails", 411 wantHTTPJSONParseError: true, 412 apiInformation: map[string]interface{}{}, 413 wantAPIInformation: map[string]interface{}{}, 414 }, 415 } 416 for _, tt := range tests { 417 t.Run(tt.name, func(t *testing.T) { 418 j := &JenkinsConfigProvider{ 419 apiInformation: tt.apiInformation, 420 } 421 j.client.SetOptions(piperhttp.ClientOptions{ 422 MaxRequestDuration: 5 * time.Second, 423 Token: "TOKEN", 424 TransportSkipVerification: true, 425 UseDefaultTransport: true, // need to use default transport for http mock 426 MaxRetries: -1, 427 }) 428 429 defer resetEnv(os.Environ()) 430 os.Clearenv() 431 os.Setenv("BUILD_URL", "https://jaas.url/job/foo/job/bar/job/main/1234/") 432 433 fakeUrl := "https://jaas.url/job/foo/job/bar/job/main/1234/api/json" 434 httpmock.Activate() 435 defer httpmock.DeactivateAndReset() 436 httpmock.RegisterResponder("GET", fakeUrl, 437 func(req *http.Request) (*http.Response, error) { 438 if tt.wantHTTPErr { 439 return nil, errors.New("this error shows up") 440 } 441 if tt.wantHTTPStatusCodeError { 442 return &http.Response{ 443 Status: "204", 444 StatusCode: http.StatusNoContent, 445 Request: req, 446 }, nil 447 } 448 if tt.wantHTTPJSONParseError { 449 // Intentionally malformed JSON response 450 return httpmock.NewJsonResponse(200, "timestamp:broken") 451 } 452 return httpmock.NewStringResponse(200, "{\"Success\":\"Case\"}"), nil 453 }, 454 ) 455 j.fetchAPIInformation() 456 assert.Equal(t, tt.wantAPIInformation, j.apiInformation) 457 }) 458 } 459 } 460 461 func TestJenkinsConfigProvider_GetLog(t *testing.T) { 462 463 tests := []struct { 464 name string 465 want []byte 466 wantErr assert.ErrorAssertionFunc 467 wantHTTPErr bool 468 wantHTTPStatusCodeError bool 469 }{ 470 { 471 name: "Successfully got log file", 472 want: []byte("Success!"), 473 wantErr: assert.NoError, 474 }, 475 { 476 name: "HTTP error", 477 want: []byte(""), 478 wantErr: assert.Error, 479 wantHTTPErr: true, 480 }, 481 { 482 name: "Status code error", 483 want: []byte(""), 484 wantErr: assert.NoError, 485 wantHTTPStatusCodeError: true, 486 }, 487 } 488 for _, tt := range tests { 489 t.Run(tt.name, func(t *testing.T) { 490 j := &JenkinsConfigProvider{} 491 j.client.SetOptions(piperhttp.ClientOptions{ 492 MaxRequestDuration: 5 * time.Second, 493 Token: "TOKEN", 494 TransportSkipVerification: true, 495 UseDefaultTransport: true, // need to use default transport for http mock 496 MaxRetries: -1, 497 }) 498 499 defer resetEnv(os.Environ()) 500 os.Clearenv() 501 os.Setenv("BUILD_URL", "https://jaas.url/job/foo/job/bar/job/main/1234/") 502 503 fakeUrl := "https://jaas.url/job/foo/job/bar/job/main/1234/consoleText" 504 httpmock.Activate() 505 defer httpmock.DeactivateAndReset() 506 httpmock.RegisterResponder("GET", fakeUrl, 507 func(req *http.Request) (*http.Response, error) { 508 if tt.wantHTTPErr { 509 return nil, errors.New("this error shows up") 510 } 511 if tt.wantHTTPStatusCodeError { 512 return &http.Response{ 513 Status: "204", 514 StatusCode: http.StatusNoContent, 515 Request: req, 516 }, nil 517 } 518 return httpmock.NewStringResponse(200, "Success!"), nil 519 }, 520 ) 521 522 got, err := j.GetLog() 523 if !tt.wantErr(t, err, fmt.Sprintf("GetLog()")) { 524 return 525 } 526 assert.Equalf(t, tt.want, got, "GetLog()") 527 }) 528 } 529 } 530 531 func TestJenkinsConfigProvider_InitOrchestratorProvider(t *testing.T) { 532 533 tests := []struct { 534 name string 535 settings *OrchestratorSettings 536 apiInformation map[string]interface{} 537 }{ 538 { 539 name: "Init, test empty apiInformation", 540 settings: &OrchestratorSettings{}, 541 }, 542 } 543 for _, tt := range tests { 544 t.Run(tt.name, func(t *testing.T) { 545 j := &JenkinsConfigProvider{} 546 j.InitOrchestratorProvider(tt.settings) 547 var expected map[string]interface{} 548 assert.Equal(t, j.apiInformation, expected) 549 }) 550 } 551 } 552 553 func TestJenkinsConfigProvider_GetChangeSet(t *testing.T) { 554 555 changeSetTwo := []byte(`{ 556 "displayName": "#531", 557 "duration": 424269, 558 "changeSets": [ 559 { 560 "_class": "hudson.plugins.git.GitChangeSetList", 561 "items": [ 562 { 563 "_class": "hudson.plugins.git.GitChangeSet", 564 "commitId": "987654321", 565 "timestamp": 1655057520000 566 }, 567 { 568 "_class": "hudson.plugins.git.GitChangeSet", 569 "commitId": "123456789", 570 "timestamp": 1656057520000 571 } 572 ], 573 "kind": "git" 574 } 575 ] 576 }`) 577 578 changeSetMultiple := []byte(`{ 579 "displayName": "#531", 580 "duration": 424269, 581 "changeSets": [ 582 { 583 "_class": "hudson.plugins.git.GitChangeSetList", 584 "items": [ 585 { 586 "_class": "hudson.plugins.git.GitChangeSet", 587 "commitId": "987654321", 588 "timestamp": 1655057520000 589 }, 590 { 591 "_class": "hudson.plugins.git.GitChangeSet", 592 "commitId": "123456789", 593 "timestamp": 1656057520000 594 } 595 ], 596 "kind": "git" 597 }, 598 { 599 "_class": "hudson.plugins.git.GitChangeSetList", 600 "items": [ 601 { 602 "_class": "hudson.plugins.git.GitChangeSet", 603 "commitId": "456789123", 604 "timestamp": 1659948036000 605 }, 606 { 607 "_class": "hudson.plugins.git.GitChangeSet", 608 "commitId": "654717777", 609 "timestamp": 1660053494000 610 } 611 ], 612 "kind": "git" 613 } 614 ] 615 }`) 616 617 changeSetEmpty := []byte(`{ 618 "displayName": "#531", 619 "duration": 424269, 620 "changeSets": [] 621 }`) 622 changeSetNotAvailable := []byte(`{ 623 "displayName": "#531", 624 "duration": 424269 625 }`) 626 tests := []struct { 627 name string 628 want []ChangeSet 629 testChangeSet []byte 630 }{ 631 { 632 name: "success", 633 want: []ChangeSet{ 634 {CommitId: "987654321", Timestamp: "1655057520000"}, 635 {CommitId: "123456789", Timestamp: "1656057520000"}, 636 }, 637 testChangeSet: changeSetTwo, 638 }, 639 { 640 name: "success multiple", 641 want: []ChangeSet{ 642 {CommitId: "987654321", Timestamp: "1655057520000"}, 643 {CommitId: "123456789", Timestamp: "1656057520000"}, 644 {CommitId: "456789123", Timestamp: "1659948036000"}, 645 {CommitId: "654717777", Timestamp: "1660053494000"}, 646 }, 647 testChangeSet: changeSetMultiple, 648 }, 649 { 650 name: "failure - changeSet empty", 651 want: []ChangeSet(nil), 652 testChangeSet: changeSetEmpty, 653 }, 654 { 655 name: "failure - no changeSet found", 656 want: []ChangeSet(nil), 657 testChangeSet: changeSetNotAvailable, 658 }, 659 } 660 for _, tt := range tests { 661 t.Run(tt.name, func(t *testing.T) { 662 var apiInformation map[string]interface{} 663 err := json.Unmarshal(tt.testChangeSet, &apiInformation) 664 if err != nil { 665 t.Fatal("could not parse json:", err) 666 } 667 j := &JenkinsConfigProvider{apiInformation: apiInformation} 668 assert.Equalf(t, tt.want, j.GetChangeSet(), "GetChangeSet()") 669 }) 670 } 671 }