sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/trigger/pull-request_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 trigger 18 19 import ( 20 "context" 21 "encoding/json" 22 "strconv" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/sirupsen/logrus" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 clienttesting "k8s.io/client-go/testing" 29 30 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 31 "sigs.k8s.io/prow/pkg/client/clientset/versioned/fake" 32 "sigs.k8s.io/prow/pkg/config" 33 "sigs.k8s.io/prow/pkg/github" 34 "sigs.k8s.io/prow/pkg/github/fakegithub" 35 "sigs.k8s.io/prow/pkg/kube" 36 "sigs.k8s.io/prow/pkg/labels" 37 "sigs.k8s.io/prow/pkg/plugins" 38 ) 39 40 func TestTrusted(t *testing.T) { 41 const rando = "random-person" 42 const member = "org-member" 43 const sister = "trusted-org-member" 44 const friend = "repo-collaborator" 45 46 var testcases = []struct { 47 name string 48 author string 49 labels []string 50 onlyOrg bool 51 expected bool 52 }{ 53 { 54 name: "trust org member", 55 author: member, 56 labels: []string{}, 57 expected: true, 58 }, 59 { 60 name: "trust member of other trusted org", 61 author: sister, 62 labels: []string{}, 63 expected: true, 64 }, 65 { 66 name: "accept random PR with ok-to-test", 67 author: rando, 68 labels: []string{labels.OkToTest}, 69 expected: true, 70 }, 71 { 72 name: "accept random PR with both labels", 73 author: rando, 74 labels: []string{labels.OkToTest, labels.NeedsOkToTest}, 75 expected: true, 76 }, 77 { 78 name: "reject random PR with needs-ok-to-test", 79 author: rando, 80 labels: []string{labels.NeedsOkToTest}, 81 expected: false, 82 }, 83 { 84 name: "reject random PR with no label", 85 author: rando, 86 labels: []string{}, 87 expected: false, 88 }, 89 } 90 for _, tc := range testcases { 91 t.Run(tc.name, func(t *testing.T) { 92 g := fakegithub.NewFakeClient() 93 g.OrgMembers = map[string][]string{"kubernetes": {sister}, "kubernetes-sigs": {member, fakegithub.Bot}} 94 g.Collaborators = []string{friend} 95 g.IssueComments = map[int][]github.IssueComment{} 96 trigger := plugins.Trigger{ 97 TrustedOrg: "kubernetes", 98 OnlyOrgMembers: tc.onlyOrg, 99 } 100 var labels []github.Label 101 for _, label := range tc.labels { 102 labels = append(labels, github.Label{ 103 Name: label, 104 }) 105 } 106 _, actual, err := TrustedPullRequest(g, trigger, tc.author, "kubernetes-sigs", "random-repo", 1, labels) 107 if err != nil { 108 t.Fatalf("Didn't expect error: %s", err) 109 } 110 if actual != tc.expected { 111 t.Errorf("actual result %t != expected %t", actual, tc.expected) 112 } 113 }) 114 } 115 } 116 117 func TestHandlePullRequest(t *testing.T) { 118 jobToAbort := &prowapi.ProwJob{ 119 ObjectMeta: metav1.ObjectMeta{ 120 Name: "job-to-abort", 121 Namespace: "namespace", 122 Labels: map[string]string{ 123 kube.OrgLabel: "org", 124 kube.RepoLabel: "repo", 125 kube.PullLabel: "0", 126 kube.ProwJobTypeLabel: string(prowapi.PresubmitJob), 127 }, 128 }, 129 } 130 131 var testcases = []struct { 132 name string 133 134 Author string 135 ShouldBuild bool 136 ShouldComment bool 137 HasOkToTest bool 138 prLabel string 139 prChanges bool 140 prAction github.PullRequestEventAction 141 prIsDraft bool 142 eventSender string 143 jobToAbort *prowapi.ProwJob 144 issueLabelsAdded []string 145 }{ 146 { 147 name: "Trusted user open PR should build", 148 149 Author: "t", 150 ShouldBuild: true, 151 prAction: github.PullRequestActionOpened, 152 }, 153 { 154 name: "Trusted user open draft PR should not build and should comment", 155 156 Author: "t", 157 ShouldBuild: false, 158 prAction: github.PullRequestActionOpened, 159 prIsDraft: true, 160 ShouldComment: true, 161 }, 162 { 163 name: "Untrusted user open PR with ok-to-test should not add needs-ok-to-test", 164 165 Author: "u", 166 ShouldComment: true, 167 prAction: github.PullRequestActionOpened, 168 HasOkToTest: true, 169 }, 170 { 171 name: "Untrusted user open PR without ok-to-test should add needs-ok-to-test", 172 173 Author: "u", 174 ShouldComment: true, 175 prAction: github.PullRequestActionOpened, 176 issueLabelsAdded: []string{"org/repo#0:needs-ok-to-test"}, 177 }, 178 { 179 name: "Untrusted user open PR should not build and should comment", 180 181 Author: "u", 182 ShouldBuild: false, 183 ShouldComment: true, 184 prAction: github.PullRequestActionOpened, 185 issueLabelsAdded: []string{"org/repo#0:needs-ok-to-test"}, 186 }, 187 { 188 name: "Untrusted user open draft PR should not build and should comment", 189 190 Author: "u", 191 ShouldBuild: false, 192 ShouldComment: true, 193 prAction: github.PullRequestActionOpened, 194 prIsDraft: true, 195 issueLabelsAdded: []string{"org/repo#0:needs-ok-to-test"}, 196 }, 197 { 198 name: "Trusted user reopen PR should build", 199 200 Author: "t", 201 ShouldBuild: true, 202 prAction: github.PullRequestActionReopened, 203 }, 204 { 205 name: "Trusted user reopen draft PR should not build", 206 207 Author: "t", 208 ShouldBuild: false, 209 prAction: github.PullRequestActionReopened, 210 prIsDraft: true, 211 }, 212 { 213 name: "Trusted user switch PR from draft to normal shoud build", 214 215 Author: "t", 216 ShouldBuild: true, 217 prAction: github.PullRequestActionReadyForReview, 218 }, 219 { 220 name: "Untrusted user switch PR from draft to normal should not build", 221 222 Author: "u", 223 ShouldBuild: false, 224 prAction: github.PullRequestActionReadyForReview, 225 }, 226 { 227 name: "Untrusted user switch PR from draft to normal with ok-to-test should build", 228 229 Author: "u", 230 HasOkToTest: true, 231 ShouldBuild: true, 232 prAction: github.PullRequestActionReadyForReview, 233 }, 234 { 235 name: "Untrusted user reopen PR with ok-to-test should build", 236 237 Author: "u", 238 ShouldBuild: true, 239 HasOkToTest: true, 240 prAction: github.PullRequestActionReopened, 241 }, 242 { 243 name: "Untrusted user reopen PR without ok-to-test should not build", 244 245 Author: "u", 246 ShouldBuild: false, 247 prAction: github.PullRequestActionReopened, 248 }, 249 { 250 name: "Untrusted user reopen draft PR should not build", 251 252 Author: "u", 253 ShouldBuild: false, 254 prAction: github.PullRequestActionReopened, 255 prIsDraft: true, 256 }, 257 { 258 name: "Trusted user edit PR with changes should build", 259 260 Author: "t", 261 ShouldBuild: true, 262 prChanges: true, 263 prAction: github.PullRequestActionEdited, 264 }, 265 { 266 name: "Trusted user edit draft PR with changes should not build", 267 268 Author: "t", 269 ShouldBuild: false, 270 prChanges: true, 271 prAction: github.PullRequestActionEdited, 272 prIsDraft: true, 273 }, 274 { 275 name: "Trusted user edit PR without changes should not build", 276 277 Author: "t", 278 ShouldBuild: false, 279 prAction: github.PullRequestActionEdited, 280 }, 281 { 282 name: "Untrusted user edit PR without changes and without ok-to-test should not build", 283 284 Author: "u", 285 ShouldBuild: false, 286 prAction: github.PullRequestActionEdited, 287 }, 288 { 289 name: "Untrusted user edit PR with changes and without ok-to-test should not build", 290 291 Author: "u", 292 ShouldBuild: false, 293 prChanges: true, 294 prAction: github.PullRequestActionEdited, 295 }, 296 { 297 name: "Untrusted user edit PR without changes and with ok-to-test should not build", 298 299 Author: "u", 300 ShouldBuild: false, 301 HasOkToTest: true, 302 prAction: github.PullRequestActionEdited, 303 }, 304 { 305 name: "Untrusted user edit PR with changes and with ok-to-test should build", 306 307 Author: "u", 308 ShouldBuild: true, 309 HasOkToTest: true, 310 prChanges: true, 311 prAction: github.PullRequestActionEdited, 312 }, 313 { 314 name: "Trusted user sync PR should build", 315 316 Author: "t", 317 ShouldBuild: true, 318 prAction: github.PullRequestActionSynchronize, 319 }, 320 { 321 name: "Untrusted user sync PR without ok-to-test should not build", 322 323 Author: "u", 324 ShouldBuild: false, 325 prAction: github.PullRequestActionSynchronize, 326 }, 327 { 328 name: "Untrusted user sync PR with ok-to-test should build", 329 330 Author: "u", 331 ShouldBuild: true, 332 HasOkToTest: true, 333 prAction: github.PullRequestActionSynchronize, 334 }, 335 { 336 name: "Trusted user labeled PR with lgtm should not build", 337 338 Author: "t", 339 ShouldBuild: false, 340 prAction: github.PullRequestActionLabeled, 341 prLabel: labels.LGTM, 342 }, 343 { 344 name: "Untrusted user labeled PR with lgtm should build", 345 346 Author: "u", 347 ShouldBuild: true, 348 prAction: github.PullRequestActionLabeled, 349 prLabel: labels.LGTM, 350 }, 351 { 352 name: "Untrusted user labeled PR without lgtm should not build", 353 354 Author: "u", 355 ShouldBuild: false, 356 prAction: github.PullRequestActionLabeled, 357 prLabel: "test", 358 }, 359 { 360 name: "Trusted user closed PR should not build", 361 362 Author: "t", 363 ShouldBuild: false, 364 prAction: github.PullRequestActionClosed, 365 }, 366 { 367 name: "Trusted user labeled PR with ok-to-test should build", 368 369 Author: "t", 370 ShouldBuild: true, 371 eventSender: "not-k8s-ci-robot", 372 prAction: github.PullRequestActionLabeled, 373 prLabel: labels.OkToTest, 374 }, 375 { 376 name: "Untrusted user labeled PR with ok-to-test should build", 377 378 Author: "u", 379 ShouldBuild: true, 380 eventSender: "not-k8s-ci-robot", 381 prAction: github.PullRequestActionLabeled, 382 prLabel: labels.OkToTest, 383 }, 384 { 385 name: "Label added by a bot. Build should not be triggered in this case.", 386 387 Author: "u", 388 eventSender: "k8s-ci-robot", 389 prLabel: labels.OkToTest, 390 prAction: github.PullRequestActionLabeled, 391 ShouldBuild: false, 392 }, 393 { 394 name: "Abort jobs if PR is closed", 395 396 Author: "t", 397 HasOkToTest: true, 398 prAction: github.PullRequestActionClosed, 399 ShouldBuild: false, 400 jobToAbort: jobToAbort, 401 }, 402 { 403 name: "Abort jobs if PR is changed to draft", 404 405 Author: "t", 406 HasOkToTest: true, 407 prAction: github.PullRequestActionConvertedToDraft, 408 ShouldBuild: false, 409 jobToAbort: jobToAbort, 410 }, 411 { 412 name: "Abort old jobs and build on push", 413 414 Author: "t", 415 HasOkToTest: true, 416 prAction: github.PullRequestActionSynchronize, 417 ShouldBuild: true, 418 jobToAbort: jobToAbort, 419 }, 420 } 421 for _, tc := range testcases { 422 t.Logf("running scenario %q", tc.name) 423 t.Run(tc.name, func(t *testing.T) { 424 g := fakegithub.NewFakeClient() 425 g.IssueComments = map[int][]github.IssueComment{} 426 g.OrgMembers = map[string][]string{"org": {"t"}} 427 g.PullRequests = map[int]*github.PullRequest{ 428 0: { 429 Number: 0, 430 User: github.User{Login: tc.Author}, 431 Base: github.PullRequestBranch{ 432 Ref: "master", 433 Repo: github.Repo{ 434 Owner: github.User{Login: "org"}, 435 Name: "repo", 436 }, 437 }, 438 Draft: tc.prIsDraft, 439 }, 440 } 441 fakeProwJobClient := fake.NewSimpleClientset(jobToAbort) 442 c := Client{ 443 GitHubClient: g, 444 ProwJobClient: fakeProwJobClient.ProwV1().ProwJobs("namespace"), 445 Config: &config.Config{}, 446 Logger: logrus.WithField("plugin", PluginName), 447 GitClient: nil, 448 } 449 450 presubmits := map[string][]config.Presubmit{ 451 "org/repo": { 452 { 453 JobBase: config.JobBase{ 454 Name: "jib", 455 }, 456 AlwaysRun: true, 457 }, 458 }, 459 } 460 if err := c.Config.SetPresubmits(presubmits); err != nil { 461 t.Fatalf("failed to set presubmits: %v", err) 462 } 463 464 if tc.HasOkToTest { 465 g.IssueLabelsExisting = append(g.IssueLabelsExisting, issueLabels(labels.OkToTest)...) 466 } 467 if tc.eventSender == "" { 468 tc.eventSender = tc.Author 469 } 470 pr := github.PullRequestEvent{ 471 Action: tc.prAction, 472 Label: github.Label{Name: tc.prLabel}, 473 PullRequest: github.PullRequest{ 474 Number: 0, 475 User: github.User{Login: tc.Author}, 476 Base: github.PullRequestBranch{ 477 Ref: "master", 478 Repo: github.Repo{ 479 Owner: github.User{Login: "org"}, 480 Name: "repo", 481 FullName: "org/repo", 482 }, 483 }, 484 Draft: tc.prIsDraft, 485 }, 486 Sender: github.User{ 487 Login: tc.eventSender, 488 }, 489 } 490 if tc.prChanges { 491 data := []byte(`{"base":{"ref":{"from":"REF"}, "sha":{"from":"SHA"}}}`) 492 pr.Changes = (json.RawMessage)(data) 493 } 494 trigger := plugins.Trigger{ 495 TrustedOrg: "org", 496 OnlyOrgMembers: true, 497 } 498 trigger.SetDefaults() 499 if err := handlePR(c, trigger, pr); err != nil { 500 t.Fatalf("Didn't expect error: %s", err) 501 } 502 var numStarted int 503 for _, action := range fakeProwJobClient.Actions() { 504 switch action.(type) { 505 case clienttesting.CreateActionImpl: 506 numStarted++ 507 } 508 } 509 if numStarted > 0 && !tc.ShouldBuild { 510 t.Errorf("Built but should not have: %+v", tc) 511 } else if numStarted == 0 && tc.ShouldBuild { 512 t.Errorf("Not built but should have: %+v", tc) 513 } 514 if tc.ShouldComment && len(g.IssueCommentsAdded) == 0 { 515 t.Error("Expected comment to github") 516 } else if !tc.ShouldComment && len(g.IssueCommentsAdded) > 0 { 517 t.Errorf("Expected no comments to github, but got %d", len(g.IssueCommentsAdded)) 518 } 519 if tc.jobToAbort != nil { 520 pj, err := fakeProwJobClient.ProwV1().ProwJobs("namespace").Get(context.Background(), tc.jobToAbort.Name, metav1.GetOptions{}) 521 if err != nil { 522 t.Fatalf("failed to get prowjob: %v", err) 523 } 524 525 if pj.Status.State != prowapi.AbortedState { 526 t.Errorf("exptected job %s to be aborted, found state: %v", tc.jobToAbort.Name, pj.Status.State) 527 } 528 if pj.Complete() { 529 t.Errorf("exptected job %s to not be set to complete.", tc.jobToAbort.Name) 530 } 531 } 532 if cmp.Diff(tc.issueLabelsAdded, g.IssueLabelsAdded) != "" { 533 t.Errorf("exptected added issue labels %v to match %v", tc.issueLabelsAdded, g.IssueLabelsAdded) 534 } 535 }) 536 } 537 } 538 539 func TestAbortAllJobs(t *testing.T) { 540 t.Parallel() 541 const org, repo, number = "org", "repo", 1 542 pj := func(modifier ...func(*prowapi.ProwJob)) *prowapi.ProwJob { 543 job := &prowapi.ProwJob{ 544 ObjectMeta: metav1.ObjectMeta{ 545 Name: "my-pj", 546 Labels: map[string]string{ 547 kube.OrgLabel: org, 548 kube.RepoLabel: repo, 549 kube.PullLabel: strconv.Itoa(number), 550 kube.ProwJobTypeLabel: string(prowapi.PresubmitJob), 551 }, 552 }, 553 } 554 555 for _, m := range modifier { 556 m(job) 557 } 558 559 return job 560 } 561 testCases := []struct { 562 name string 563 pj *prowapi.ProwJob 564 expectedAbortedProwJob bool 565 }{ 566 { 567 name: "Job gets aborted", 568 pj: pj(), 569 expectedAbortedProwJob: true, 570 }, 571 { 572 name: "Wrong org, ignored", 573 pj: pj(func(pj *prowapi.ProwJob) { pj.Labels[kube.OrgLabel] = "wrong" }), 574 expectedAbortedProwJob: false, 575 }, 576 { 577 name: "Wrong repo, ignored", 578 pj: pj(func(pj *prowapi.ProwJob) { pj.Labels[kube.RepoLabel] = "wrong" }), 579 expectedAbortedProwJob: false, 580 }, 581 { 582 name: "Wrong pr, ignored", 583 pj: pj(func(pj *prowapi.ProwJob) { pj.Labels[kube.PullLabel] = "99" }), 584 expectedAbortedProwJob: false, 585 }, 586 { 587 name: "Wrong type, ignored", 588 pj: pj(func(pj *prowapi.ProwJob) { pj.Labels[kube.ProwJobTypeLabel] = "wrong" }), 589 expectedAbortedProwJob: false, 590 }, 591 { 592 name: "Job completed, ignored", 593 pj: pj(func(pj *prowapi.ProwJob) { 594 pj.Status.CompletionTime = &[]metav1.Time{metav1.Now()}[0] 595 }), 596 expectedAbortedProwJob: false, 597 }, 598 } 599 600 for _, tc := range testCases { 601 t.Run(tc.name, func(t *testing.T) { 602 pjClient := fake.NewSimpleClientset(tc.pj) 603 client := Client{ 604 ProwJobClient: pjClient.ProwV1().ProwJobs(""), 605 Logger: logrus.NewEntry(logrus.New()), 606 } 607 pr := &github.PullRequest{ 608 Base: github.PullRequestBranch{ 609 Repo: github.Repo{ 610 Owner: github.User{ 611 Login: org, 612 }, 613 Name: repo, 614 }, 615 }, 616 Number: number, 617 } 618 619 if err := abortAllJobs(client, pr); err != nil { 620 t.Fatalf("error caling abortAllJobs: %v", err) 621 } 622 623 pj, err := pjClient.ProwV1().ProwJobs("").Get(context.Background(), pj().Name, metav1.GetOptions{}) 624 if err != nil { 625 t.Fatalf("failed to get prowjob: %v", err) 626 } 627 628 if isAborted := (pj.Status.State == prowapi.AbortedState && pj.Status.Description == abortedDescription); isAborted != tc.expectedAbortedProwJob { 629 t.Errorf("IsAborted: %t, but expected aborted: %t", isAborted, tc.expectedAbortedProwJob) 630 } 631 }) 632 } 633 }