sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/crier/reporters/slack/reporter_test.go (about) 1 /* 2 Copyright 2019 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 slack 18 19 import ( 20 "context" 21 "testing" 22 23 "github.com/sirupsen/logrus" 24 25 v1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 26 "sigs.k8s.io/prow/pkg/config" 27 ) 28 29 func TestShouldReport(t *testing.T) { 30 boolPtr := func(b bool) *bool { 31 return &b 32 } 33 testCases := []struct { 34 name string 35 config config.SlackReporter 36 pj *v1.ProwJob 37 expected bool 38 }{ 39 { 40 name: "Presubmit Job should report", 41 config: config.SlackReporter{ 42 JobTypesToReport: []v1.ProwJobType{v1.PresubmitJob}, 43 SlackReporterConfig: v1.SlackReporterConfig{ 44 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 45 }, 46 }, 47 pj: &v1.ProwJob{ 48 Spec: v1.ProwJobSpec{ 49 Type: v1.PresubmitJob, 50 }, 51 Status: v1.ProwJobStatus{ 52 State: v1.SuccessState, 53 }, 54 }, 55 expected: true, 56 }, 57 { 58 name: "Wrong job type should not report", 59 config: config.SlackReporter{ 60 JobTypesToReport: []v1.ProwJobType{v1.PostsubmitJob}, 61 SlackReporterConfig: v1.SlackReporterConfig{ 62 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 63 }, 64 }, 65 pj: &v1.ProwJob{ 66 Spec: v1.ProwJobSpec{ 67 Type: v1.PresubmitJob, 68 }, 69 Status: v1.ProwJobStatus{ 70 State: v1.SuccessState, 71 }, 72 }, 73 expected: false, 74 }, 75 { 76 name: "Successful Job should report", 77 config: config.SlackReporter{ 78 JobTypesToReport: []v1.ProwJobType{v1.PostsubmitJob}, 79 SlackReporterConfig: v1.SlackReporterConfig{ 80 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 81 }, 82 }, 83 pj: &v1.ProwJob{ 84 Spec: v1.ProwJobSpec{ 85 Type: v1.PostsubmitJob, 86 }, 87 Status: v1.ProwJobStatus{ 88 State: v1.SuccessState, 89 }, 90 }, 91 expected: true, 92 }, 93 { 94 name: "Successful Job with report:false should not report", 95 config: config.SlackReporter{ 96 JobTypesToReport: []v1.ProwJobType{v1.PostsubmitJob}, 97 SlackReporterConfig: v1.SlackReporterConfig{ 98 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 99 Report: boolPtr(false), 100 }, 101 }, 102 pj: &v1.ProwJob{ 103 Spec: v1.ProwJobSpec{ 104 Type: v1.PostsubmitJob, 105 }, 106 Status: v1.ProwJobStatus{ 107 State: v1.SuccessState, 108 }, 109 }, 110 expected: false, 111 }, 112 { 113 name: "Successful Job with report:true should report", 114 config: config.SlackReporter{ 115 JobTypesToReport: []v1.ProwJobType{v1.PostsubmitJob}, 116 SlackReporterConfig: v1.SlackReporterConfig{ 117 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 118 Report: boolPtr(true), 119 }, 120 }, 121 pj: &v1.ProwJob{ 122 Spec: v1.ProwJobSpec{ 123 Type: v1.PostsubmitJob, 124 }, 125 Status: v1.ProwJobStatus{ 126 State: v1.SuccessState, 127 }, 128 }, 129 expected: true, 130 }, 131 { 132 // Note: this is impossible to hit, as roundtrip with `omitempty` 133 // would never result in empty slice. 134 name: "Empty job config settings negate global", 135 config: config.SlackReporter{ 136 JobTypesToReport: []v1.ProwJobType{v1.PostsubmitJob}, 137 SlackReporterConfig: v1.SlackReporterConfig{ 138 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 139 }, 140 }, 141 pj: &v1.ProwJob{ 142 Spec: v1.ProwJobSpec{ 143 Type: v1.PostsubmitJob, 144 ReporterConfig: &v1.ReporterConfig{ 145 Slack: &v1.SlackReporterConfig{JobStatesToReport: []v1.ProwJobState{}}, 146 }, 147 }, 148 Status: v1.ProwJobStatus{ 149 State: v1.SuccessState, 150 }, 151 }, 152 expected: false, 153 }, 154 { 155 name: "Nil job config settings does not negate global", 156 config: config.SlackReporter{ 157 JobTypesToReport: []v1.ProwJobType{v1.PostsubmitJob}, 158 SlackReporterConfig: v1.SlackReporterConfig{ 159 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 160 }, 161 }, 162 pj: &v1.ProwJob{ 163 Spec: v1.ProwJobSpec{ 164 Type: v1.PostsubmitJob, 165 ReporterConfig: &v1.ReporterConfig{ 166 Slack: &v1.SlackReporterConfig{JobStatesToReport: nil}, 167 }, 168 }, 169 Status: v1.ProwJobStatus{ 170 State: v1.SuccessState, 171 }, 172 }, 173 expected: true, 174 }, 175 { 176 name: "Successful Job should not report", 177 config: config.SlackReporter{ 178 JobTypesToReport: []v1.ProwJobType{v1.PostsubmitJob}, 179 SlackReporterConfig: v1.SlackReporterConfig{ 180 JobStatesToReport: []v1.ProwJobState{v1.PendingState}, 181 }, 182 }, 183 pj: &v1.ProwJob{ 184 Spec: v1.ProwJobSpec{ 185 Type: v1.PostsubmitJob, 186 }, 187 Status: v1.ProwJobStatus{ 188 State: v1.SuccessState, 189 }, 190 }, 191 expected: false, 192 }, 193 { 194 name: "Job with channel config should ignore the JobTypesToReport config", 195 config: config.SlackReporter{ 196 JobTypesToReport: []v1.ProwJobType{}, 197 SlackReporterConfig: v1.SlackReporterConfig{ 198 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 199 }, 200 }, 201 pj: &v1.ProwJob{ 202 Spec: v1.ProwJobSpec{ 203 Type: v1.PostsubmitJob, 204 ReporterConfig: &v1.ReporterConfig{ 205 Slack: &v1.SlackReporterConfig{Channel: "whatever-channel"}, 206 }, 207 }, 208 Status: v1.ProwJobStatus{ 209 State: v1.SuccessState, 210 }, 211 }, 212 expected: true, 213 }, 214 { 215 name: "JobStatesToReport in Job config should override the one in Prow config", 216 config: config.SlackReporter{ 217 JobTypesToReport: []v1.ProwJobType{}, 218 SlackReporterConfig: v1.SlackReporterConfig{ 219 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 220 }, 221 }, 222 pj: &v1.ProwJob{ 223 Spec: v1.ProwJobSpec{ 224 Type: v1.PostsubmitJob, 225 ReporterConfig: &v1.ReporterConfig{ 226 Slack: &v1.SlackReporterConfig{ 227 Channel: "whatever-channel", 228 JobStatesToReport: []v1.ProwJobState{v1.FailureState, v1.PendingState}, 229 }, 230 }, 231 }, 232 Status: v1.ProwJobStatus{ 233 State: v1.FailureState, 234 }, 235 }, 236 expected: true, 237 }, 238 { 239 name: "Job with channel config but does not have matched state in Prow config should not report", 240 config: config.SlackReporter{ 241 JobTypesToReport: []v1.ProwJobType{}, 242 SlackReporterConfig: v1.SlackReporterConfig{ 243 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 244 }, 245 }, 246 pj: &v1.ProwJob{ 247 Spec: v1.ProwJobSpec{ 248 Type: v1.PostsubmitJob, 249 ReporterConfig: &v1.ReporterConfig{ 250 Slack: &v1.SlackReporterConfig{Channel: "whatever-channel"}, 251 }, 252 }, 253 Status: v1.ProwJobStatus{ 254 State: v1.PendingState, 255 }, 256 }, 257 expected: false, 258 }, 259 { 260 name: "Job with channel and state config where the state does not match, should not report", 261 config: config.SlackReporter{ 262 JobTypesToReport: []v1.ProwJobType{}, 263 SlackReporterConfig: v1.SlackReporterConfig{ 264 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 265 }, 266 }, 267 pj: &v1.ProwJob{ 268 Spec: v1.ProwJobSpec{ 269 Type: v1.PostsubmitJob, 270 ReporterConfig: &v1.ReporterConfig{ 271 Slack: &v1.SlackReporterConfig{ 272 Channel: "whatever-channel", 273 JobStatesToReport: []v1.ProwJobState{v1.FailureState, v1.PendingState}, 274 }, 275 }, 276 }, 277 Status: v1.ProwJobStatus{ 278 State: v1.SuccessState, 279 }, 280 }, 281 expected: false, 282 }, 283 { 284 name: "Empty config should not report", 285 config: config.SlackReporter{}, 286 pj: &v1.ProwJob{ 287 Spec: v1.ProwJobSpec{ 288 Type: v1.PostsubmitJob, 289 }, 290 Status: v1.ProwJobStatus{ 291 State: v1.SuccessState, 292 }, 293 }, 294 expected: false, 295 }, 296 } 297 298 for _, tc := range testCases { 299 cfgGetter := func(*v1.Refs) config.SlackReporter { 300 return tc.config 301 } 302 t.Run(tc.name, func(t *testing.T) { 303 reporter := &slackReporter{ 304 config: cfgGetter, 305 } 306 307 if result := reporter.ShouldReport(context.Background(), logrus.NewEntry(logrus.StandardLogger()), tc.pj); result != tc.expected { 308 t.Errorf("expected result to be %t but was %t", tc.expected, result) 309 } 310 }) 311 } 312 } 313 314 func TestReloadsConfig(t *testing.T) { 315 cfg := config.SlackReporter{} 316 cfgGetter := func(*v1.Refs) config.SlackReporter { 317 return cfg 318 } 319 320 pj := &v1.ProwJob{ 321 Spec: v1.ProwJobSpec{ 322 Type: v1.PostsubmitJob, 323 }, 324 Status: v1.ProwJobStatus{ 325 State: v1.FailureState, 326 }, 327 } 328 329 reporter := &slackReporter{ 330 config: cfgGetter, 331 } 332 333 if shouldReport := reporter.ShouldReport(context.Background(), logrus.NewEntry(logrus.StandardLogger()), pj); shouldReport { 334 t.Error("Did expect shouldReport to be false") 335 } 336 337 cfg.JobStatesToReport = []v1.ProwJobState{v1.FailureState} 338 cfg.JobTypesToReport = []v1.ProwJobType{v1.PostsubmitJob} 339 340 if shouldReport := reporter.ShouldReport(context.Background(), logrus.NewEntry(logrus.StandardLogger()), pj); !shouldReport { 341 t.Error("Did expect shouldReport to be true after config change") 342 } 343 } 344 345 func TestUsesChannelOverrideFromJob(t *testing.T) { 346 testCases := []struct { 347 name string 348 config func() config.Config 349 pj *v1.ProwJob 350 wantHost string 351 wantChannel string 352 emptyExpected bool 353 }{ 354 { 355 name: "No job-level config, use global default", 356 config: func() config.Config { 357 slackCfg := map[string]config.SlackReporter{ 358 "*": { 359 SlackReporterConfig: v1.SlackReporterConfig{ 360 Host: "global-default-host", 361 Channel: "global-default", 362 }, 363 }, 364 } 365 return config.Config{ 366 ProwConfig: config.ProwConfig{ 367 SlackReporterConfigs: slackCfg, 368 }, 369 } 370 }, 371 pj: &v1.ProwJob{Spec: v1.ProwJobSpec{}}, 372 wantHost: "global-default-host", 373 wantChannel: "global-default", 374 }, 375 { 376 name: "org/repo for ref exists in config, use it", 377 config: func() config.Config { 378 slackCfg := map[string]config.SlackReporter{ 379 "*": { 380 SlackReporterConfig: v1.SlackReporterConfig{ 381 Channel: "global-default", 382 }, 383 }, 384 "istio/proxy": { 385 SlackReporterConfig: v1.SlackReporterConfig{ 386 Host: "global-default-host", 387 Channel: "org-repo-config", 388 }, 389 }, 390 } 391 return config.Config{ 392 ProwConfig: config.ProwConfig{ 393 SlackReporterConfigs: slackCfg, 394 }, 395 } 396 }, 397 pj: &v1.ProwJob{ 398 Spec: v1.ProwJobSpec{ 399 Refs: &v1.Refs{ 400 Org: "istio", 401 Repo: "proxy", 402 }, 403 }}, 404 wantHost: "global-default-host", 405 wantChannel: "org-repo-config", 406 }, 407 { 408 name: "org for ref exists in config, use it", 409 config: func() config.Config { 410 slackCfg := map[string]config.SlackReporter{ 411 "*": { 412 SlackReporterConfig: v1.SlackReporterConfig{ 413 Channel: "global-default", 414 }, 415 }, 416 "istio": { 417 SlackReporterConfig: v1.SlackReporterConfig{ 418 Channel: "org-config", 419 }, 420 }, 421 } 422 return config.Config{ 423 ProwConfig: config.ProwConfig{ 424 SlackReporterConfigs: slackCfg, 425 }, 426 } 427 }, 428 pj: &v1.ProwJob{ 429 Spec: v1.ProwJobSpec{ 430 Refs: &v1.Refs{ 431 Org: "istio", 432 Repo: "proxy", 433 }, 434 }}, 435 wantHost: "*", 436 wantChannel: "org-config", 437 }, 438 { 439 name: "org/repo takes precedence over org", 440 config: func() config.Config { 441 slackCfg := map[string]config.SlackReporter{ 442 "*": { 443 SlackReporterConfig: v1.SlackReporterConfig{ 444 Channel: "global-default", 445 }, 446 }, 447 "istio": { 448 SlackReporterConfig: v1.SlackReporterConfig{ 449 Channel: "org-config", 450 }, 451 }, 452 "istio/proxy": { 453 SlackReporterConfig: v1.SlackReporterConfig{ 454 Channel: "org-repo-config", 455 }, 456 }, 457 } 458 return config.Config{ 459 ProwConfig: config.ProwConfig{ 460 SlackReporterConfigs: slackCfg, 461 }, 462 } 463 }, 464 pj: &v1.ProwJob{ 465 Spec: v1.ProwJobSpec{ 466 Refs: &v1.Refs{ 467 Org: "istio", 468 Repo: "proxy", 469 }, 470 }}, 471 wantHost: "*", 472 wantChannel: "org-repo-config", 473 }, 474 { 475 name: "Job-level config present, use it", 476 config: func() config.Config { 477 slackCfg := map[string]config.SlackReporter{ 478 "*": { 479 SlackReporterConfig: v1.SlackReporterConfig{ 480 Channel: "global-default", 481 }, 482 }, 483 "istio": { 484 SlackReporterConfig: v1.SlackReporterConfig{ 485 Channel: "org-config", 486 }, 487 }, 488 "istio/proxy": { 489 SlackReporterConfig: v1.SlackReporterConfig{ 490 Channel: "org-repo-config", 491 }, 492 }, 493 } 494 return config.Config{ 495 ProwConfig: config.ProwConfig{ 496 SlackReporterConfigs: slackCfg, 497 }, 498 } 499 }, 500 pj: &v1.ProwJob{ 501 Spec: v1.ProwJobSpec{ 502 ReporterConfig: &v1.ReporterConfig{ 503 Slack: &v1.SlackReporterConfig{ 504 Channel: "team-a", 505 }, 506 }, 507 }, 508 }, 509 wantHost: "*", 510 wantChannel: "team-a", 511 }, 512 { 513 name: "No matching slack config", 514 config: func() config.Config { 515 slackCfg := map[string]config.SlackReporter{ 516 "istio": { 517 SlackReporterConfig: v1.SlackReporterConfig{ 518 Channel: "org-config", 519 }, 520 }, 521 "istio/proxy": { 522 SlackReporterConfig: v1.SlackReporterConfig{ 523 Channel: "org-repo-config", 524 }, 525 }, 526 } 527 return config.Config{ 528 ProwConfig: config.ProwConfig{ 529 SlackReporterConfigs: slackCfg, 530 }, 531 } 532 }, 533 pj: &v1.ProwJob{ 534 Spec: v1.ProwJobSpec{ 535 Refs: &v1.Refs{ 536 Org: "unknownorg", 537 Repo: "unknownrepo", 538 }, 539 }}, 540 wantHost: "*", 541 emptyExpected: true, 542 }, 543 { 544 name: "Refs unset but extra refs exist, use it", 545 config: func() config.Config { 546 slackCfg := map[string]config.SlackReporter{ 547 "istio/proxy": { 548 SlackReporterConfig: v1.SlackReporterConfig{ 549 Channel: "org-repo-config", 550 }, 551 }, 552 } 553 return config.Config{ 554 ProwConfig: config.ProwConfig{ 555 SlackReporterConfigs: slackCfg, 556 }, 557 } 558 }, 559 pj: &v1.ProwJob{ 560 Spec: v1.ProwJobSpec{ 561 ExtraRefs: []v1.Refs{{ 562 Org: "istio", 563 Repo: "proxy", 564 }}, 565 }, 566 }, 567 wantHost: "*", 568 wantChannel: "org-repo-config", 569 }, 570 } 571 572 for _, tc := range testCases { 573 t.Run(tc.name, func(t *testing.T) { 574 cfgGetter := func(refs *v1.Refs) config.SlackReporter { 575 return tc.config().SlackReporterConfigs.GetSlackReporter(refs) 576 } 577 sr := slackReporter{ 578 config: cfgGetter, 579 } 580 581 prowSlackCfg, jobSlackCfg := sr.getConfig(tc.pj) 582 jobSlackCfg = jobSlackCfg.ApplyDefault(&prowSlackCfg.SlackReporterConfig) 583 gotHost, gotChannel := hostAndChannel(jobSlackCfg) 584 if gotHost != tc.wantHost { 585 t.Fatalf("Expected host: %q, got: %q", tc.wantHost, gotHost) 586 } 587 if gotChannel != tc.wantChannel { 588 t.Fatalf("Expected channel: %q, got: %q", tc.wantChannel, gotChannel) 589 } 590 }) 591 } 592 } 593 594 func TestShouldReportDefaultsToExtraRefs(t *testing.T) { 595 job := &v1.ProwJob{ 596 Spec: v1.ProwJobSpec{ 597 Type: v1.PeriodicJob, 598 ExtraRefs: []v1.Refs{{Org: "org"}}, 599 }, 600 Status: v1.ProwJobStatus{ 601 State: v1.SuccessState, 602 }, 603 } 604 sr := slackReporter{ 605 config: func(r *v1.Refs) config.SlackReporter { 606 if r.Org == "org" { 607 return config.SlackReporter{ 608 JobTypesToReport: []v1.ProwJobType{v1.PeriodicJob}, 609 SlackReporterConfig: v1.SlackReporterConfig{ 610 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 611 }, 612 } 613 } 614 return config.SlackReporter{} 615 }, 616 } 617 618 if !sr.ShouldReport(context.Background(), logrus.NewEntry(logrus.StandardLogger()), job) { 619 t.Fatal("expected job to report but did not") 620 } 621 } 622 623 type fakeSlackClient struct { 624 messages map[string]string 625 } 626 627 func (fsc *fakeSlackClient) WriteMessage(text, channel string) error { 628 if fsc.messages == nil { 629 fsc.messages = map[string]string{} 630 } 631 fsc.messages[channel] = text 632 return nil 633 } 634 635 var _ slackClient = &fakeSlackClient{} 636 637 func TestReportDefaultsToExtraRefs(t *testing.T) { 638 job := &v1.ProwJob{ 639 Spec: v1.ProwJobSpec{ 640 Type: v1.PeriodicJob, 641 ExtraRefs: []v1.Refs{{Org: "org"}}, 642 }, 643 Status: v1.ProwJobStatus{ 644 State: v1.SuccessState, 645 }, 646 } 647 fsc := &fakeSlackClient{} 648 sr := slackReporter{ 649 config: func(r *v1.Refs) config.SlackReporter { 650 if r.Org == "org" { 651 return config.SlackReporter{ 652 JobTypesToReport: []v1.ProwJobType{v1.PeriodicJob}, 653 SlackReporterConfig: v1.SlackReporterConfig{ 654 JobStatesToReport: []v1.ProwJobState{v1.SuccessState}, 655 Channel: "emercengy", 656 ReportTemplate: "there you go", 657 }, 658 } 659 } 660 return config.SlackReporter{} 661 }, 662 clients: map[string]slackClient{DefaultHostName: fsc}, 663 } 664 665 if _, _, err := sr.Report(context.Background(), logrus.NewEntry(logrus.StandardLogger()), job); err != nil { 666 t.Fatalf("reporting failed: %v", err) 667 } 668 if fsc.messages["emercengy"] != "there you go" { 669 t.Errorf("expected the channel 'emergency' to contain message 'there you go' but wasn't the case, all messages: %v", fsc.messages) 670 } 671 }