github.com/GoogleCloudPlatform/testgrid@v0.0.174/pkg/api/v1/summary_test.go (about) 1 /* 2 Copyright 2023 The TestGrid 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 v1 18 19 import ( 20 "context" 21 "net/http" 22 "net/http/httptest" 23 "testing" 24 25 apipb "github.com/GoogleCloudPlatform/testgrid/pb/api/v1" 26 configpb "github.com/GoogleCloudPlatform/testgrid/pb/config" 27 summarypb "github.com/GoogleCloudPlatform/testgrid/pb/summary" 28 "github.com/golang/protobuf/ptypes/timestamp" 29 "github.com/google/go-cmp/cmp" 30 "google.golang.org/protobuf/encoding/protojson" 31 "google.golang.org/protobuf/testing/protocmp" 32 ) 33 34 func TestListTabSummaries(t *testing.T) { 35 tests := []struct { 36 name string 37 config map[string]*configpb.Configuration 38 summaries map[string]*summarypb.DashboardSummary 39 req *apipb.ListTabSummariesRequest 40 want *apipb.ListTabSummariesResponse 41 expectError bool 42 }{ 43 { 44 name: "Returns an error when there's no dashboard in config", 45 config: map[string]*configpb.Configuration{ 46 "gs://default/config": {}, 47 }, 48 req: &apipb.ListTabSummariesRequest{ 49 Dashboard: "missing", 50 }, 51 expectError: true, 52 }, 53 { 54 name: "Returns an error when there's no summary for dashboard yet", 55 config: map[string]*configpb.Configuration{ 56 "gs://default/config": { 57 Dashboards: []*configpb.Dashboard{ 58 { 59 Name: "ACME", 60 DashboardTab: []*configpb.DashboardTab{ 61 { 62 Name: "me-me", 63 TestGroupName: "testgroupname", 64 }, 65 }, 66 }, 67 }, 68 }, 69 }, 70 req: &apipb.ListTabSummariesRequest{ 71 Dashboard: "acme", 72 }, 73 expectError: true, 74 }, 75 { 76 name: "Returns correct tab summaries for a dashboard, with failing tests", 77 config: map[string]*configpb.Configuration{ 78 "gs://default/config": { 79 Dashboards: []*configpb.Dashboard{ 80 { 81 Name: "Marco", 82 DashboardTab: []*configpb.DashboardTab{ 83 { 84 Name: "polo-1", 85 TestGroupName: "cheesecake", 86 }, 87 { 88 Name: "polo-2", 89 TestGroupName: "tiramisu", 90 }, 91 }, 92 }, 93 }, 94 }, 95 }, 96 summaries: map[string]*summarypb.DashboardSummary{ 97 "gs://default/summary/summary-marco": { 98 TabSummaries: []*summarypb.DashboardTabSummary{ 99 { 100 DashboardName: "Marco", 101 DashboardTabName: "polo-1", 102 Status: "1/7 tests are passing!", 103 OverallStatus: summarypb.DashboardTabSummary_FLAKY, 104 LatestGreen: "Hulk", 105 LastUpdateTimestamp: float64(915166800.916166782), 106 LastRunTimestamp: float64(915166800.916166782), 107 FailingTestSummaries: []*summarypb.FailingTestSummary{ 108 { 109 DisplayName: "top-failure-3", 110 FailCount: 3, 111 PassTimestamp: float64(914166800.33), 112 FailTimestamp: float64(914166852.33), 113 }, 114 { 115 DisplayName: "top-failure-1", 116 FailCount: 322, 117 PassTimestamp: float64(1677883461.2543), 118 FailTimestamp: float64(1677883441), 119 }, 120 { 121 DisplayName: "top-failure-2", 122 FailCount: 128, 123 PassTimestamp: float64(1677983461.354), 124 FailTimestamp: float64(1677983561), 125 }, 126 { 127 DisplayName: "top-failure-4", 128 FailCount: 64, 129 PassTimestamp: float64(1677983461.354), 130 FailTimestamp: float64(1677983561), 131 }, 132 { 133 DisplayName: "top-failure-5", 134 FailCount: 32, 135 PassTimestamp: float64(1677983461.354), 136 FailTimestamp: float64(1677983561), 137 }, 138 { 139 DisplayName: "not-top-failure-1", 140 FailCount: 2, 141 PassTimestamp: float64(1677983461.354), 142 FailTimestamp: float64(1677983561), 143 }, 144 }, 145 }, 146 { 147 DashboardName: "Marco", 148 DashboardTabName: "polo-2", 149 Status: "1/7 tests are passing!", 150 OverallStatus: summarypb.DashboardTabSummary_ACCEPTABLE, 151 LatestGreen: "Lantern", 152 LastUpdateTimestamp: float64(0.1), 153 LastRunTimestamp: float64(0.1), 154 FailingTestSummaries: []*summarypb.FailingTestSummary{ 155 { 156 DisplayName: "top-failure-1", 157 FailCount: 33, 158 PassTimestamp: float64(914166800.213), 159 FailTimestamp: float64(914176800), 160 }, 161 }, 162 }, 163 }, 164 }, 165 }, 166 req: &apipb.ListTabSummariesRequest{ 167 Dashboard: "marco", 168 }, 169 want: &apipb.ListTabSummariesResponse{ 170 TabSummaries: []*apipb.TabSummary{ 171 { 172 DashboardName: "Marco", 173 TabName: "polo-1", 174 DetailedStatusMessage: "1/7 tests are passing!", 175 OverallStatus: "FLAKY", 176 LatestPassingBuild: "Hulk", 177 LastRunTimestamp: ×tamp.Timestamp{ 178 Seconds: 915166800, 179 Nanos: 916166782, 180 }, 181 LastUpdateTimestamp: ×tamp.Timestamp{ 182 Seconds: 915166800, 183 Nanos: 916166782, 184 }, 185 FailuresSummary: &apipb.FailuresSummary{ 186 FailureStats: &apipb.FailureStats{ 187 NumFailingTests: 6, 188 }, 189 TopFailingTests: []*apipb.FailingTestInfo{ 190 { 191 DisplayName: "top-failure-1", 192 FailCount: 322, 193 PassTimestamp: ×tamp.Timestamp{ 194 Seconds: int64(1677883461), 195 Nanos: int32(254300117), 196 }, 197 FailTimestamp: ×tamp.Timestamp{ 198 Seconds: int64(1677883441), 199 }, 200 }, 201 { 202 DisplayName: "top-failure-2", 203 FailCount: 128, 204 PassTimestamp: ×tamp.Timestamp{ 205 Seconds: int64(1677983461), 206 Nanos: int32(354000091), 207 }, 208 FailTimestamp: ×tamp.Timestamp{ 209 Seconds: int64(1677983561), 210 }, 211 }, 212 { 213 DisplayName: "top-failure-4", 214 FailCount: 64, 215 PassTimestamp: ×tamp.Timestamp{ 216 Seconds: int64(1677983461), 217 Nanos: int32(354000091), 218 }, 219 FailTimestamp: ×tamp.Timestamp{ 220 Seconds: int64(1677983561), 221 }, 222 }, 223 { 224 DisplayName: "top-failure-5", 225 FailCount: 32, 226 PassTimestamp: ×tamp.Timestamp{ 227 Seconds: int64(1677983461), 228 Nanos: int32(354000091), 229 }, 230 FailTimestamp: ×tamp.Timestamp{ 231 Seconds: int64(1677983561), 232 }, 233 }, 234 { 235 DisplayName: "top-failure-3", 236 FailCount: 3, 237 PassTimestamp: ×tamp.Timestamp{ 238 Seconds: int64(914166800), 239 Nanos: int32(330000042), 240 }, 241 FailTimestamp: ×tamp.Timestamp{ 242 Seconds: int64(914166852), 243 Nanos: int32(330000042), 244 }, 245 }, 246 }, 247 }, 248 }, 249 { 250 DashboardName: "Marco", 251 TabName: "polo-2", 252 DetailedStatusMessage: "1/7 tests are passing!", 253 OverallStatus: "ACCEPTABLE", 254 LatestPassingBuild: "Lantern", 255 LastRunTimestamp: ×tamp.Timestamp{ 256 Nanos: 100000000, 257 }, 258 LastUpdateTimestamp: ×tamp.Timestamp{ 259 Nanos: 100000000, 260 }, 261 FailuresSummary: &apipb.FailuresSummary{ 262 FailureStats: &apipb.FailureStats{ 263 NumFailingTests: 1, 264 }, 265 TopFailingTests: []*apipb.FailingTestInfo{ 266 { 267 DisplayName: "top-failure-1", 268 FailCount: 33, 269 PassTimestamp: ×tamp.Timestamp{ 270 Seconds: int64(914166800), 271 Nanos: int32(213000059), 272 }, 273 FailTimestamp: ×tamp.Timestamp{ 274 Seconds: int64(914176800), 275 }, 276 }, 277 }, 278 }, 279 }, 280 }, 281 }, 282 }, 283 { 284 name: "Returns correct tab summaries for a dashboard, with healthiness info", 285 config: map[string]*configpb.Configuration{ 286 "gs://default/config": { 287 Dashboards: []*configpb.Dashboard{ 288 { 289 Name: "Marco", 290 DashboardTab: []*configpb.DashboardTab{ 291 { 292 Name: "polo-1", 293 TestGroupName: "cheesecake", 294 }, 295 { 296 Name: "polo-2", 297 TestGroupName: "tiramisu", 298 }, 299 }, 300 }, 301 }, 302 }, 303 }, 304 summaries: map[string]*summarypb.DashboardSummary{ 305 "gs://default/summary/summary-marco": { 306 TabSummaries: []*summarypb.DashboardTabSummary{ 307 { 308 DashboardName: "Marco", 309 DashboardTabName: "polo-1", 310 Status: "1/7 tests are passing!", 311 OverallStatus: summarypb.DashboardTabSummary_FLAKY, 312 LatestGreen: "Hulk", 313 LastUpdateTimestamp: float64(915166800.916166782), 314 LastRunTimestamp: float64(915166800.916166782), 315 Healthiness: &summarypb.HealthinessInfo{ 316 Start: ×tamp.Timestamp{ 317 Seconds: int64(915166800), 318 Nanos: int32(916166782), 319 }, 320 End: ×tamp.Timestamp{ 321 Seconds: int64(916166800), 322 Nanos: int32(916166782), 323 }, 324 AverageFlakiness: float32(35.0), 325 PreviousFlakiness: []float32{44.0}, 326 Tests: []*summarypb.TestInfo{ 327 { 328 DisplayName: "top-flaky-1", 329 Flakiness: float32(47.0), 330 ChangeFromLastInterval: summarypb.TestInfo_DOWN, 331 }, 332 { 333 DisplayName: "top-flaky-2", 334 Flakiness: float32(67.6), 335 ChangeFromLastInterval: summarypb.TestInfo_UP, 336 }, 337 { 338 DisplayName: "top-flaky-3", 339 Flakiness: float32(67.6), 340 ChangeFromLastInterval: summarypb.TestInfo_NO_CHANGE, 341 }, 342 { 343 DisplayName: "top-flaky-4", 344 Flakiness: float32(33.3), 345 ChangeFromLastInterval: summarypb.TestInfo_DOWN, 346 }, 347 { 348 DisplayName: "top-flaky-5", 349 Flakiness: float32(89.25), 350 ChangeFromLastInterval: summarypb.TestInfo_NO_CHANGE, 351 }, 352 { 353 DisplayName: "not-top-flaky-1", 354 Flakiness: float32(15.0), 355 ChangeFromLastInterval: summarypb.TestInfo_UP, 356 }, 357 { 358 DisplayName: "not-top-flaky-2", 359 Flakiness: float32(0.0), 360 ChangeFromLastInterval: summarypb.TestInfo_UNKNOWN, 361 }, 362 }, 363 }, 364 }, 365 { 366 DashboardName: "Marco", 367 DashboardTabName: "polo-2", 368 Status: "1/7 tests are passing!", 369 OverallStatus: summarypb.DashboardTabSummary_ACCEPTABLE, 370 LatestGreen: "Lantern", 371 LastUpdateTimestamp: float64(0.1), 372 LastRunTimestamp: float64(0.1), 373 Healthiness: &summarypb.HealthinessInfo{ 374 Start: ×tamp.Timestamp{ 375 Seconds: int64(946702801), 376 }, 377 End: ×tamp.Timestamp{ 378 Seconds: int64(946704801), 379 }, 380 AverageFlakiness: float32(15.2), 381 Tests: []*summarypb.TestInfo{ 382 { 383 DisplayName: "top-flaky-1", 384 Flakiness: float32(75.0), 385 ChangeFromLastInterval: summarypb.TestInfo_UP, 386 }, 387 { 388 DisplayName: "not-top-flaky-1", 389 Flakiness: float32(0.0), 390 ChangeFromLastInterval: summarypb.TestInfo_UNKNOWN, 391 }, 392 }, 393 }, 394 }, 395 }, 396 }, 397 }, 398 req: &apipb.ListTabSummariesRequest{ 399 Dashboard: "marco", 400 }, 401 want: &apipb.ListTabSummariesResponse{ 402 TabSummaries: []*apipb.TabSummary{ 403 { 404 DashboardName: "Marco", 405 TabName: "polo-1", 406 DetailedStatusMessage: "1/7 tests are passing!", 407 OverallStatus: "FLAKY", 408 LatestPassingBuild: "Hulk", 409 LastRunTimestamp: ×tamp.Timestamp{ 410 Seconds: 915166800, 411 Nanos: 916166782, 412 }, 413 LastUpdateTimestamp: ×tamp.Timestamp{ 414 Seconds: 915166800, 415 Nanos: 916166782, 416 }, 417 HealthinessSummary: &apipb.HealthinessSummary{ 418 HealthinessStats: &apipb.HealthinessStats{ 419 Start: ×tamp.Timestamp{ 420 Seconds: int64(915166800), 421 Nanos: int32(916166782), 422 }, 423 End: ×tamp.Timestamp{ 424 Seconds: int64(916166800), 425 Nanos: int32(916166782), 426 }, 427 AverageFlakiness: float32(35.0), 428 PreviousFlakiness: float32(44.0), 429 NumFlakyTests: int32(6), 430 }, 431 TopFlakyTests: []*apipb.FlakyTestInfo{ 432 { 433 DisplayName: "top-flaky-5", 434 Flakiness: float32(89.25), 435 Change: summarypb.TestInfo_NO_CHANGE, 436 }, 437 { 438 DisplayName: "top-flaky-2", 439 Flakiness: float32(67.6), 440 Change: summarypb.TestInfo_UP, 441 }, 442 { 443 DisplayName: "top-flaky-3", 444 Flakiness: float32(67.6), 445 Change: summarypb.TestInfo_NO_CHANGE, 446 }, 447 { 448 DisplayName: "top-flaky-1", 449 Flakiness: float32(47.0), 450 Change: summarypb.TestInfo_DOWN, 451 }, 452 { 453 DisplayName: "top-flaky-4", 454 Flakiness: float32(33.3), 455 Change: summarypb.TestInfo_DOWN, 456 }, 457 }, 458 }, 459 }, 460 { 461 DashboardName: "Marco", 462 TabName: "polo-2", 463 DetailedStatusMessage: "1/7 tests are passing!", 464 OverallStatus: "ACCEPTABLE", 465 LatestPassingBuild: "Lantern", 466 LastRunTimestamp: ×tamp.Timestamp{ 467 Nanos: 100000000, 468 }, 469 LastUpdateTimestamp: ×tamp.Timestamp{ 470 Nanos: 100000000, 471 }, 472 HealthinessSummary: &apipb.HealthinessSummary{ 473 HealthinessStats: &apipb.HealthinessStats{ 474 Start: ×tamp.Timestamp{ 475 Seconds: int64(946702801), 476 }, 477 End: ×tamp.Timestamp{ 478 Seconds: int64(946704801), 479 }, 480 AverageFlakiness: float32(15.2), 481 PreviousFlakiness: float32(-1.0), 482 NumFlakyTests: int32(1), 483 }, 484 TopFlakyTests: []*apipb.FlakyTestInfo{ 485 { 486 DisplayName: "top-flaky-1", 487 Flakiness: float32(75.0), 488 Change: summarypb.TestInfo_UP, 489 }, 490 }, 491 }, 492 }, 493 }, 494 }, 495 }, 496 { 497 name: "Server error with unreadable config", 498 config: map[string]*configpb.Configuration{ 499 "gs://welp/config": {}, 500 }, 501 req: &apipb.ListTabSummariesRequest{ 502 Dashboard: "doesntmatter", 503 }, 504 expectError: true, 505 }, 506 } 507 508 for _, tc := range tests { 509 t.Run(tc.name, func(t *testing.T) { 510 server := setupTestServer(t, tc.config, nil, tc.summaries) 511 got, err := server.ListTabSummaries(context.Background(), tc.req) 512 switch { 513 case err != nil: 514 if !tc.expectError { 515 t.Errorf("got unexpected error: %v", err) 516 } 517 case tc.expectError: 518 t.Error("failed to receive an error") 519 default: 520 if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { 521 t.Errorf("got unexpected diff (-want +got):\n%s", diff) 522 } 523 } 524 }) 525 526 } 527 528 } 529 530 func GetTabSummary(t *testing.T) { 531 tests := []struct { 532 name string 533 config map[string]*configpb.Configuration 534 summaries map[string]*summarypb.DashboardSummary 535 req *apipb.GetTabSummaryRequest 536 want *apipb.GetTabSummaryResponse 537 expectError bool 538 }{ 539 { 540 name: "Returns an error when there's no dashboard in config", 541 config: map[string]*configpb.Configuration{ 542 "gs://default/config": {}, 543 }, 544 req: &apipb.GetTabSummaryRequest{ 545 Dashboard: "missing", 546 Tab: "Carpe Noctem", 547 }, 548 expectError: true, 549 }, 550 { 551 name: "Returns an error when there's no tab in config", 552 config: map[string]*configpb.Configuration{ 553 "gs://default/config": { 554 Dashboards: []*configpb.Dashboard{ 555 { 556 Name: "Aurora", 557 DashboardTab: []*configpb.DashboardTab{ 558 { 559 Name: "Borealis", 560 }, 561 }, 562 }, 563 }, 564 }, 565 }, 566 req: &apipb.GetTabSummaryRequest{ 567 Dashboard: "Aurora", 568 Tab: "Noctem", 569 }, 570 expectError: true, 571 }, 572 { 573 name: "Returns an error when there's no summary for dashboard yet", 574 config: map[string]*configpb.Configuration{ 575 "gs://default/config": { 576 Dashboards: []*configpb.Dashboard{ 577 { 578 Name: "ACME", 579 DashboardTab: []*configpb.DashboardTab{ 580 { 581 Name: "me-me", 582 TestGroupName: "testgroupname", 583 }, 584 }, 585 }, 586 }, 587 }, 588 }, 589 req: &apipb.GetTabSummaryRequest{ 590 Dashboard: "acme", 591 Tab: "me-me", 592 }, 593 expectError: true, 594 }, 595 { 596 name: "Returns an error when there's no summary for a tab", 597 config: map[string]*configpb.Configuration{ 598 "gs://default/config": { 599 Dashboards: []*configpb.Dashboard{ 600 { 601 Name: "Marco", 602 DashboardTab: []*configpb.DashboardTab{ 603 { 604 Name: "polo-1", 605 TestGroupName: "cheesecake", 606 }, 607 { 608 Name: "polo-2", 609 TestGroupName: "tiramisu", 610 }, 611 }, 612 }, 613 }, 614 }, 615 }, 616 summaries: map[string]*summarypb.DashboardSummary{ 617 "gs://default/summary/summary-marco": { 618 TabSummaries: []*summarypb.DashboardTabSummary{ 619 { 620 DashboardName: "Marco", 621 DashboardTabName: "polo-1", 622 Status: "1/7 tests are passing!", 623 OverallStatus: summarypb.DashboardTabSummary_FLAKY, 624 LatestGreen: "Hulk", 625 LastUpdateTimestamp: float64(915166800), 626 LastRunTimestamp: float64(915166800), 627 }, 628 }, 629 }, 630 }, 631 req: &apipb.GetTabSummaryRequest{ 632 Dashboard: "marco", 633 Tab: "polo-2", 634 }, 635 expectError: true, 636 }, 637 { 638 name: "Returns correct tab summary for a dashboard-tab", 639 config: map[string]*configpb.Configuration{ 640 "gs://default/config": { 641 Dashboards: []*configpb.Dashboard{ 642 { 643 Name: "Marco", 644 DashboardTab: []*configpb.DashboardTab{ 645 { 646 Name: "polo-1", 647 TestGroupName: "cheesecake", 648 }, 649 { 650 Name: "polo-2", 651 TestGroupName: "tiramisu", 652 }, 653 }, 654 }, 655 }, 656 }, 657 }, 658 summaries: map[string]*summarypb.DashboardSummary{ 659 "gs://default/summary/summary-marco": { 660 TabSummaries: []*summarypb.DashboardTabSummary{ 661 { 662 DashboardName: "Marco", 663 DashboardTabName: "polo-1", 664 Status: "1/7 tests are passing!", 665 OverallStatus: summarypb.DashboardTabSummary_FLAKY, 666 LatestGreen: "Hulk", 667 LastUpdateTimestamp: float64(915166800), 668 LastRunTimestamp: float64(915166800), 669 }, 670 { 671 DashboardName: "Marco", 672 DashboardTabName: "polo-2", 673 Status: "1/7 tests are failing!", 674 OverallStatus: summarypb.DashboardTabSummary_ACCEPTABLE, 675 LatestGreen: "Lantern", 676 LastUpdateTimestamp: float64(916166800), 677 LastRunTimestamp: float64(916166800), 678 }, 679 }, 680 }, 681 }, 682 req: &apipb.GetTabSummaryRequest{ 683 Dashboard: "marco", 684 Tab: "polo-1", 685 }, 686 want: &apipb.GetTabSummaryResponse{ 687 TabSummary: &apipb.TabSummary{ 688 DashboardName: "Marco", 689 TabName: "polo-1", 690 DetailedStatusMessage: "1/7 tests are passing!", 691 OverallStatus: "FLAKY", 692 LatestPassingBuild: "Hulk", 693 LastRunTimestamp: ×tamp.Timestamp{ 694 Seconds: 915166800, 695 }, 696 LastUpdateTimestamp: ×tamp.Timestamp{ 697 Seconds: 915166800, 698 }, 699 }, 700 }, 701 }, 702 { 703 name: "Server error with unreadable config", 704 config: map[string]*configpb.Configuration{ 705 "gs://welp/config": {}, 706 }, 707 req: &apipb.GetTabSummaryRequest{ 708 Dashboard: "non refert", 709 }, 710 expectError: true, 711 }, 712 } 713 714 for _, tc := range tests { 715 t.Run(tc.name, func(t *testing.T) { 716 server := setupTestServer(t, tc.config, nil, tc.summaries) 717 got, err := server.GetTabSummary(context.Background(), tc.req) 718 switch { 719 case err != nil: 720 if !tc.expectError { 721 t.Errorf("got unexpected error: %v", err) 722 } 723 case tc.expectError: 724 t.Error("failed to receive an error") 725 default: 726 if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { 727 t.Errorf("got unexpected diff (-want +got):\n%s", diff) 728 } 729 } 730 }) 731 732 } 733 734 } 735 736 func TestListTabSummariesHTTP(t *testing.T) { 737 tests := []struct { 738 name string 739 config map[string]*configpb.Configuration 740 summaries map[string]*summarypb.DashboardSummary 741 endpoint string 742 params string 743 expectedCode int 744 expectedResponse *apipb.ListTabSummariesResponse 745 }{ 746 { 747 name: "Returns an error when there's no dashboard in config", 748 config: map[string]*configpb.Configuration{ 749 "gs://default/config": {}, 750 }, 751 endpoint: "/dashboards/whatever/tab-summaries", 752 expectedCode: http.StatusNotFound, 753 }, 754 { 755 name: "Returns an error when there's no summary for dashboard yet", 756 config: map[string]*configpb.Configuration{ 757 "gs://default/config": { 758 Dashboards: []*configpb.Dashboard{ 759 { 760 Name: "ACME", 761 DashboardTab: []*configpb.DashboardTab{ 762 { 763 Name: "me-me", 764 TestGroupName: "testgroupname", 765 }, 766 }, 767 }, 768 }, 769 }, 770 }, 771 endpoint: "/dashboards/acme/tab-summaries", 772 expectedCode: http.StatusNotFound, 773 }, 774 { 775 name: "Returns correct tab summaries for a dashboard", 776 config: map[string]*configpb.Configuration{ 777 "gs://default/config": { 778 Dashboards: []*configpb.Dashboard{ 779 { 780 Name: "Marco", 781 DashboardTab: []*configpb.DashboardTab{ 782 { 783 Name: "polo-1", 784 TestGroupName: "cheesecake", 785 }, 786 { 787 Name: "polo-2", 788 TestGroupName: "tiramisu", 789 }, 790 }, 791 }, 792 }, 793 }, 794 }, 795 summaries: map[string]*summarypb.DashboardSummary{ 796 "gs://default/summary/summary-marco": { 797 TabSummaries: []*summarypb.DashboardTabSummary{ 798 { 799 DashboardName: "Marco", 800 DashboardTabName: "polo-1", 801 Status: "1/7 tests are passing!", 802 OverallStatus: summarypb.DashboardTabSummary_FLAKY, 803 LatestGreen: "Hulk", 804 LastUpdateTimestamp: float64(915166800), 805 LastRunTimestamp: float64(915166800), 806 }, 807 { 808 DashboardName: "Marco", 809 DashboardTabName: "polo-2", 810 Status: "1/7 tests are failing!", 811 OverallStatus: summarypb.DashboardTabSummary_ACCEPTABLE, 812 LatestGreen: "Lantern", 813 LastUpdateTimestamp: float64(916166800), 814 LastRunTimestamp: float64(916166800), 815 }, 816 }, 817 }, 818 }, 819 endpoint: "/dashboards/marco/tab-summaries", 820 expectedCode: http.StatusOK, 821 expectedResponse: &apipb.ListTabSummariesResponse{ 822 TabSummaries: []*apipb.TabSummary{ 823 { 824 DashboardName: "Marco", 825 TabName: "polo-1", 826 OverallStatus: "FLAKY", 827 DetailedStatusMessage: "1/7 tests are passing!", 828 LatestPassingBuild: "Hulk", 829 LastUpdateTimestamp: ×tamp.Timestamp{ 830 Seconds: 915166800, 831 }, 832 LastRunTimestamp: ×tamp.Timestamp{ 833 Seconds: 915166800, 834 }, 835 }, 836 { 837 DashboardName: "Marco", 838 TabName: "polo-2", 839 OverallStatus: "ACCEPTABLE", 840 DetailedStatusMessage: "1/7 tests are failing!", 841 LatestPassingBuild: "Lantern", 842 LastUpdateTimestamp: ×tamp.Timestamp{ 843 Seconds: 916166800, 844 }, 845 LastRunTimestamp: ×tamp.Timestamp{ 846 Seconds: 916166800, 847 }, 848 }, 849 }, 850 }, 851 }, 852 { 853 name: "Returns correct tab summaries for a dashboard with an updated scope", 854 config: map[string]*configpb.Configuration{ 855 "gs://k9s/config": { 856 Dashboards: []*configpb.Dashboard{ 857 { 858 Name: "Marco", 859 DashboardTab: []*configpb.DashboardTab{ 860 { 861 Name: "polo-1", 862 TestGroupName: "cheesecake", 863 }, 864 }, 865 }, 866 }, 867 }, 868 }, 869 summaries: map[string]*summarypb.DashboardSummary{ 870 "gs://k9s/summary/summary-marco": { 871 TabSummaries: []*summarypb.DashboardTabSummary{ 872 { 873 DashboardName: "Marco", 874 DashboardTabName: "polo-1", 875 Status: "1/7 tests are passing!", 876 OverallStatus: summarypb.DashboardTabSummary_FLAKY, 877 LatestGreen: "Hulk", 878 LastUpdateTimestamp: float64(915166800), 879 LastRunTimestamp: float64(915166800), 880 }, 881 }, 882 }, 883 }, 884 endpoint: "/dashboards/marco/tab-summaries?scope=gs://k9s", 885 expectedCode: http.StatusOK, 886 expectedResponse: &apipb.ListTabSummariesResponse{ 887 TabSummaries: []*apipb.TabSummary{ 888 { 889 DashboardName: "Marco", 890 TabName: "polo-1", 891 OverallStatus: "FLAKY", 892 DetailedStatusMessage: "1/7 tests are passing!", 893 LatestPassingBuild: "Hulk", 894 LastUpdateTimestamp: ×tamp.Timestamp{ 895 Seconds: 915166800, 896 }, 897 LastRunTimestamp: ×tamp.Timestamp{ 898 Seconds: 915166800, 899 }, 900 }, 901 }, 902 }, 903 }, 904 } 905 for _, test := range tests { 906 t.Run(test.name, func(t *testing.T) { 907 router := Route(nil, setupTestServer(t, test.config, nil, test.summaries)) 908 request, err := http.NewRequest("GET", test.endpoint, nil) 909 if err != nil { 910 t.Fatalf("Can't form request: %v", err) 911 } 912 response := httptest.NewRecorder() 913 router.ServeHTTP(response, request) 914 915 if response.Code != test.expectedCode { 916 t.Errorf("Expected %d, but got %d", test.expectedCode, response.Code) 917 } 918 919 if response.Code == http.StatusOK { 920 var ts apipb.ListTabSummariesResponse 921 if err := protojson.Unmarshal(response.Body.Bytes(), &ts); err != nil { 922 t.Fatalf("Failed to unmarshal json message into a proto message: %v", err) 923 } 924 if diff := cmp.Diff(test.expectedResponse, &ts, protocmp.Transform()); diff != "" { 925 t.Errorf("Obtained unexpected diff (-want +got):\n%s", diff) 926 } 927 } 928 }) 929 } 930 } 931 932 func TestListDashboardSummaries(t *testing.T) { 933 tests := []struct { 934 name string 935 config map[string]*configpb.Configuration 936 summaries map[string]*summarypb.DashboardSummary 937 req *apipb.ListDashboardSummariesRequest 938 want *apipb.ListDashboardSummariesResponse 939 expectError bool 940 }{ 941 { 942 name: "Returns an error when there's no dashboard group in config", 943 config: map[string]*configpb.Configuration{ 944 "gs://default/config": {}, 945 }, 946 req: &apipb.ListDashboardSummariesRequest{ 947 DashboardGroup: "missing", 948 }, 949 expectError: true, 950 }, 951 { 952 name: "Returns empty response for group with no dashboards, different names but same normalized", 953 config: map[string]*configpb.Configuration{ 954 "gs://default/config": { 955 DashboardGroups: []*configpb.DashboardGroup{ 956 { 957 Name: "Voila", 958 }, 959 }, 960 }, 961 }, 962 req: &apipb.ListDashboardSummariesRequest{ 963 DashboardGroup: "[voilA]", 964 }, 965 want: &apipb.ListDashboardSummariesResponse{}, 966 }, 967 { 968 name: "Returns correct dashboard summaries for a dashboard group, same name", 969 config: map[string]*configpb.Configuration{ 970 "gs://default/config": { 971 Dashboards: []*configpb.Dashboard{ 972 { 973 Name: "Ed", 974 DashboardTab: []*configpb.DashboardTab{ 975 { 976 Name: "ed-tab-1", 977 TestGroupName: "ed-tg-1", 978 }, 979 { 980 Name: "ed-tab-2", 981 TestGroupName: "ed-tg-2", 982 }, 983 { 984 Name: "ed-tab-3", 985 TestGroupName: "ed-tg-3", 986 }, 987 }, 988 }, 989 { 990 Name: "Edd", 991 DashboardTab: []*configpb.DashboardTab{ 992 { 993 Name: "edd-tab-1", 994 TestGroupName: "edd-tg-1", 995 }, 996 { 997 Name: "edd-tab-2", 998 TestGroupName: "edd-tg-2", 999 }, 1000 { 1001 Name: "edd-tab-3", 1002 TestGroupName: "edd-tg-3", 1003 }, 1004 }, 1005 }, 1006 { 1007 Name: "Eddie", 1008 DashboardTab: []*configpb.DashboardTab{ 1009 { 1010 Name: "eddie-tab-1", 1011 TestGroupName: "eddie-tg-1", 1012 }, 1013 { 1014 Name: "eddie-tab-2", 1015 TestGroupName: "eddie-tg-2", 1016 }, 1017 { 1018 Name: "eddie-tab-3", 1019 TestGroupName: "eddie-tg-3", 1020 }, 1021 }, 1022 }, 1023 }, 1024 DashboardGroups: []*configpb.DashboardGroup{ 1025 { 1026 Name: "C-N[123]", 1027 DashboardNames: []string{"Ed", "Edd", "Eddie"}, 1028 }, 1029 }, 1030 }, 1031 }, 1032 summaries: map[string]*summarypb.DashboardSummary{ 1033 "gs://default/summary/summary-ed": { 1034 TabSummaries: []*summarypb.DashboardTabSummary{ 1035 { 1036 DashboardName: "Ed", 1037 DashboardTabName: "ed-tab-1", 1038 OverallStatus: summarypb.DashboardTabSummary_PASS, 1039 }, 1040 { 1041 DashboardName: "Ed", 1042 DashboardTabName: "ed-tab-2", 1043 OverallStatus: summarypb.DashboardTabSummary_ACCEPTABLE, 1044 }, 1045 { 1046 DashboardName: "Ed", 1047 DashboardTabName: "ed-tab-3", 1048 OverallStatus: summarypb.DashboardTabSummary_FLAKY, 1049 }, 1050 }, 1051 }, 1052 "gs://default/summary/summary-edd": { 1053 TabSummaries: []*summarypb.DashboardTabSummary{ 1054 { 1055 DashboardName: "Edd", 1056 DashboardTabName: "edd-tab-1", 1057 OverallStatus: summarypb.DashboardTabSummary_PENDING, 1058 }, 1059 { 1060 DashboardName: "Edd", 1061 DashboardTabName: "edd-tab-2", 1062 OverallStatus: summarypb.DashboardTabSummary_STALE, 1063 }, 1064 { 1065 DashboardName: "Edd", 1066 DashboardTabName: "edd-tab-3", 1067 OverallStatus: summarypb.DashboardTabSummary_BROKEN, 1068 }, 1069 }, 1070 }, 1071 "gs://default/summary/summary-eddie": { 1072 TabSummaries: []*summarypb.DashboardTabSummary{ 1073 { 1074 DashboardName: "Eddie", 1075 DashboardTabName: "eddie-tab-1", 1076 OverallStatus: summarypb.DashboardTabSummary_PASS, 1077 }, 1078 { 1079 DashboardName: "Eddie", 1080 DashboardTabName: "eddie-tab-2", 1081 OverallStatus: summarypb.DashboardTabSummary_PASS, 1082 }, 1083 { 1084 DashboardName: "Eddie", 1085 DashboardTabName: "eddie-tab-3", 1086 OverallStatus: summarypb.DashboardTabSummary_PASS, 1087 }, 1088 }, 1089 }, 1090 }, 1091 req: &apipb.ListDashboardSummariesRequest{ 1092 DashboardGroup: "C-N[123]", 1093 }, 1094 want: &apipb.ListDashboardSummariesResponse{ 1095 DashboardSummaries: []*apipb.DashboardSummary{ 1096 { 1097 Name: "Ed", 1098 OverallStatus: flaky, 1099 TabStatusCount: map[string]int32{ 1100 passing: 1, 1101 acceptable: 1, 1102 flaky: 1, 1103 }, 1104 }, 1105 { 1106 Name: "Edd", 1107 OverallStatus: broken, 1108 TabStatusCount: map[string]int32{ 1109 pending: 1, 1110 stale: 1, 1111 broken: 1, 1112 }, 1113 }, 1114 { 1115 Name: "Eddie", 1116 OverallStatus: passing, 1117 TabStatusCount: map[string]int32{ 1118 passing: 3, 1119 }, 1120 }, 1121 }, 1122 }, 1123 }, 1124 { 1125 name: "Server error with unreadable config", 1126 config: map[string]*configpb.Configuration{ 1127 "gs://welp/config": {}, 1128 }, 1129 req: &apipb.ListDashboardSummariesRequest{ 1130 DashboardGroup: "doesntmatter", 1131 }, 1132 expectError: true, 1133 }, 1134 } 1135 1136 for _, tc := range tests { 1137 t.Run(tc.name, func(t *testing.T) { 1138 server := setupTestServer(t, tc.config, nil, tc.summaries) 1139 got, err := server.ListDashboardSummaries(context.Background(), tc.req) 1140 switch { 1141 case err != nil: 1142 if !tc.expectError { 1143 t.Errorf("got unexpected error: %v", err) 1144 } 1145 case tc.expectError: 1146 t.Error("failed to receive an error") 1147 default: 1148 if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { 1149 t.Errorf("got unexpected diff (-want +got):\n%s", diff) 1150 } 1151 } 1152 }) 1153 1154 } 1155 } 1156 1157 func TestGetDashboardSummary(t *testing.T) { 1158 tests := []struct { 1159 name string 1160 config map[string]*configpb.Configuration 1161 summaries map[string]*summarypb.DashboardSummary 1162 req *apipb.GetDashboardSummaryRequest 1163 want *apipb.GetDashboardSummaryResponse 1164 expectError bool 1165 }{ 1166 { 1167 name: "Returns an error when there's no dashboard group in config", 1168 config: map[string]*configpb.Configuration{ 1169 "gs://default/config": {}, 1170 }, 1171 req: &apipb.GetDashboardSummaryRequest{ 1172 Dashboard: "missing", 1173 }, 1174 expectError: true, 1175 }, 1176 { 1177 name: "Returns correct dashboard summary for a dashboard, same name", 1178 config: map[string]*configpb.Configuration{ 1179 "gs://default/config": { 1180 Dashboards: []*configpb.Dashboard{ 1181 { 1182 Name: "Plank", 1183 DashboardTab: []*configpb.DashboardTab{ 1184 { 1185 Name: "plank-tab-1", 1186 TestGroupName: "plank-tg-1", 1187 }, 1188 { 1189 Name: "plank-tab-2", 1190 TestGroupName: "plank-tg-2", 1191 }, 1192 { 1193 Name: "plank-tab-3", 1194 TestGroupName: "plank-tg-3", 1195 }, 1196 }, 1197 }, 1198 }, 1199 }, 1200 }, 1201 summaries: map[string]*summarypb.DashboardSummary{ 1202 "gs://default/summary/summary-plank": { 1203 TabSummaries: []*summarypb.DashboardTabSummary{ 1204 { 1205 DashboardName: "Plank", 1206 DashboardTabName: "plank-tab-1", 1207 OverallStatus: summarypb.DashboardTabSummary_PASS, 1208 }, 1209 { 1210 DashboardName: "Plank", 1211 DashboardTabName: "plank-tab-2", 1212 OverallStatus: summarypb.DashboardTabSummary_ACCEPTABLE, 1213 }, 1214 { 1215 DashboardName: "Plank", 1216 DashboardTabName: "plank-tab-3", 1217 OverallStatus: summarypb.DashboardTabSummary_FLAKY, 1218 }, 1219 }, 1220 }, 1221 }, 1222 req: &apipb.GetDashboardSummaryRequest{ 1223 Dashboard: "Plank", 1224 }, 1225 want: &apipb.GetDashboardSummaryResponse{ 1226 DashboardSummary: &apipb.DashboardSummary{ 1227 Name: "Plank", 1228 OverallStatus: flaky, 1229 TabStatusCount: map[string]int32{ 1230 passing: 1, 1231 acceptable: 1, 1232 flaky: 1, 1233 }, 1234 }, 1235 }, 1236 }, 1237 { 1238 name: "Returns correct dashboard summary for a dashboard, different names but same normalized", 1239 config: map[string]*configpb.Configuration{ 1240 "gs://default/config": { 1241 Dashboards: []*configpb.Dashboard{ 1242 { 1243 Name: "Plank", 1244 DashboardTab: []*configpb.DashboardTab{ 1245 { 1246 Name: "plank-tab-1", 1247 TestGroupName: "plank-tg-1", 1248 }, 1249 }, 1250 }, 1251 }, 1252 }, 1253 }, 1254 summaries: map[string]*summarypb.DashboardSummary{ 1255 "gs://default/summary/summary-plank": { 1256 TabSummaries: []*summarypb.DashboardTabSummary{ 1257 { 1258 DashboardName: "Plank", 1259 DashboardTabName: "plank-tab-1", 1260 OverallStatus: summarypb.DashboardTabSummary_PASS, 1261 }, 1262 }, 1263 }, 1264 }, 1265 req: &apipb.GetDashboardSummaryRequest{ 1266 Dashboard: "P_L-A(N}K", 1267 }, 1268 want: &apipb.GetDashboardSummaryResponse{ 1269 DashboardSummary: &apipb.DashboardSummary{ 1270 Name: "Plank", 1271 OverallStatus: passing, 1272 TabStatusCount: map[string]int32{ 1273 passing: 1, 1274 }, 1275 }, 1276 }, 1277 }, 1278 { 1279 name: "Server error with unreadable config", 1280 config: map[string]*configpb.Configuration{ 1281 "gs://welp/config": {}, 1282 }, 1283 req: &apipb.GetDashboardSummaryRequest{ 1284 Dashboard: "doesntmatter", 1285 }, 1286 expectError: true, 1287 }, 1288 } 1289 1290 for _, tc := range tests { 1291 t.Run(tc.name, func(t *testing.T) { 1292 server := setupTestServer(t, tc.config, nil, tc.summaries) 1293 got, err := server.GetDashboardSummary(context.Background(), tc.req) 1294 switch { 1295 case err != nil: 1296 if !tc.expectError { 1297 t.Errorf("got unexpected error: %v", err) 1298 } 1299 case tc.expectError: 1300 t.Error("failed to receive an error") 1301 default: 1302 if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { 1303 t.Errorf("got unexpected diff (-want +got):\n%s", diff) 1304 } 1305 } 1306 }) 1307 } 1308 } 1309 1310 func TestListDashboardSummariesHTTP(t *testing.T) { 1311 tests := []struct { 1312 name string 1313 config map[string]*configpb.Configuration 1314 summaries map[string]*summarypb.DashboardSummary 1315 endpoint string 1316 params string 1317 expectedCode int 1318 expectedResponse *apipb.ListDashboardSummariesResponse 1319 }{ 1320 { 1321 name: "Returns an error when there's no dashboard in config", 1322 config: map[string]*configpb.Configuration{ 1323 "gs://default/config": {}, 1324 }, 1325 endpoint: "/dashboard-groups/whatever/dashboard-summaries", 1326 expectedCode: http.StatusNotFound, 1327 }, 1328 { 1329 name: "Returns an empty response for group with no dashboards, different names but same normalized", 1330 config: map[string]*configpb.Configuration{ 1331 "gs://default/config": { 1332 DashboardGroups: []*configpb.DashboardGroup{ 1333 { 1334 Name: "Sherlock", 1335 }, 1336 }, 1337 }, 1338 }, 1339 endpoint: "/dashboard-groups/sherLOCK/dashboard-summaries", 1340 expectedCode: http.StatusOK, 1341 expectedResponse: &apipb.ListDashboardSummariesResponse{}, 1342 }, 1343 { 1344 name: "Returns correct dashboard summaries for a dashboard group, same name", 1345 config: map[string]*configpb.Configuration{ 1346 "gs://default/config": { 1347 Dashboards: []*configpb.Dashboard{ 1348 { 1349 Name: "Ed", 1350 DashboardTab: []*configpb.DashboardTab{ 1351 { 1352 Name: "ed-tab-1", 1353 TestGroupName: "ed-tg-1", 1354 }, 1355 { 1356 Name: "ed-tab-2", 1357 TestGroupName: "ed-tg-2", 1358 }, 1359 { 1360 Name: "ed-tab-3", 1361 TestGroupName: "ed-tg-3", 1362 }, 1363 }, 1364 }, 1365 { 1366 Name: "Edd", 1367 DashboardTab: []*configpb.DashboardTab{ 1368 { 1369 Name: "edd-tab-1", 1370 TestGroupName: "edd-tg-1", 1371 }, 1372 { 1373 Name: "edd-tab-2", 1374 TestGroupName: "edd-tg-2", 1375 }, 1376 { 1377 Name: "edd-tab-3", 1378 TestGroupName: "edd-tg-3", 1379 }, 1380 }, 1381 }, 1382 { 1383 Name: "Eddie", 1384 DashboardTab: []*configpb.DashboardTab{ 1385 { 1386 Name: "eddie-tab-1", 1387 TestGroupName: "eddie-tg-1", 1388 }, 1389 { 1390 Name: "eddie-tab-2", 1391 TestGroupName: "eddie-tg-2", 1392 }, 1393 { 1394 Name: "eddie-tab-3", 1395 TestGroupName: "eddie-tg-3", 1396 }, 1397 }, 1398 }, 1399 }, 1400 DashboardGroups: []*configpb.DashboardGroup{ 1401 { 1402 Name: "C-N[123]", 1403 DashboardNames: []string{"Ed", "Edd", "Eddie"}, 1404 }, 1405 }, 1406 }, 1407 }, 1408 summaries: map[string]*summarypb.DashboardSummary{ 1409 "gs://default/summary/summary-ed": { 1410 TabSummaries: []*summarypb.DashboardTabSummary{ 1411 { 1412 DashboardName: "Ed", 1413 DashboardTabName: "ed-tab-1", 1414 OverallStatus: summarypb.DashboardTabSummary_PASS, 1415 }, 1416 { 1417 DashboardName: "Ed", 1418 DashboardTabName: "ed-tab-2", 1419 OverallStatus: summarypb.DashboardTabSummary_ACCEPTABLE, 1420 }, 1421 { 1422 DashboardName: "Ed", 1423 DashboardTabName: "ed-tab-3", 1424 OverallStatus: summarypb.DashboardTabSummary_FLAKY, 1425 }, 1426 }, 1427 }, 1428 "gs://default/summary/summary-edd": { 1429 TabSummaries: []*summarypb.DashboardTabSummary{ 1430 { 1431 DashboardName: "Edd", 1432 DashboardTabName: "edd-tab-1", 1433 OverallStatus: summarypb.DashboardTabSummary_PENDING, 1434 }, 1435 { 1436 DashboardName: "Edd", 1437 DashboardTabName: "edd-tab-2", 1438 OverallStatus: summarypb.DashboardTabSummary_STALE, 1439 }, 1440 { 1441 DashboardName: "Edd", 1442 DashboardTabName: "edd-tab-3", 1443 OverallStatus: summarypb.DashboardTabSummary_BROKEN, 1444 }, 1445 }, 1446 }, 1447 "gs://default/summary/summary-eddie": { 1448 TabSummaries: []*summarypb.DashboardTabSummary{ 1449 { 1450 DashboardName: "Eddie", 1451 DashboardTabName: "eddie-tab-1", 1452 OverallStatus: summarypb.DashboardTabSummary_PASS, 1453 }, 1454 { 1455 DashboardName: "Eddie", 1456 DashboardTabName: "eddie-tab-2", 1457 OverallStatus: summarypb.DashboardTabSummary_PASS, 1458 }, 1459 { 1460 DashboardName: "Eddie", 1461 DashboardTabName: "eddie-tab-3", 1462 OverallStatus: summarypb.DashboardTabSummary_PASS, 1463 }, 1464 }, 1465 }, 1466 }, 1467 endpoint: "/dashboard-groups/C-N[123]/dashboard-summaries", 1468 expectedCode: http.StatusOK, 1469 expectedResponse: &apipb.ListDashboardSummariesResponse{ 1470 DashboardSummaries: []*apipb.DashboardSummary{ 1471 { 1472 Name: "Ed", 1473 OverallStatus: flaky, 1474 TabStatusCount: map[string]int32{ 1475 passing: 1, 1476 acceptable: 1, 1477 flaky: 1, 1478 }, 1479 }, 1480 { 1481 Name: "Edd", 1482 OverallStatus: broken, 1483 TabStatusCount: map[string]int32{ 1484 pending: 1, 1485 stale: 1, 1486 broken: 1, 1487 }, 1488 }, 1489 { 1490 Name: "Eddie", 1491 OverallStatus: passing, 1492 TabStatusCount: map[string]int32{ 1493 passing: 3, 1494 }, 1495 }, 1496 }, 1497 }, 1498 }, 1499 { 1500 name: "Returns correct dashboard summaries for a group with an updated scope", 1501 config: map[string]*configpb.Configuration{ 1502 "gs://k9s/config": { 1503 Dashboards: []*configpb.Dashboard{ 1504 { 1505 Name: "Marco", 1506 DashboardTab: []*configpb.DashboardTab{ 1507 { 1508 Name: "polo-1", 1509 TestGroupName: "cheesecake", 1510 }, 1511 }, 1512 }, 1513 }, 1514 DashboardGroups: []*configpb.DashboardGroup{ 1515 { 1516 Name: "explorers", 1517 DashboardNames: []string{"Marco"}, 1518 }, 1519 }, 1520 }, 1521 }, 1522 summaries: map[string]*summarypb.DashboardSummary{ 1523 "gs://k9s/summary/summary-marco": { 1524 TabSummaries: []*summarypb.DashboardTabSummary{ 1525 { 1526 DashboardName: "Marco", 1527 DashboardTabName: "polo-1", 1528 OverallStatus: summarypb.DashboardTabSummary_FLAKY, 1529 }, 1530 }, 1531 }, 1532 }, 1533 endpoint: "/dashboard-groups/explorers/dashboard-summaries?scope=gs://k9s", 1534 expectedCode: http.StatusOK, 1535 expectedResponse: &apipb.ListDashboardSummariesResponse{ 1536 DashboardSummaries: []*apipb.DashboardSummary{ 1537 { 1538 Name: "Marco", 1539 OverallStatus: flaky, 1540 TabStatusCount: map[string]int32{ 1541 flaky: 1, 1542 }, 1543 }, 1544 }, 1545 }, 1546 }, 1547 } 1548 for _, test := range tests { 1549 t.Run(test.name, func(t *testing.T) { 1550 router := Route(nil, setupTestServer(t, test.config, nil, test.summaries)) 1551 request, err := http.NewRequest("GET", test.endpoint, nil) 1552 if err != nil { 1553 t.Fatalf("Can't form request: %v", err) 1554 } 1555 response := httptest.NewRecorder() 1556 router.ServeHTTP(response, request) 1557 1558 if response.Code != test.expectedCode { 1559 t.Errorf("Expected %d, but got %d", test.expectedCode, response.Code) 1560 } 1561 1562 if response.Code == http.StatusOK { 1563 var ts apipb.ListDashboardSummariesResponse 1564 if err := protojson.Unmarshal(response.Body.Bytes(), &ts); err != nil { 1565 t.Fatalf("Failed to unmarshal json message into a proto message: %v", err) 1566 } 1567 if diff := cmp.Diff(test.expectedResponse, &ts, protocmp.Transform()); diff != "" { 1568 t.Errorf("Obtained unexpected diff (-want +got):\n%s", diff) 1569 } 1570 } 1571 }) 1572 } 1573 } 1574 1575 func TestGetDashboardSummaryHTTP(t *testing.T) { 1576 tests := []struct { 1577 name string 1578 config map[string]*configpb.Configuration 1579 summaries map[string]*summarypb.DashboardSummary 1580 endpoint string 1581 params string 1582 expectedCode int 1583 expectedResponse *apipb.GetDashboardSummaryResponse 1584 }{ 1585 { 1586 name: "Returns an error when there's no dashboard in config", 1587 config: map[string]*configpb.Configuration{ 1588 "gs://default/config": {}, 1589 }, 1590 endpoint: "/dashboards/whatever/summary", 1591 expectedCode: http.StatusNotFound, 1592 }, 1593 { 1594 name: "Returns correct dashboard summary for a dashboard, different names but same normalized", 1595 config: map[string]*configpb.Configuration{ 1596 "gs://default/config": { 1597 Dashboards: []*configpb.Dashboard{ 1598 { 1599 Name: "Ed", 1600 DashboardTab: []*configpb.DashboardTab{ 1601 { 1602 Name: "ed-tab-1", 1603 TestGroupName: "ed-tg-1", 1604 }, 1605 { 1606 Name: "ed-tab-2", 1607 TestGroupName: "ed-tg-2", 1608 }, 1609 }, 1610 }, 1611 }, 1612 }, 1613 }, 1614 summaries: map[string]*summarypb.DashboardSummary{ 1615 "gs://default/summary/summary-ed": { 1616 TabSummaries: []*summarypb.DashboardTabSummary{ 1617 { 1618 DashboardName: "Ed", 1619 DashboardTabName: "ed-tab-1", 1620 OverallStatus: summarypb.DashboardTabSummary_PASS, 1621 }, 1622 { 1623 DashboardName: "Ed", 1624 DashboardTabName: "ed-tab-2", 1625 OverallStatus: summarypb.DashboardTabSummary_ACCEPTABLE, 1626 }, 1627 }, 1628 }, 1629 }, 1630 endpoint: "/dashboards/ED/summary", 1631 expectedCode: http.StatusOK, 1632 expectedResponse: &apipb.GetDashboardSummaryResponse{ 1633 DashboardSummary: &apipb.DashboardSummary{ 1634 Name: "Ed", 1635 OverallStatus: acceptable, 1636 TabStatusCount: map[string]int32{ 1637 passing: 1, 1638 acceptable: 1, 1639 }, 1640 }, 1641 }, 1642 }, 1643 { 1644 name: "Returns correct dashboard summary for a dashboard with an updated scope", 1645 config: map[string]*configpb.Configuration{ 1646 "gs://k9s/config": { 1647 Dashboards: []*configpb.Dashboard{ 1648 { 1649 Name: "Marco", 1650 DashboardTab: []*configpb.DashboardTab{ 1651 { 1652 Name: "polo-1", 1653 TestGroupName: "cheesecake", 1654 }, 1655 }, 1656 }, 1657 }, 1658 }, 1659 }, 1660 summaries: map[string]*summarypb.DashboardSummary{ 1661 "gs://k9s/summary/summary-marco": { 1662 TabSummaries: []*summarypb.DashboardTabSummary{ 1663 { 1664 DashboardName: "Marco", 1665 DashboardTabName: "polo-1", 1666 OverallStatus: summarypb.DashboardTabSummary_BROKEN, 1667 }, 1668 }, 1669 }, 1670 }, 1671 endpoint: "/dashboards/marco/summary?scope=gs://k9s", 1672 expectedCode: http.StatusOK, 1673 expectedResponse: &apipb.GetDashboardSummaryResponse{ 1674 DashboardSummary: &apipb.DashboardSummary{ 1675 Name: "Marco", 1676 OverallStatus: broken, 1677 TabStatusCount: map[string]int32{ 1678 broken: 1, 1679 }, 1680 }, 1681 }, 1682 }, 1683 } 1684 for _, test := range tests { 1685 t.Run(test.name, func(t *testing.T) { 1686 router := Route(nil, setupTestServer(t, test.config, nil, test.summaries)) 1687 request, err := http.NewRequest("GET", test.endpoint, nil) 1688 if err != nil { 1689 t.Fatalf("Can't form request: %v", err) 1690 } 1691 response := httptest.NewRecorder() 1692 router.ServeHTTP(response, request) 1693 1694 if response.Code != test.expectedCode { 1695 t.Errorf("Expected %d, but got %d", test.expectedCode, response.Code) 1696 } 1697 1698 if response.Code == http.StatusOK { 1699 var ts apipb.GetDashboardSummaryResponse 1700 if err := protojson.Unmarshal(response.Body.Bytes(), &ts); err != nil { 1701 t.Fatalf("Failed to unmarshal json message into a proto message: %v", err) 1702 } 1703 if diff := cmp.Diff(test.expectedResponse, &ts, protocmp.Transform()); diff != "" { 1704 t.Errorf("Obtained unexpected diff (-want +got):\n%s", diff) 1705 } 1706 } 1707 }) 1708 } 1709 }