sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/approve/approvers/approvers_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 approvers 18 19 import ( 20 "net/url" 21 "reflect" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/google/go-cmp/cmp/cmpopts" 26 "github.com/sirupsen/logrus" 27 28 "k8s.io/apimachinery/pkg/util/sets" 29 30 "sigs.k8s.io/prow/pkg/plugins/ownersconfig" 31 ) 32 33 func TestUnapprovedFiles(t *testing.T) { 34 rootApprovers := sets.New[string]("Alice", "Bob") 35 aApprovers := sets.New[string]("Art", "Anne") 36 bApprovers := sets.New[string]("Bill", "Ben", "Barbara") 37 cApprovers := sets.New[string]("Chris", "Carol") 38 dApprovers := sets.New[string]("David", "Dan", "Debbie") 39 eApprovers := sets.New[string]("Eve", "Erin") 40 edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers) 41 FakeRepoMap := map[string]sets.Set[string]{ 42 "": rootApprovers, 43 "a": aApprovers, 44 "b": bApprovers, 45 "c": cApprovers, 46 "a/d": dApprovers, 47 "a/combo": edcApprovers, 48 } 49 tests := []struct { 50 testName string 51 filenames []string 52 currentlyApproved sets.Set[string] 53 expectedUnapproved sets.Set[string] 54 }{ 55 { 56 testName: "Empty PR", 57 filenames: []string{}, 58 currentlyApproved: sets.New[string](), 59 expectedUnapproved: sets.New[string](), 60 }, 61 { 62 testName: "Single Root File PR Approved", 63 filenames: []string{"kubernetes.go"}, 64 currentlyApproved: sets.New[string](sets.List(rootApprovers)[0]), 65 expectedUnapproved: sets.New[string](), 66 }, 67 { 68 testName: "Single Root File PR No One Approved", 69 filenames: []string{"kubernetes.go"}, 70 currentlyApproved: sets.New[string](), 71 expectedUnapproved: sets.New[string](""), 72 }, 73 { 74 testName: "B Only UnApproved", 75 currentlyApproved: bApprovers, 76 expectedUnapproved: sets.New[string](), 77 }, 78 { 79 testName: "B Files Approved at Root", 80 filenames: []string{"b/test.go", "b/test_1.go"}, 81 currentlyApproved: rootApprovers, 82 expectedUnapproved: sets.New[string](), 83 }, 84 { 85 testName: "B Only UnApproved", 86 filenames: []string{"b/test_1.go", "b/test.go"}, 87 currentlyApproved: sets.New[string](), 88 expectedUnapproved: sets.New[string]("b"), 89 }, 90 { 91 testName: "Combo and Other; Neither Approved", 92 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 93 currentlyApproved: sets.New[string](), 94 expectedUnapproved: sets.New[string]("a/combo", "a/d"), 95 }, 96 { 97 testName: "Combo and Other; Combo Approved", 98 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 99 currentlyApproved: edcApprovers.Difference(dApprovers), 100 expectedUnapproved: sets.New[string]("a/d"), 101 }, 102 { 103 testName: "Combo and Other; Both Approved", 104 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 105 currentlyApproved: edcApprovers.Intersection(dApprovers), 106 expectedUnapproved: sets.New[string](), 107 }, 108 } 109 110 for _, test := range tests { 111 testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap), seed: TestSeed, log: logrus.WithField("plugin", "some_plugin")}) 112 testApprovers.RequireIssue = false 113 for approver := range test.currentlyApproved { 114 testApprovers.AddApprover(approver, "REFERENCE", false) 115 } 116 calculated := testApprovers.UnapprovedFiles() 117 if !test.expectedUnapproved.Equal(calculated) { 118 t.Errorf("Failed for test %v. Expected unapproved files: %v. Found %v", test.testName, test.expectedUnapproved, calculated) 119 } 120 } 121 } 122 123 func TestGetFiles(t *testing.T) { 124 rootApprovers := sets.New[string]("Alice", "Bob") 125 aApprovers := sets.New[string]("Art", "Anne") 126 bApprovers := sets.New[string]("Bill", "Ben", "Barbara") 127 cApprovers := sets.New[string]("Chris", "Carol") 128 dApprovers := sets.New[string]("David", "Dan", "Debbie") 129 eApprovers := sets.New[string]("Eve", "Erin") 130 edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers) 131 FakeRepoMap := map[string]sets.Set[string]{ 132 "": rootApprovers, 133 "a": aApprovers, 134 "b": bApprovers, 135 "c": cApprovers, 136 "a/d": dApprovers, 137 "a/combo": edcApprovers, 138 } 139 tests := []struct { 140 testName string 141 filenames []string 142 currentlyApproved sets.Set[string] 143 expectedFiles []File 144 }{ 145 { 146 testName: "Empty PR", 147 filenames: []string{}, 148 currentlyApproved: sets.New[string](), 149 expectedFiles: []File{}, 150 }, 151 { 152 testName: "Single Root File PR Approved", 153 filenames: []string{"kubernetes.go"}, 154 currentlyApproved: sets.New[string](sets.List(rootApprovers)[0]), 155 expectedFiles: []File{ApprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "", ownersconfig.DefaultOwnersFile, sets.New[string](sets.List(rootApprovers)[0]), "master"}}, 156 }, 157 { 158 testName: "Single File PR in B No One Approved", 159 filenames: []string{"b/test.go"}, 160 currentlyApproved: sets.New[string](), 161 expectedFiles: []File{UnapprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "b", ownersconfig.DefaultOwnersFile, nil, "master"}}, 162 }, 163 { 164 testName: "Single File PR in B Fully Approved", 165 filenames: []string{"b/test.go"}, 166 currentlyApproved: bApprovers, 167 expectedFiles: []File{ApprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "b", ownersconfig.DefaultOwnersFile, bApprovers, "master"}}, 168 }, 169 { 170 testName: "Single Root File PR No One Approved", 171 filenames: []string{"kubernetes.go"}, 172 currentlyApproved: sets.New[string](), 173 expectedFiles: []File{UnapprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "", ownersconfig.DefaultOwnersFile, nil, "master"}}, 174 }, 175 { 176 testName: "Combo and Other; Neither Approved", 177 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 178 currentlyApproved: sets.New[string](), 179 expectedFiles: []File{ 180 UnapprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "a/combo", ownersconfig.DefaultOwnersFile, nil, "master"}, 181 UnapprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "a/d", ownersconfig.DefaultOwnersFile, nil, "master"}, 182 }, 183 }, 184 { 185 testName: "Combo and Other; Combo Approved", 186 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 187 currentlyApproved: eApprovers, 188 expectedFiles: []File{ 189 ApprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "a/combo", ownersconfig.DefaultOwnersFile, eApprovers, "master"}, 190 UnapprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "a/d", ownersconfig.DefaultOwnersFile, nil, "master"}, 191 }, 192 }, 193 { 194 testName: "Combo and Other; Both Approved", 195 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 196 currentlyApproved: edcApprovers.Intersection(dApprovers), 197 expectedFiles: []File{ 198 ApprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "a/combo", ownersconfig.DefaultOwnersFile, edcApprovers.Intersection(dApprovers), "master"}, 199 ApprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "a/d", ownersconfig.DefaultOwnersFile, edcApprovers.Intersection(dApprovers), "master"}, 200 }, 201 }, 202 { 203 testName: "Combo, C, D; Combo and C Approved", 204 filenames: []string{"a/combo/test.go", "a/d/test.go", "c/test"}, 205 currentlyApproved: cApprovers, 206 expectedFiles: []File{ 207 ApprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "a/combo", ownersconfig.DefaultOwnersFile, cApprovers, "master"}, 208 UnapprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "a/d", ownersconfig.DefaultOwnersFile, nil, "master"}, 209 ApprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "c", ownersconfig.DefaultOwnersFile, cApprovers, "master"}, 210 }, 211 }, 212 { 213 testName: "Files Approved Multiple times", 214 filenames: []string{"a/test.go", "a/d/test.go", "b/test"}, 215 currentlyApproved: rootApprovers.Union(aApprovers).Union(bApprovers), 216 expectedFiles: []File{ 217 ApprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "a", ownersconfig.DefaultOwnersFile, rootApprovers.Union(aApprovers), "master"}, 218 ApprovedFile{&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "b", ownersconfig.DefaultOwnersFile, rootApprovers.Union(bApprovers), "master"}, 219 }, 220 }, 221 } 222 223 for _, test := range tests { 224 t.Run(test.testName, func(t *testing.T) { 225 testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap), seed: TestSeed, log: logrus.WithField("plugin", "some_plugin")}) 226 testApprovers.RequireIssue = false 227 for approver := range test.currentlyApproved { 228 testApprovers.AddApprover(approver, "REFERENCE", false) 229 } 230 calculated := testApprovers.GetFiles(&url.URL{Scheme: "https", Host: "github.com", Path: "org/repo"}, "master") 231 if diff := cmp.Diff(test.expectedFiles, calculated, cmpopts.EquateEmpty(), cmp.Exporter(func(_ reflect.Type) bool { return true })); diff != "" { 232 t.Errorf("expected files differ from actual: %s", diff) 233 } 234 }) 235 } 236 } 237 238 func TestGetCCs(t *testing.T) { 239 rootApprovers := sets.New[string]("Alice", "Bob") 240 aApprovers := sets.New[string]("Art", "Anne") 241 bApprovers := sets.New[string]("Bill", "Ben", "Barbara") 242 cApprovers := sets.New[string]("Chris", "Carol") 243 dApprovers := sets.New[string]("David", "Dan", "Debbie") 244 eApprovers := sets.New[string]("Eve", "Erin") 245 edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers) 246 FakeRepoMap := map[string]sets.Set[string]{ 247 "": rootApprovers, 248 "a": aApprovers, 249 "b": bApprovers, 250 "c": cApprovers, 251 "a/d": dApprovers, 252 "a/combo": edcApprovers, 253 } 254 tests := []struct { 255 testName string 256 filenames []string 257 currentlyApproved sets.Set[string] 258 // testSeed affects who is chosen for CC 259 testSeed int64 260 assignees []string 261 // order matters for CCs 262 expectedCCs []string 263 expectedAssignedCCs []string 264 expectedSuggestedCCs []string 265 }{ 266 { 267 testName: "Empty PR", 268 filenames: []string{}, 269 currentlyApproved: sets.New[string](), 270 testSeed: 0, 271 expectedCCs: []string{}, 272 expectedAssignedCCs: []string{}, 273 expectedSuggestedCCs: []string{}, 274 }, 275 { 276 testName: "Single Root FFile PR Approved", 277 filenames: []string{"kubernetes.go"}, 278 currentlyApproved: sets.New[string](sets.List(rootApprovers)[0]), 279 testSeed: 13, 280 expectedCCs: []string{}, 281 expectedAssignedCCs: []string{}, 282 expectedSuggestedCCs: []string{}, 283 }, 284 { 285 testName: "Single Root File PR Unapproved Seed = 13", 286 filenames: []string{"kubernetes.go"}, 287 currentlyApproved: sets.New[string](), 288 testSeed: 13, 289 expectedCCs: []string{"alice"}, 290 expectedAssignedCCs: []string{}, 291 expectedSuggestedCCs: []string{"alice"}, 292 }, 293 { 294 testName: "Single Root File PR No One Seed = 10", 295 filenames: []string{"kubernetes.go"}, 296 testSeed: 10, 297 currentlyApproved: sets.New[string](), 298 expectedCCs: []string{"bob"}, 299 expectedAssignedCCs: []string{}, 300 expectedSuggestedCCs: []string{"bob"}, 301 }, 302 { 303 testName: "Combo and Other; Neither Approved", 304 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 305 testSeed: 0, 306 currentlyApproved: sets.New[string](), 307 expectedCCs: []string{"dan"}, 308 expectedAssignedCCs: []string{}, 309 expectedSuggestedCCs: []string{"dan"}, 310 }, 311 { 312 testName: "Combo and Other; Combo Approved", 313 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 314 testSeed: 0, 315 currentlyApproved: eApprovers, 316 expectedCCs: []string{"dan"}, 317 expectedAssignedCCs: []string{}, 318 expectedSuggestedCCs: []string{"dan"}, 319 }, 320 { 321 testName: "Combo and Other; Both Approved", 322 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 323 testSeed: 0, 324 currentlyApproved: dApprovers, // dApprovers can approve combo and d directory 325 expectedCCs: []string{}, 326 expectedAssignedCCs: []string{}, 327 expectedSuggestedCCs: []string{}, 328 }, 329 { 330 testName: "Combo, C, D; None Approved", 331 filenames: []string{"a/combo/test.go", "a/d/test.go", "c/test"}, 332 testSeed: 0, 333 currentlyApproved: sets.New[string](), 334 // chris can approve c and combo, debbie can approve d 335 expectedCCs: []string{"chris", "debbie"}, 336 expectedAssignedCCs: []string{}, 337 expectedSuggestedCCs: []string{"chris", "debbie"}, 338 }, 339 { 340 testName: "A, B, C; Nothing Approved", 341 filenames: []string{"a/test.go", "b/test.go", "c/test"}, 342 testSeed: 0, 343 currentlyApproved: sets.New[string](), 344 // Need an approver from each of the three owners files 345 expectedCCs: []string{"anne", "bill", "carol"}, 346 expectedAssignedCCs: []string{}, 347 expectedSuggestedCCs: []string{"anne", "bill", "carol"}, 348 }, 349 { 350 testName: "A, B, C; Partially approved by non-suggested approvers", 351 filenames: []string{"a/test.go", "b/test.go", "c/test"}, 352 testSeed: 0, 353 // Approvers are valid approvers, but not the one we would suggest 354 currentlyApproved: sets.New[string]("Art", "Ben"), 355 // We don't suggest approvers for a and b, only for unapproved c. 356 expectedCCs: []string{"carol"}, 357 expectedAssignedCCs: []string{}, 358 expectedSuggestedCCs: []string{"carol"}, 359 }, 360 { 361 testName: "A, B, C; Nothing approved, but assignees can approve", 362 filenames: []string{"a/test.go", "b/test.go", "c/test"}, 363 testSeed: 0, 364 // Approvers are valid approvers, but not the one we would suggest 365 currentlyApproved: sets.New[string](), 366 assignees: []string{"Art", "Ben"}, 367 // We suggest assigned people rather than "suggested" people 368 // Suggested would be "Anne", "Bill", "Carol" if no one was assigned. 369 expectedCCs: []string{"art", "ben", "carol"}, 370 expectedAssignedCCs: []string{"art", "ben"}, 371 expectedSuggestedCCs: []string{"carol"}, 372 }, 373 { 374 testName: "A, B, C; Nothing approved, but SOME assignees can approve", 375 filenames: []string{"a/test.go", "b/test.go", "c/test"}, 376 testSeed: 0, 377 currentlyApproved: sets.New[string](), 378 // Assignees are a mix of potential approvers and random people 379 assignees: []string{"Art", "Ben", "John", "Jack"}, 380 // We suggest assigned people rather than "suggested" people 381 expectedCCs: []string{"art", "ben", "carol"}, 382 expectedAssignedCCs: []string{"art", "ben"}, 383 expectedSuggestedCCs: []string{"carol"}, 384 }, 385 { 386 testName: "Assignee is top OWNER, No one has approved", 387 filenames: []string{"a/test.go"}, 388 testSeed: 0, 389 currentlyApproved: sets.New[string](), 390 // Assignee is a root approver 391 assignees: []string{"alice"}, 392 expectedCCs: []string{"alice"}, 393 expectedAssignedCCs: []string{"alice"}, 394 expectedSuggestedCCs: []string{}, 395 }, 396 } 397 398 for _, test := range tests { 399 testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap), seed: test.testSeed, log: logrus.WithField("plugin", "some_plugin")}) 400 testApprovers.RequireIssue = false 401 for approver := range test.currentlyApproved { 402 testApprovers.AddApprover(approver, "REFERENCE", false) 403 } 404 testApprovers.AddAssignees(test.assignees...) 405 calculated := testApprovers.GetCCs() 406 if !reflect.DeepEqual(test.expectedCCs, calculated) { 407 t.Errorf("Failed for test %v. Expected CCs: %v. Found %v", test.testName, test.expectedCCs, calculated) 408 } 409 calculated = testApprovers.AssignedCCs() 410 if !reflect.DeepEqual(test.expectedAssignedCCs, calculated) { 411 t.Errorf("Failed for test %v. Expected AssignedCCs: %v. Found %v", test.testName, test.expectedAssignedCCs, calculated) 412 } 413 calculated = testApprovers.SuggestedCCs() 414 if !reflect.DeepEqual(test.expectedSuggestedCCs, calculated) { 415 t.Errorf("Failed for test %v. Expected SuggestedCCs: %v. Found %v", test.testName, test.expectedSuggestedCCs, calculated) 416 } 417 } 418 } 419 420 func TestIsApproved(t *testing.T) { 421 rootApprovers := sets.New[string]("Alice", "Bob") 422 aApprovers := sets.New[string]("Art", "Anne") 423 bApprovers := sets.New[string]("Bill", "Ben", "Barbara") 424 cApprovers := sets.New[string]("Chris", "Carol") 425 dApprovers := sets.New[string]("David", "Dan", "Debbie") 426 eApprovers := sets.New[string]("Eve", "Erin") 427 edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers) 428 FakeRepoMap := map[string]sets.Set[string]{ 429 "": rootApprovers, 430 "a": aApprovers, 431 "b": bApprovers, 432 "c": cApprovers, 433 "a/d": dApprovers, 434 "a/combo": edcApprovers, 435 "d": {}, 436 } 437 tests := []struct { 438 testName string 439 filenames []string 440 autoApproveUnownedSubfoldersMap map[string]bool 441 currentlyApproved sets.Set[string] 442 testSeed int64 443 isApproved bool 444 }{ 445 { 446 testName: "Empty PR", 447 filenames: []string{}, 448 currentlyApproved: sets.New[string](), 449 testSeed: 0, 450 isApproved: false, 451 }, 452 { 453 testName: "Single Root File PR Approved", 454 filenames: []string{"kubernetes.go"}, 455 currentlyApproved: sets.New[string](sets.List(rootApprovers)[0]), 456 testSeed: 3, 457 isApproved: true, 458 }, 459 { 460 testName: "Single Root File PR No One Approved", 461 filenames: []string{"kubernetes.go"}, 462 testSeed: 0, 463 currentlyApproved: sets.New[string](), 464 isApproved: false, 465 }, 466 { 467 testName: "Combo and Other; Neither Approved", 468 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 469 testSeed: 0, 470 currentlyApproved: sets.New[string](), 471 isApproved: false, 472 }, 473 { 474 testName: "Combo and Other; Both Approved", 475 filenames: []string{"a/combo/test.go", "a/d/test.go"}, 476 testSeed: 0, 477 currentlyApproved: edcApprovers.Intersection(dApprovers), 478 isApproved: true, 479 }, 480 { 481 testName: "A, B, C; Nothing Approved", 482 filenames: []string{"a/test.go", "b/test.go", "c/test"}, 483 testSeed: 0, 484 currentlyApproved: sets.New[string](), 485 isApproved: false, 486 }, 487 { 488 testName: "A, B, C; Partially Approved", 489 filenames: []string{"a/test.go", "b/test.go", "c/test"}, 490 testSeed: 0, 491 currentlyApproved: aApprovers.Union(bApprovers), 492 isApproved: false, 493 }, 494 { 495 testName: "A, B, C; Approved At the Root", 496 filenames: []string{"a/test.go", "b/test.go", "c/test"}, 497 testSeed: 0, 498 currentlyApproved: rootApprovers, 499 isApproved: true, 500 }, 501 { 502 testName: "A, B, C; Approved At the Leaves", 503 filenames: []string{"a/test.go", "b/test.go", "c/test"}, 504 testSeed: 0, 505 currentlyApproved: sets.New[string]("Anne", "Ben", "Carol"), 506 isApproved: true, 507 }, 508 { 509 testName: "File in folder with AutoApproveUnownedSubfolders does not get approved", 510 filenames: []string{"a/test.go"}, 511 autoApproveUnownedSubfoldersMap: map[string]bool{"a": true}, 512 isApproved: false, 513 }, 514 { 515 testName: "Subfolder in folder with AutoApproveUnownedSubfolders gets approved", 516 filenames: []string{"a/new-folder/test.go"}, 517 autoApproveUnownedSubfoldersMap: map[string]bool{"a": true}, 518 isApproved: true, 519 }, 520 { 521 testName: "Subfolder in folder with AutoApproveUnownedSubfolders whose ownersfile has no approvers gets approved", 522 filenames: []string{"d/new-folder/test.go"}, 523 autoApproveUnownedSubfoldersMap: map[string]bool{"d": true}, 524 isApproved: true, 525 }, 526 { 527 testName: "Subfolder in folder with AutoApproveUnownedSubfolders and other unapproved file does not get approved", 528 filenames: []string{"b/unapproved.go", "a/new-folder/test.go"}, 529 autoApproveUnownedSubfoldersMap: map[string]bool{"a": true}, 530 isApproved: false, 531 }, 532 { 533 testName: "Subfolder in folder with AutoApproveUnownedSubfolders and approved file, approved", 534 filenames: []string{"b/approved.go", "a/new-folder/test.go"}, 535 autoApproveUnownedSubfoldersMap: map[string]bool{"a": true}, 536 currentlyApproved: sets.New[string](sets.List(bApprovers)[0]), 537 isApproved: true, 538 }, 539 { 540 testName: "Nested subfolder in folder with AutoApproveUnownedSubfolders gets approved", 541 filenames: []string{"a/new-folder/child/grandchild/test.go"}, 542 autoApproveUnownedSubfoldersMap: map[string]bool{"a": true}, 543 isApproved: true, 544 }, 545 { 546 testName: "Change in folder with Owners whose parent has AutoApproveUnownedSubfolders does not get approved", 547 filenames: []string{"a/d/new-file.go"}, 548 autoApproveUnownedSubfoldersMap: map[string]bool{"a": true}, 549 isApproved: false, 550 }, 551 } 552 553 for _, test := range tests { 554 t.Run(test.testName, func(t *testing.T) { 555 testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap, func(fr *FakeRepo) { fr.autoApproveUnownedSubfolders = test.autoApproveUnownedSubfoldersMap }), seed: test.testSeed, log: logrus.WithField("plugin", "some_plugin")}) 556 for approver := range test.currentlyApproved { 557 testApprovers.AddApprover(approver, "REFERENCE", false) 558 } 559 calculated := testApprovers.IsApproved() 560 if test.isApproved != calculated { 561 t.Errorf("Failed for test %v. Expected Approval Status: %v. Found %v", test.testName, test.isApproved, calculated) 562 } 563 }) 564 } 565 } 566 567 func TestIsApprovedWithIssue(t *testing.T) { 568 aApprovers := sets.New[string]("Author", "Anne", "Carl") 569 bApprovers := sets.New[string]("Bill", "Carl") 570 FakeRepoMap := map[string]sets.Set[string]{"a": aApprovers, "b": bApprovers} 571 tests := []struct { 572 testName string 573 filenames []string 574 currentlyApproved map[string]bool 575 associatedIssue int 576 isApproved bool 577 }{ 578 { 579 testName: "Empty PR", 580 filenames: []string{}, 581 currentlyApproved: map[string]bool{}, 582 associatedIssue: 0, 583 isApproved: false, 584 }, 585 { 586 testName: "Single file missing issue", 587 filenames: []string{"a/file.go"}, 588 currentlyApproved: map[string]bool{"Carl": false}, 589 associatedIssue: 0, 590 isApproved: false, 591 }, 592 { 593 testName: "Single file no-issue", 594 filenames: []string{"a/file.go"}, 595 currentlyApproved: map[string]bool{"Carl": true}, 596 associatedIssue: 0, 597 isApproved: true, 598 }, 599 { 600 testName: "Single file with issue", 601 filenames: []string{"a/file.go"}, 602 currentlyApproved: map[string]bool{"Carl": false}, 603 associatedIssue: 100, 604 isApproved: true, 605 }, 606 { 607 testName: "Two files missing issue", 608 filenames: []string{"a/file.go", "b/file2.go"}, 609 currentlyApproved: map[string]bool{"Carl": false}, 610 associatedIssue: 0, 611 isApproved: false, 612 }, 613 { 614 testName: "Two files no-issue", 615 filenames: []string{"a/file.go", "b/file2.go"}, 616 currentlyApproved: map[string]bool{"Carl": true}, 617 associatedIssue: 0, 618 isApproved: true, 619 }, 620 { 621 testName: "Two files two no-issue two approvers", 622 filenames: []string{"a/file.go", "b/file2.go"}, 623 currentlyApproved: map[string]bool{"Anne": true, "Bill": true}, 624 associatedIssue: 0, 625 isApproved: true, 626 }, 627 { 628 testName: "Two files one no-issue two approvers", 629 filenames: []string{"a/file.go", "b/file2.go"}, 630 currentlyApproved: map[string]bool{"Anne": true, "Bill": false}, 631 associatedIssue: 0, 632 isApproved: true, 633 }, 634 { 635 testName: "Two files missing issue two approvers", 636 filenames: []string{"a/file.go", "b/file2.go"}, 637 currentlyApproved: map[string]bool{"Anne": false, "Bill": false}, 638 associatedIssue: 0, 639 isApproved: false, 640 }, 641 { 642 testName: "Self approval (implicit) missing issue", 643 filenames: []string{"a/file.go"}, 644 currentlyApproved: map[string]bool{}, 645 associatedIssue: 0, 646 isApproved: false, 647 }, 648 { 649 testName: "Self approval (implicit) with issue", 650 filenames: []string{"a/file.go"}, 651 currentlyApproved: map[string]bool{}, 652 associatedIssue: 10, 653 isApproved: true, 654 }, 655 { 656 testName: "Self approval (explicit) missing issue", 657 filenames: []string{"a/file.go"}, 658 currentlyApproved: map[string]bool{"Author": false}, 659 associatedIssue: 0, 660 isApproved: false, 661 }, 662 { 663 testName: "Self approval (explicit) no-issue", 664 filenames: []string{"a/file.go"}, 665 currentlyApproved: map[string]bool{"Author": true}, 666 associatedIssue: 0, 667 isApproved: true, 668 }, 669 { 670 testName: "Self approval (explicit) missing issue, two files", 671 filenames: []string{"a/file.go", "b/file2.go"}, 672 currentlyApproved: map[string]bool{"Author": false, "Bill": false}, 673 associatedIssue: 0, 674 isApproved: false, 675 }, 676 { 677 testName: "Self approval (explicit) no-issue from author, two files", 678 filenames: []string{"a/file.go", "b/file2.go"}, 679 currentlyApproved: map[string]bool{"Author": true, "Bill": false}, 680 associatedIssue: 0, 681 isApproved: true, 682 }, 683 { 684 testName: "Self approval (explicit) no-issue from friend, two files", 685 filenames: []string{"a/file.go", "b/file2.go"}, 686 currentlyApproved: map[string]bool{"Author": false, "Bill": true}, 687 associatedIssue: 0, 688 isApproved: true, 689 }, 690 } 691 692 for _, test := range tests { 693 testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap), seed: 0, log: logrus.WithField("plugin", "some_plugin")}) 694 testApprovers.RequireIssue = true 695 testApprovers.AssociatedIssue = test.associatedIssue 696 for approver, noissue := range test.currentlyApproved { 697 testApprovers.AddApprover(approver, "REFERENCE", noissue) 698 } 699 testApprovers.AddAuthorSelfApprover("Author", "REFERENCE", false) 700 calculated := testApprovers.IsApproved() 701 if test.isApproved != calculated { 702 t.Errorf("Failed for test %v. Expected Approval Status: %v. Found %v", test.testName, test.isApproved, calculated) 703 } 704 } 705 } 706 707 func TestGetFilesApprovers(t *testing.T) { 708 tests := []struct { 709 testName string 710 filenames []string 711 approvers []string 712 owners map[string]sets.Set[string] 713 expectedStatus map[string]sets.Set[string] 714 }{ 715 { 716 testName: "Empty PR", 717 filenames: []string{}, 718 approvers: []string{}, 719 owners: map[string]sets.Set[string]{}, 720 expectedStatus: map[string]sets.Set[string]{}, 721 }, 722 { 723 testName: "No approvers", 724 filenames: []string{"a/a", "c"}, 725 approvers: []string{}, 726 owners: map[string]sets.Set[string]{"": sets.New[string]("RootOwner")}, 727 expectedStatus: map[string]sets.Set[string]{"": {}}, 728 }, 729 { 730 testName: "Approvers approves some", 731 filenames: []string{ 732 "a/a", 733 "c/c", 734 }, 735 approvers: []string{"CApprover"}, 736 owners: map[string]sets.Set[string]{ 737 "a": sets.New[string]("AApprover"), 738 "c": sets.New[string]("CApprover"), 739 }, 740 expectedStatus: map[string]sets.Set[string]{ 741 "a": {}, 742 "c": sets.New[string]("CApprover"), 743 }, 744 }, 745 { 746 testName: "Multiple approvers", 747 filenames: []string{ 748 "a/a", 749 "c/c", 750 }, 751 approvers: []string{"RootApprover", "CApprover"}, 752 owners: map[string]sets.Set[string]{ 753 "": sets.New[string]("RootApprover"), 754 "a": sets.New[string]("AApprover"), 755 "c": sets.New[string]("CApprover"), 756 }, 757 expectedStatus: map[string]sets.Set[string]{ 758 "a": sets.New[string]("RootApprover"), 759 "c": sets.New[string]("RootApprover", "CApprover"), 760 }, 761 }, 762 { 763 testName: "Case-insensitive approvers", 764 filenames: []string{"file"}, 765 approvers: []string{"RootApprover"}, 766 owners: map[string]sets.Set[string]{"": sets.New[string]("rOOtaPProver")}, 767 expectedStatus: map[string]sets.Set[string]{"": sets.New[string]("RootApprover")}, 768 }, 769 } 770 771 for _, test := range tests { 772 testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(test.owners), log: logrus.WithField("plugin", "some_plugin")}) 773 for _, approver := range test.approvers { 774 testApprovers.AddApprover(approver, "REFERENCE", false) 775 } 776 calculated := testApprovers.GetFilesApprovers() 777 if !reflect.DeepEqual(test.expectedStatus, calculated) { 778 t.Errorf("Failed for test %v. Expected approval status: %v. Found %v", test.testName, test.expectedStatus, calculated) 779 } 780 } 781 } 782 783 func TestGetMessage(t *testing.T) { 784 ap := NewApprovers( 785 Owners{ 786 filenames: []string{"a/a.go", "b/b.go"}, 787 repo: createFakeRepo(map[string]sets.Set[string]{ 788 "a": sets.New[string]("Alice"), 789 "b": sets.New[string]("Bill"), 790 }), 791 log: logrus.WithField("plugin", "some_plugin"), 792 }, 793 ) 794 ap.RequireIssue = true 795 ap.AddApprover("Bill", "REFERENCE", false) 796 797 want := `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 798 799 This pull-request has been approved by: *<a href="REFERENCE" title="Approved">Bill</a>* 800 **Once this PR has been reviewed and has the lgtm label**, please assign [alice](https://github.com/alice) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process). 801 802 *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + ` 803 804 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=org%2Frepo). 805 806 <details open> 807 Needs approval from an approver in each of these files: 808 809 - **[a/OWNERS](https://github.com/org/repo/blob/dev/a/OWNERS)** 810 - ~~[b/OWNERS](https://github.com/org/repo/blob/dev/b/OWNERS)~~ [Bill] 811 812 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 813 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 814 </details> 815 <!-- META={"approvers":["alice"]} -->` 816 if got := GetMessage(ap, &url.URL{Scheme: "https", Host: "github.com"}, "https://go.k8s.io/bot-commands", "https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process", "org", "repo", "dev"); got == nil { 817 t.Error("GetMessage() failed") 818 } else if *got != want { 819 t.Errorf("GetMessage() = %+v, want = %+v", *got, want) 820 } 821 822 ap = NewApprovers( 823 Owners{ 824 filenames: []string{"a/a.go", "b/b.go", "c/c.go"}, 825 repo: createFakeRepo(map[string]sets.Set[string]{ 826 "a": sets.New[string]("Alice"), 827 "b": sets.New[string]("Bill"), 828 "c": sets.New[string]("Bob"), 829 }), 830 log: logrus.WithField("plugin", "some_plugin"), 831 }, 832 ) 833 ap.RequireIssue = true 834 ap.AddApprover("Bill", "REFERENCE", false) 835 836 want = `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 837 838 This pull-request has been approved by: *<a href="REFERENCE" title="Approved">Bill</a>* 839 **Once this PR has been reviewed and has the lgtm label**, please assign [alice](https://github.com/alice), [bob](https://github.com/bob) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process). 840 841 *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + ` 842 843 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=org%2Frepo). 844 845 <details open> 846 Needs approval from an approver in each of these files: 847 848 - **[a/OWNERS](https://github.com/org/repo/blob/dev/a/OWNERS)** 849 - ~~[b/OWNERS](https://github.com/org/repo/blob/dev/b/OWNERS)~~ [Bill] 850 - **[c/OWNERS](https://github.com/org/repo/blob/dev/c/OWNERS)** 851 852 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 853 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 854 </details> 855 <!-- META={"approvers":["alice","bob"]} -->` 856 if got := GetMessage(ap, &url.URL{Scheme: "https", Host: "github.com"}, "https://go.k8s.io/bot-commands", "https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process", "org", "repo", "dev"); got == nil { 857 t.Error("GetMessage() failed") 858 } else if *got != want { 859 t.Errorf("GetMessage() = %+v, want = %+v", *got, want) 860 } 861 } 862 863 func TestGetMessageAllApproved(t *testing.T) { 864 ap := NewApprovers( 865 Owners{ 866 filenames: []string{"a/a.go", "b/b.go"}, 867 repo: createFakeRepo(map[string]sets.Set[string]{ 868 "a": sets.New[string]("Alice"), 869 "b": sets.New[string]("Bill"), 870 }), 871 log: logrus.WithField("plugin", "some_plugin"), 872 }, 873 ) 874 ap.RequireIssue = true 875 ap.AddApprover("Alice", "REFERENCE", false) 876 ap.AddLGTMer("Bill", "REFERENCE", false) 877 878 want := `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 879 880 This pull-request has been approved by: *<a href="REFERENCE" title="Approved">Alice</a>*, *<a href="REFERENCE" title="LGTM">Bill</a>* 881 882 *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + ` 883 884 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=org%2Frepo). 885 886 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 887 888 <details > 889 Needs approval from an approver in each of these files: 890 891 - ~~[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)~~ [Alice] 892 - ~~[b/OWNERS](https://github.com/org/repo/blob/master/b/OWNERS)~~ [Bill] 893 894 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 895 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 896 </details> 897 <!-- META={"approvers":[]} -->` 898 if got := GetMessage(ap, &url.URL{Scheme: "https", Host: "github.com"}, "https://go.k8s.io/bot-commands", "https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process", "org", "repo", "master"); got == nil { 899 t.Error("GetMessage() failed") 900 } else if *got != want { 901 t.Errorf("GetMessage() = %+v, want = %+v", *got, want) 902 } 903 } 904 905 func TestGetMessageNoneApproved(t *testing.T) { 906 ap := NewApprovers( 907 Owners{ 908 filenames: []string{"a/a.go", "b/b.go"}, 909 repo: createFakeRepo(map[string]sets.Set[string]{ 910 "a": sets.New[string]("Alice"), 911 "b": sets.New[string]("Bill"), 912 }), 913 log: logrus.WithField("plugin", "some_plugin"), 914 }, 915 ) 916 ap.AddAuthorSelfApprover("John", "REFERENCE", false) 917 ap.RequireIssue = true 918 want := `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 919 920 This pull-request has been approved by: *<a href="REFERENCE" title="Author self-approved">John</a>* 921 **Once this PR has been reviewed and has the lgtm label**, please assign [alice](https://github.com/alice), [bill](https://github.com/bill) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process). 922 923 *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + ` 924 925 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=org%2Frepo). 926 927 <details open> 928 Needs approval from an approver in each of these files: 929 930 - **[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)** 931 - **[b/OWNERS](https://github.com/org/repo/blob/master/b/OWNERS)** 932 933 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 934 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 935 </details> 936 <!-- META={"approvers":["alice","bill"]} -->` 937 if got := GetMessage(ap, &url.URL{Scheme: "https", Host: "github.com"}, "https://go.k8s.io/bot-commands", "https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process", "org", "repo", "master"); got == nil { 938 t.Error("GetMessage() failed") 939 } else if *got != want { 940 t.Errorf("GetMessage() = %+v, want = %+v", *got, want) 941 } 942 } 943 944 func TestGetMessageApprovedIssueAssociated(t *testing.T) { 945 ap := NewApprovers( 946 Owners{ 947 filenames: []string{"a/a.go", "b/b.go"}, 948 repo: createFakeRepo(map[string]sets.Set[string]{ 949 "a": sets.New[string]("Alice"), 950 "b": sets.New[string]("Bill"), 951 }), 952 log: logrus.WithField("plugin", "some_plugin"), 953 }, 954 ) 955 ap.RequireIssue = true 956 ap.AssociatedIssue = 12345 957 ap.AddAuthorSelfApprover("John", "REFERENCE", false) 958 ap.AddApprover("Bill", "REFERENCE", false) 959 ap.AddApprover("Alice", "REFERENCE", false) 960 961 want := `[APPROVALNOTIFIER] This PR is **APPROVED** 962 963 This pull-request has been approved by: *<a href="REFERENCE" title="Approved">Alice</a>*, *<a href="REFERENCE" title="Approved">Bill</a>*, *<a href="REFERENCE" title="Author self-approved">John</a>* 964 965 Associated issue: *#12345* 966 967 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=org%2Frepo). 968 969 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 970 971 <details > 972 Needs approval from an approver in each of these files: 973 974 - ~~[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)~~ [Alice] 975 - ~~[b/OWNERS](https://github.com/org/repo/blob/master/b/OWNERS)~~ [Bill] 976 977 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 978 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 979 </details> 980 <!-- META={"approvers":[]} -->` 981 if got := GetMessage(ap, &url.URL{Scheme: "https", Host: "github.com"}, "https://go.k8s.io/bot-commands", "https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process", "org", "repo", "master"); got == nil { 982 t.Error("GetMessage() failed") 983 } else if *got != want { 984 t.Errorf("GetMessage() = %+v, want = %+v", *got, want) 985 } 986 } 987 988 func TestGetMessageApprovedNoIssueByPassed(t *testing.T) { 989 ap := NewApprovers( 990 Owners{ 991 filenames: []string{"a/a.go", "b/b.md"}, 992 repo: createFakeRepo(map[string]sets.Set[string]{ 993 "a": sets.New[string]("Alice"), 994 "b": sets.New[string]("Bill"), 995 }), 996 log: logrus.WithField("plugin", "some_plugin"), 997 }, 998 ) 999 ap.RequireIssue = true 1000 ap.AddAuthorSelfApprover("John", "REFERENCE", false) 1001 ap.AddApprover("Bill", "REFERENCE", true) 1002 ap.AddApprover("Alice", "REFERENCE", true) 1003 1004 want := `[APPROVALNOTIFIER] This PR is **APPROVED** 1005 1006 This pull-request has been approved by: *<a href="REFERENCE" title="Approved">Alice</a>*, *<a href="REFERENCE" title="Approved">Bill</a>*, *<a href="REFERENCE" title="Author self-approved">John</a>* 1007 1008 Associated issue requirement bypassed by: *<a href="REFERENCE" title="Approved">Alice</a>*, *<a href="REFERENCE" title="Approved">Bill</a>* 1009 1010 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=org%2Frepo). 1011 1012 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 1013 1014 <details > 1015 Needs approval from an approver in each of these files: 1016 1017 - ~~[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)~~ [Alice] 1018 - ~~[b/OWNERS](https://github.com/org/repo/blob/master/b/OWNERS)~~ [Bill] 1019 1020 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 1021 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 1022 </details> 1023 <!-- META={"approvers":[]} -->` 1024 if got := GetMessage(ap, &url.URL{Scheme: "https", Host: "github.com"}, "https://go.k8s.io/bot-commands", "https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process", "org", "repo", "master"); got == nil { 1025 t.Error("GetMessage() failed") 1026 } else if *got != want { 1027 t.Errorf("GetMessage() = %+v, want = %+v", *got, want) 1028 } 1029 } 1030 1031 func TestGetMessageMDOwners(t *testing.T) { 1032 ap := NewApprovers( 1033 Owners{ 1034 filenames: []string{"a/a.go", "b/README.md"}, 1035 repo: createFakeRepo(map[string]sets.Set[string]{ 1036 "a": sets.New[string]("Alice"), 1037 "b": sets.New[string]("Bill"), 1038 "b/README.md": sets.New[string]("Doctor"), 1039 }), 1040 log: logrus.WithField("plugin", "some_plugin"), 1041 }, 1042 ) 1043 ap.AddAuthorSelfApprover("John", "REFERENCE", false) 1044 ap.RequireIssue = true 1045 want := `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 1046 1047 This pull-request has been approved by: *<a href="REFERENCE" title="Author self-approved">John</a>* 1048 **Once this PR has been reviewed and has the lgtm label**, please assign [alice](https://github.com/alice), [doctor](https://github.com/doctor) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process). 1049 1050 *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + ` 1051 1052 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=org%2Frepo). 1053 1054 <details open> 1055 Needs approval from an approver in each of these files: 1056 1057 - **[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)** 1058 - **[b/README.md](https://github.com/org/repo/blob/master/b/README.md)** 1059 1060 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 1061 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 1062 </details> 1063 <!-- META={"approvers":["alice","doctor"]} -->` 1064 if got := GetMessage(ap, &url.URL{Scheme: "https", Host: "github.com"}, "https://go.k8s.io/bot-commands", "https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process", "org", "repo", "master"); got == nil { 1065 t.Error("GetMessage() failed") 1066 } else if *got != want { 1067 t.Errorf("GetMessage() = %+v, want = %+v", *got, want) 1068 } 1069 } 1070 1071 func TestGetMessageDifferentGitHubLink(t *testing.T) { 1072 ap := NewApprovers( 1073 Owners{ 1074 filenames: []string{"a/a.go", "b/README.md"}, 1075 repo: createFakeRepo(map[string]sets.Set[string]{ 1076 "a": sets.New[string]("Alice"), 1077 "b": sets.New[string]("Bill"), 1078 "b/README.md": sets.New[string]("Doctor"), 1079 }), 1080 log: logrus.WithField("plugin", "some_plugin"), 1081 }, 1082 ) 1083 ap.AddAuthorSelfApprover("John", "REFERENCE", false) 1084 ap.RequireIssue = true 1085 want := `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 1086 1087 This pull-request has been approved by: *<a href="REFERENCE" title="Author self-approved">John</a>* 1088 **Once this PR has been reviewed and has the lgtm label**, please assign [alice](https://github.com/alice), [doctor](https://github.com/doctor) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process). 1089 1090 *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + ` 1091 1092 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=org%2Frepo). 1093 1094 <details open> 1095 Needs approval from an approver in each of these files: 1096 1097 - **[a/OWNERS](https://github.mycorp.com/org/repo/blob/master/a/OWNERS)** 1098 - **[b/README.md](https://github.mycorp.com/org/repo/blob/master/b/README.md)** 1099 1100 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 1101 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 1102 </details> 1103 <!-- META={"approvers":["alice","doctor"]} -->` 1104 if got := GetMessage(ap, &url.URL{Scheme: "https", Host: "github.mycorp.com"}, "https://go.k8s.io/bot-commands", "https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process", "org", "repo", "master"); got == nil { 1105 t.Error("GetMessage() failed") 1106 } else if *got != want { 1107 t.Errorf("GetMessage() = %+v, want = %+v", *got, want) 1108 } 1109 }