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: &timestamp.Timestamp{
   178  							Seconds: 915166800,
   179  							Nanos:   916166782,
   180  						},
   181  						LastUpdateTimestamp: &timestamp.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: &timestamp.Timestamp{
   194  										Seconds: int64(1677883461),
   195  										Nanos:   int32(254300117),
   196  									},
   197  									FailTimestamp: &timestamp.Timestamp{
   198  										Seconds: int64(1677883441),
   199  									},
   200  								},
   201  								{
   202  									DisplayName: "top-failure-2",
   203  									FailCount:   128,
   204  									PassTimestamp: &timestamp.Timestamp{
   205  										Seconds: int64(1677983461),
   206  										Nanos:   int32(354000091),
   207  									},
   208  									FailTimestamp: &timestamp.Timestamp{
   209  										Seconds: int64(1677983561),
   210  									},
   211  								},
   212  								{
   213  									DisplayName: "top-failure-4",
   214  									FailCount:   64,
   215  									PassTimestamp: &timestamp.Timestamp{
   216  										Seconds: int64(1677983461),
   217  										Nanos:   int32(354000091),
   218  									},
   219  									FailTimestamp: &timestamp.Timestamp{
   220  										Seconds: int64(1677983561),
   221  									},
   222  								},
   223  								{
   224  									DisplayName: "top-failure-5",
   225  									FailCount:   32,
   226  									PassTimestamp: &timestamp.Timestamp{
   227  										Seconds: int64(1677983461),
   228  										Nanos:   int32(354000091),
   229  									},
   230  									FailTimestamp: &timestamp.Timestamp{
   231  										Seconds: int64(1677983561),
   232  									},
   233  								},
   234  								{
   235  									DisplayName: "top-failure-3",
   236  									FailCount:   3,
   237  									PassTimestamp: &timestamp.Timestamp{
   238  										Seconds: int64(914166800),
   239  										Nanos:   int32(330000042),
   240  									},
   241  									FailTimestamp: &timestamp.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: &timestamp.Timestamp{
   256  							Nanos: 100000000,
   257  						},
   258  						LastUpdateTimestamp: &timestamp.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: &timestamp.Timestamp{
   270  										Seconds: int64(914166800),
   271  										Nanos:   int32(213000059),
   272  									},
   273  									FailTimestamp: &timestamp.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: &timestamp.Timestamp{
   317  									Seconds: int64(915166800),
   318  									Nanos:   int32(916166782),
   319  								},
   320  								End: &timestamp.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: &timestamp.Timestamp{
   375  									Seconds: int64(946702801),
   376  								},
   377  								End: &timestamp.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: &timestamp.Timestamp{
   410  							Seconds: 915166800,
   411  							Nanos:   916166782,
   412  						},
   413  						LastUpdateTimestamp: &timestamp.Timestamp{
   414  							Seconds: 915166800,
   415  							Nanos:   916166782,
   416  						},
   417  						HealthinessSummary: &apipb.HealthinessSummary{
   418  							HealthinessStats: &apipb.HealthinessStats{
   419  								Start: &timestamp.Timestamp{
   420  									Seconds: int64(915166800),
   421  									Nanos:   int32(916166782),
   422  								},
   423  								End: &timestamp.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: &timestamp.Timestamp{
   467  							Nanos: 100000000,
   468  						},
   469  						LastUpdateTimestamp: &timestamp.Timestamp{
   470  							Nanos: 100000000,
   471  						},
   472  						HealthinessSummary: &apipb.HealthinessSummary{
   473  							HealthinessStats: &apipb.HealthinessStats{
   474  								Start: &timestamp.Timestamp{
   475  									Seconds: int64(946702801),
   476  								},
   477  								End: &timestamp.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: &timestamp.Timestamp{
   694  						Seconds: 915166800,
   695  					},
   696  					LastUpdateTimestamp: &timestamp.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: &timestamp.Timestamp{
   830  							Seconds: 915166800,
   831  						},
   832  						LastRunTimestamp: &timestamp.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: &timestamp.Timestamp{
   843  							Seconds: 916166800,
   844  						},
   845  						LastRunTimestamp: &timestamp.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: &timestamp.Timestamp{
   895  							Seconds: 915166800,
   896  						},
   897  						LastRunTimestamp: &timestamp.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  }