sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/resultstore/payload_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package resultstore
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/GoogleCloudPlatform/testgrid/metadata"
    24  	"github.com/google/go-cmp/cmp"
    25  	"google.golang.org/genproto/googleapis/devtools/resultstore/v2"
    26  	"google.golang.org/protobuf/testing/protocmp"
    27  	"google.golang.org/protobuf/types/known/durationpb"
    28  	"google.golang.org/protobuf/types/known/timestamppb"
    29  	corev1 "k8s.io/api/core/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	v1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    32  	"sigs.k8s.io/prow/pkg/kube"
    33  )
    34  
    35  func TestInvocationID(t *testing.T) {
    36  	for _, tc := range []struct {
    37  		desc    string
    38  		payload Payload
    39  		want    string
    40  		wantErr bool
    41  	}{
    42  		{
    43  			desc: "success",
    44  			payload: Payload{
    45  				Job: &v1.ProwJob{
    46  					ObjectMeta: metav1.ObjectMeta{
    47  						Name: "job-name",
    48  					},
    49  				},
    50  			},
    51  			want: "job-name",
    52  		},
    53  		{
    54  			desc:    "nil job",
    55  			payload: Payload{},
    56  			wantErr: true,
    57  		},
    58  	} {
    59  		t.Run(tc.desc, func(t *testing.T) {
    60  			got, err := tc.payload.InvocationID()
    61  			if err != nil {
    62  				if tc.wantErr {
    63  					t.Logf("got expected error: %v", err)
    64  					return
    65  				}
    66  				t.Fatal("got unexpected error")
    67  			}
    68  			if tc.wantErr {
    69  				t.Fatal("want error, got nil")
    70  			}
    71  			if got != tc.want {
    72  				t.Errorf("InvocationID got %v, want %v", got, tc.want)
    73  			}
    74  		})
    75  	}
    76  }
    77  
    78  func int64Pointer(v int64) *int64 {
    79  	return &v
    80  }
    81  
    82  func TestInvocation(t *testing.T) {
    83  	for _, tc := range []struct {
    84  		desc    string
    85  		payload *Payload
    86  		want    *resultstore.Invocation
    87  		wantErr bool
    88  	}{
    89  		{
    90  			desc: "complete",
    91  			payload: &Payload{
    92  				Job: &v1.ProwJob{
    93  					ObjectMeta: metav1.ObjectMeta{
    94  						Name: "job-name",
    95  						Labels: map[string]string{
    96  							kube.ProwJobTypeLabel:  "job-type-label",
    97  							kube.RepoLabel:         "repo-label",
    98  							kube.PullLabel:         "pull-label",
    99  							kube.GerritPatchset:    "gerrit-patchset-label",
   100  							kube.ProwBuildIDLabel:  "build-id-label",
   101  							kube.ProwJobAnnotation: "job-label",
   102  						},
   103  					},
   104  					Spec: v1.ProwJobSpec{
   105  						Job: "spec-job",
   106  						PodSpec: &corev1.PodSpec{
   107  							Containers: []corev1.Container{
   108  								{
   109  									Name:    "container-1",
   110  									Args:    []string{"arg-1", "arg-2"},
   111  									Command: []string{"command"},
   112  									Env: []corev1.EnvVar{
   113  										{
   114  											Name:  "env1",
   115  											Value: "env1-value",
   116  										},
   117  									},
   118  								},
   119  							},
   120  						},
   121  					},
   122  					Status: v1.ProwJobStatus{
   123  						StartTime: metav1.Time{
   124  							Time: time.Unix(100, 0),
   125  						},
   126  						CompletionTime: &metav1.Time{
   127  							Time: time.Unix(300, 0),
   128  						},
   129  						State:   v1.SuccessState,
   130  						URL:     "https://prow/url",
   131  						BuildID: "build-id",
   132  					},
   133  				},
   134  				Started: &metadata.Started{
   135  					Timestamp:  150,
   136  					RepoCommit: "repo-commit",
   137  					Repos: map[string]string{
   138  						"https://started-review.repo": "started-branch",
   139  					},
   140  				},
   141  				Finished: &metadata.Finished{
   142  					Timestamp: int64Pointer(250),
   143  				},
   144  				ProjectID: "project-id",
   145  			},
   146  			want: &resultstore.Invocation{
   147  				InvocationAttributes: &resultstore.InvocationAttributes{
   148  					ProjectId: "project-id",
   149  					Labels: []string{
   150  						"prow",
   151  					},
   152  					Description: "job-type-label for repo-label/pull-label/gerrit-patchset-label/build-id-label/job-label",
   153  				},
   154  				Properties: []*resultstore.Property{
   155  					{
   156  						Key:   "Instance",
   157  						Value: "build-id",
   158  					},
   159  					{
   160  						Key:   "Job",
   161  						Value: "spec-job",
   162  					},
   163  					{
   164  						Key:   "Prow_Dashboard_URL",
   165  						Value: "https://prow/url",
   166  					},
   167  					{
   168  						Key:   "Env",
   169  						Value: "env1=env1-value",
   170  					},
   171  					{
   172  						Key:   "Commit",
   173  						Value: "repo-commit",
   174  					},
   175  					{
   176  						Key:   "Branch",
   177  						Value: "started-branch",
   178  					},
   179  					{
   180  						Key:   "Repo",
   181  						Value: "https://started.repo",
   182  					},
   183  				},
   184  				StatusAttributes: &resultstore.StatusAttributes{
   185  					Status: resultstore.Status_PASSED,
   186  				},
   187  				Timing: &resultstore.Timing{
   188  					StartTime: &timestamppb.Timestamp{
   189  						Seconds: 100,
   190  					},
   191  					Duration: &durationpb.Duration{
   192  						Seconds: 200,
   193  					},
   194  				},
   195  				WorkspaceInfo: &resultstore.WorkspaceInfo{
   196  					CommandLines: []*resultstore.CommandLine{
   197  						{
   198  							Label: "original",
   199  							Tool:  "command",
   200  							Args:  []string{"arg-1", "arg-2"},
   201  						},
   202  					},
   203  				},
   204  			},
   205  		},
   206  		{
   207  			desc: "completiontime started finished nil",
   208  			payload: &Payload{
   209  				Job: &v1.ProwJob{
   210  					ObjectMeta: metav1.ObjectMeta{
   211  						Name: "job-name",
   212  						Labels: map[string]string{
   213  							kube.ProwJobTypeLabel:  "job-type-label",
   214  							kube.RepoLabel:         "repo-label",
   215  							kube.ProwBuildIDLabel:  "build-id-label",
   216  							kube.ProwJobAnnotation: "job-label",
   217  						},
   218  					},
   219  					Spec: v1.ProwJobSpec{
   220  						Job: "spec-job",
   221  						PodSpec: &corev1.PodSpec{
   222  							Containers: []corev1.Container{
   223  								{
   224  									Name:    "container-1",
   225  									Args:    []string{"arg-1", "arg-2"},
   226  									Command: []string{"command"},
   227  									Env: []corev1.EnvVar{
   228  										{
   229  											Name:  "env1",
   230  											Value: "env1-value",
   231  										},
   232  										{
   233  											Name:  "env2",
   234  											Value: "env2-value",
   235  										},
   236  									},
   237  								},
   238  							},
   239  						},
   240  					},
   241  					Status: v1.ProwJobStatus{
   242  						StartTime: metav1.Time{
   243  							Time: time.Unix(100, 0),
   244  						},
   245  						CompletionTime: nil,
   246  						State:          v1.SuccessState,
   247  						URL:            "https://prow/url",
   248  						BuildID:        "build-id",
   249  					},
   250  				},
   251  				Started:   nil,
   252  				Finished:  nil,
   253  				ProjectID: "project-id",
   254  			},
   255  			want: &resultstore.Invocation{
   256  				InvocationAttributes: &resultstore.InvocationAttributes{
   257  					ProjectId: "project-id",
   258  					Labels: []string{
   259  						"prow",
   260  					},
   261  					Description: "job-type-label for repo-label/build-id-label/job-label",
   262  				},
   263  				Properties: []*resultstore.Property{
   264  					{
   265  						Key:   "Instance",
   266  						Value: "build-id",
   267  					},
   268  					{
   269  						Key:   "Job",
   270  						Value: "spec-job",
   271  					},
   272  					{
   273  						Key:   "Prow_Dashboard_URL",
   274  						Value: "https://prow/url",
   275  					},
   276  					{
   277  						Key:   "Env",
   278  						Value: "env1=env1-value",
   279  					},
   280  					{
   281  						Key:   "Env",
   282  						Value: "env2=env2-value",
   283  					},
   284  				},
   285  				StatusAttributes: &resultstore.StatusAttributes{
   286  					Status: resultstore.Status_PASSED,
   287  				},
   288  				Timing: &resultstore.Timing{
   289  					StartTime: &timestamppb.Timestamp{
   290  						Seconds: 100,
   291  					},
   292  					Duration: &durationpb.Duration{
   293  						Seconds: 0,
   294  					},
   295  				},
   296  				WorkspaceInfo: &resultstore.WorkspaceInfo{
   297  					CommandLines: []*resultstore.CommandLine{
   298  						{
   299  							Label: "original",
   300  							Tool:  "command",
   301  							Args:  []string{"arg-1", "arg-2"},
   302  						},
   303  					},
   304  				},
   305  			},
   306  		},
   307  		{
   308  			desc:    "job nil",
   309  			payload: &Payload{},
   310  			wantErr: true,
   311  		},
   312  	} {
   313  		t.Run(tc.desc, func(t *testing.T) {
   314  			got, err := tc.payload.Invocation()
   315  			if err != nil {
   316  				if tc.wantErr {
   317  					t.Logf("got expected error: %v", err)
   318  					return
   319  				}
   320  				t.Fatalf("got unexpected error: %v", err)
   321  			}
   322  			if tc.wantErr {
   323  				t.Fatal("wanted error, got nil")
   324  			}
   325  			if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   326  				t.Errorf("invocation differs (-want +got):\n%s", diff)
   327  			}
   328  		})
   329  	}
   330  }
   331  
   332  func TestInvocationTiming(t *testing.T) {
   333  	for _, tc := range []struct {
   334  		desc string
   335  		job  *v1.ProwJob
   336  		want *resultstore.Timing
   337  	}{
   338  		{
   339  			desc: "success",
   340  			job: &v1.ProwJob{
   341  				Status: v1.ProwJobStatus{
   342  					StartTime: metav1.Time{
   343  						Time: time.Unix(100, 0),
   344  					},
   345  					CompletionTime: &metav1.Time{
   346  						Time: time.Unix(300, 0),
   347  					},
   348  				},
   349  			},
   350  			want: &resultstore.Timing{
   351  				StartTime: &timestamppb.Timestamp{
   352  					Seconds: 100,
   353  				},
   354  				Duration: &durationpb.Duration{
   355  					Seconds: 200,
   356  				},
   357  			},
   358  		},
   359  		{
   360  			desc: "completion nil",
   361  			job: &v1.ProwJob{
   362  				Status: v1.ProwJobStatus{
   363  					StartTime: metav1.Time{
   364  						Time: time.Unix(100, 0),
   365  					},
   366  					CompletionTime: nil,
   367  				},
   368  			},
   369  			want: &resultstore.Timing{
   370  				StartTime: &timestamppb.Timestamp{
   371  					Seconds: 100,
   372  				},
   373  				Duration: &durationpb.Duration{
   374  					Seconds: 0,
   375  				},
   376  			},
   377  		},
   378  		{
   379  			desc: "job nil",
   380  			job:  nil,
   381  			want: nil,
   382  		},
   383  	} {
   384  		t.Run(tc.desc, func(t *testing.T) {
   385  			got := invocationTiming(tc.job)
   386  			if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   387  				t.Errorf("timing differs (-want +got):\n%s", diff)
   388  			}
   389  		})
   390  	}
   391  }
   392  
   393  func TestInvocationProperties(t *testing.T) {
   394  	for _, tc := range []struct {
   395  		desc    string
   396  		job     *v1.ProwJob
   397  		started *metadata.Started
   398  		want    []*resultstore.Property
   399  	}{
   400  		{
   401  			desc: "success",
   402  			job: &v1.ProwJob{
   403  				Spec: v1.ProwJobSpec{
   404  					Job: "spec-job",
   405  					PodSpec: &corev1.PodSpec{
   406  						Containers: []corev1.Container{
   407  							{
   408  								Name:    "container-1",
   409  								Args:    []string{"arg-1", "arg-2"},
   410  								Command: []string{"command"},
   411  								Env: []corev1.EnvVar{
   412  									{
   413  										Name:  "env1",
   414  										Value: "env1-value",
   415  									},
   416  								},
   417  							},
   418  						},
   419  					},
   420  				},
   421  				Status: v1.ProwJobStatus{
   422  					URL:     "https://prow/url",
   423  					BuildID: "build-id",
   424  				},
   425  			},
   426  			started: &metadata.Started{
   427  				Timestamp:  150,
   428  				RepoCommit: "repo-commit",
   429  				Repos: map[string]string{
   430  					"https://started.repo": "started-branch",
   431  				},
   432  			},
   433  			want: []*resultstore.Property{
   434  				{
   435  					Key:   "Instance",
   436  					Value: "build-id",
   437  				},
   438  				{
   439  					Key:   "Job",
   440  					Value: "spec-job",
   441  				},
   442  				{
   443  					Key:   "Prow_Dashboard_URL",
   444  					Value: "https://prow/url",
   445  				},
   446  				{
   447  					Key:   "Env",
   448  					Value: "env1=env1-value",
   449  				},
   450  				{
   451  					Key:   "Commit",
   452  					Value: "repo-commit",
   453  				},
   454  				{
   455  					Key:   "Branch",
   456  					Value: "started-branch",
   457  				},
   458  				{
   459  					Key:   "Repo",
   460  					Value: "https://started.repo",
   461  				},
   462  			},
   463  		},
   464  		{
   465  			desc: "job nil",
   466  			job:  nil,
   467  			started: &metadata.Started{
   468  				Timestamp:  150,
   469  				RepoCommit: "repo-commit",
   470  				Repos: map[string]string{
   471  					"https://started.repo": "started-branch",
   472  				},
   473  			},
   474  			want: []*resultstore.Property{
   475  				{
   476  					Key:   "Commit",
   477  					Value: "repo-commit",
   478  				},
   479  				{
   480  					Key:   "Branch",
   481  					Value: "started-branch",
   482  				},
   483  				{
   484  					Key:   "Repo",
   485  					Value: "https://started.repo",
   486  				},
   487  			},
   488  		},
   489  	} {
   490  		t.Run(tc.desc, func(t *testing.T) {
   491  			got := invocationProperties(tc.job, tc.started)
   492  			if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   493  				t.Errorf("properties differ (-want +got):\n%s", diff)
   494  			}
   495  		})
   496  	}
   497  }
   498  
   499  func TestStartedProperties(t *testing.T) {
   500  	for _, tc := range []struct {
   501  		desc    string
   502  		started *metadata.Started
   503  		want    []*resultstore.Property
   504  	}{
   505  		{
   506  			desc: "single repo",
   507  			started: &metadata.Started{
   508  				Timestamp:  150,
   509  				RepoCommit: "repo-commit",
   510  				Repos: map[string]string{
   511  					"https://repo1.com": "branch1",
   512  				},
   513  			},
   514  			want: []*resultstore.Property{
   515  				{
   516  					Key:   "Commit",
   517  					Value: "repo-commit",
   518  				},
   519  				{
   520  					Key:   "Branch",
   521  					Value: "branch1",
   522  				},
   523  				{
   524  					Key:   "Repo",
   525  					Value: "https://repo1.com",
   526  				},
   527  			},
   528  		},
   529  		{
   530  			desc: "multi repo",
   531  			started: &metadata.Started{
   532  				Timestamp:  150,
   533  				RepoCommit: "repo-commit",
   534  				Repos: map[string]string{
   535  					"repo/two":                 "branch2",
   536  					"https://repo1-review.com": "branch1",
   537  				},
   538  			},
   539  			want: []*resultstore.Property{
   540  				{
   541  					Key:   "Commit",
   542  					Value: "repo-commit",
   543  				},
   544  				{
   545  					Key:   "Branch",
   546  					Value: "branch1",
   547  				},
   548  				{
   549  					Key:   "Branch",
   550  					Value: "branch2",
   551  				},
   552  				{
   553  					Key:   "Repo",
   554  					Value: "https://repo1.com",
   555  				},
   556  				{
   557  					Key:   "Repo",
   558  					Value: "repo/two",
   559  				},
   560  			},
   561  		},
   562  		{
   563  			desc: "non gerrit",
   564  			started: &metadata.Started{
   565  				Timestamp:  150,
   566  				RepoCommit: "repo-commit",
   567  				Repos: map[string]string{
   568  					"https://repo1.other-review.com": "branch1",
   569  				},
   570  			},
   571  			want: []*resultstore.Property{
   572  				{
   573  					Key:   "Commit",
   574  					Value: "repo-commit",
   575  				},
   576  				{
   577  					Key:   "Branch",
   578  					Value: "branch1",
   579  				},
   580  				{
   581  					Key:   "Repo",
   582  					Value: "https://repo1.other-review.com",
   583  				},
   584  			},
   585  		},
   586  		{
   587  			desc:    "nil",
   588  			started: nil,
   589  			want:    nil,
   590  		},
   591  	} {
   592  		t.Run(tc.desc, func(t *testing.T) {
   593  			got := startedProperties(tc.started)
   594  			if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   595  				t.Errorf("properties differ (-want +got):\n%s", diff)
   596  			}
   597  		})
   598  	}
   599  }
   600  
   601  func TestPodSpecProperties(t *testing.T) {
   602  	for _, tc := range []struct {
   603  		desc    string
   604  		podSpec *corev1.PodSpec
   605  		want    []*resultstore.Property
   606  	}{
   607  		{
   608  			desc: "success",
   609  			podSpec: &corev1.PodSpec{
   610  				Containers: []corev1.Container{
   611  					{
   612  						Name:    "container-1",
   613  						Args:    []string{"arg-1", "arg-2"},
   614  						Command: []string{"command"},
   615  						Env: []corev1.EnvVar{
   616  							{
   617  								Name:  "env1",
   618  								Value: "env1-value",
   619  							},
   620  							{
   621  								Name:  "env2",
   622  								Value: "env2-value",
   623  							},
   624  							{
   625  								Name:  "",
   626  								Value: "skip empty Name",
   627  							},
   628  						},
   629  					},
   630  					{
   631  						Name:    "container-2",
   632  						Args:    []string{"arg-3", "arg-4"},
   633  						Command: []string{"command2"},
   634  						Env: []corev1.EnvVar{
   635  							{
   636  								Name:  "env1",
   637  								Value: "env1-value",
   638  							},
   639  							{
   640  								Name:  "env3",
   641  								Value: "env3-value",
   642  							},
   643  						},
   644  					},
   645  				},
   646  			},
   647  			want: []*resultstore.Property{
   648  				{
   649  					Key:   "Env",
   650  					Value: "env1=env1-value",
   651  				},
   652  				{
   653  					Key:   "Env",
   654  					Value: "env2=env2-value",
   655  				},
   656  				{
   657  					Key:   "Env",
   658  					Value: "env3=env3-value",
   659  				},
   660  			},
   661  		},
   662  		{
   663  			desc:    "nil podspec",
   664  			podSpec: nil,
   665  			want:    nil,
   666  		},
   667  	} {
   668  		t.Run(tc.desc, func(t *testing.T) {
   669  			got := podSpecProperties(tc.podSpec)
   670  			if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   671  				t.Errorf("properties differ (-want +got):\n%s", diff)
   672  			}
   673  		})
   674  	}
   675  }
   676  
   677  func TestStatusAttributes(t *testing.T) {
   678  	for _, tc := range []struct {
   679  		prowState v1.ProwJobState
   680  		want      resultstore.Status
   681  	}{
   682  		{
   683  			prowState: v1.SuccessState,
   684  			want:      resultstore.Status_PASSED,
   685  		},
   686  		{
   687  			prowState: v1.FailureState,
   688  			want:      resultstore.Status_FAILED,
   689  		},
   690  		{
   691  			prowState: v1.AbortedState,
   692  			want:      resultstore.Status_CANCELLED,
   693  		},
   694  		{
   695  			prowState: v1.ErrorState,
   696  			want:      resultstore.Status_INCOMPLETE,
   697  		},
   698  		{
   699  			prowState: v1.PendingState,
   700  			want:      resultstore.Status_TOOL_FAILED,
   701  		},
   702  	} {
   703  		t.Run(string(tc.prowState), func(t *testing.T) {
   704  			job := &v1.ProwJob{
   705  				Status: v1.ProwJobStatus{
   706  					State: tc.prowState,
   707  				},
   708  			}
   709  			want := &resultstore.StatusAttributes{
   710  				Status: tc.want,
   711  			}
   712  			if diff := cmp.Diff(want, invocationStatusAttributes(job), protocmp.Transform()); diff != "" {
   713  				t.Errorf("invocationStatusAttributes differs (-want +got):\n%s", diff)
   714  			}
   715  
   716  		})
   717  	}
   718  }
   719  
   720  func TestDefaultconfiguration(t *testing.T) {
   721  	p := &Payload{}
   722  	got := p.DefaultConfiguration()
   723  	want := &resultstore.Configuration{
   724  		Id: &resultstore.Configuration_Id{
   725  			ConfigurationId: "default",
   726  		},
   727  	}
   728  	if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
   729  		t.Errorf("defaultConfiguration differs (-want +got):\n%s", diff)
   730  	}
   731  }
   732  
   733  func TestOverallTarget(t *testing.T) {
   734  	for _, tc := range []struct {
   735  		desc    string
   736  		payload *Payload
   737  		want    *resultstore.Target
   738  	}{
   739  		{
   740  			desc: "success",
   741  			payload: &Payload{
   742  				Job: &v1.ProwJob{
   743  					Spec: v1.ProwJobSpec{
   744  						Job: "spec-job",
   745  					},
   746  				},
   747  			},
   748  			want: &resultstore.Target{
   749  				Id: &resultstore.Target_Id{
   750  					TargetId: "spec-job",
   751  				},
   752  				TargetAttributes: &resultstore.TargetAttributes{
   753  					Type: resultstore.TargetType_TEST,
   754  				},
   755  				Visible: true,
   756  			},
   757  		},
   758  		{
   759  			desc:    "nil job",
   760  			payload: &Payload{},
   761  			want: &resultstore.Target{
   762  				Id: &resultstore.Target_Id{
   763  					TargetId: "Unknown",
   764  				},
   765  				TargetAttributes: &resultstore.TargetAttributes{
   766  					Type: resultstore.TargetType_TEST,
   767  				},
   768  				Visible: true,
   769  			},
   770  		},
   771  	} {
   772  		t.Run(tc.desc, func(t *testing.T) {
   773  			if diff := cmp.Diff(tc.want, tc.payload.OverallTarget(), protocmp.Transform()); diff != "" {
   774  				t.Errorf("overallTarget differs (-want +got):\n%s", diff)
   775  			}
   776  		})
   777  	}
   778  }
   779  
   780  func TestConfiguredTarget(t *testing.T) {
   781  	for _, tc := range []struct {
   782  		desc    string
   783  		payload *Payload
   784  		want    *resultstore.ConfiguredTarget
   785  	}{
   786  		{
   787  			desc: "success",
   788  			payload: &Payload{
   789  				Job: &v1.ProwJob{
   790  					Spec: v1.ProwJobSpec{
   791  						Job: "spec-job",
   792  					},
   793  					Status: v1.ProwJobStatus{
   794  						State: v1.SuccessState,
   795  					},
   796  				},
   797  				Started: &metadata.Started{
   798  					Timestamp:  150,
   799  					RepoCommit: "repo-commit",
   800  					Repos: map[string]string{
   801  						"repo-key": "repo-value",
   802  					},
   803  				},
   804  				Finished: &metadata.Finished{
   805  					Timestamp: int64Pointer(250),
   806  				},
   807  			},
   808  			want: &resultstore.ConfiguredTarget{
   809  				Id: &resultstore.ConfiguredTarget_Id{
   810  					TargetId:        "spec-job",
   811  					ConfigurationId: "default",
   812  				},
   813  				StatusAttributes: &resultstore.StatusAttributes{
   814  					Status: resultstore.Status_PASSED,
   815  				},
   816  				Timing: &resultstore.Timing{
   817  					StartTime: &timestamppb.Timestamp{
   818  						Seconds: 150,
   819  					},
   820  					Duration: &durationpb.Duration{
   821  						Seconds: 100,
   822  					},
   823  				},
   824  			},
   825  		},
   826  		{
   827  			desc:    "nil job",
   828  			payload: &Payload{},
   829  			want: &resultstore.ConfiguredTarget{
   830  				Id: &resultstore.ConfiguredTarget_Id{
   831  					TargetId:        "Unknown",
   832  					ConfigurationId: "default",
   833  				},
   834  				StatusAttributes: &resultstore.StatusAttributes{
   835  					Status: resultstore.Status_TOOL_FAILED,
   836  				},
   837  			},
   838  		},
   839  	} {
   840  		t.Run(tc.desc, func(t *testing.T) {
   841  			if diff := cmp.Diff(tc.want, tc.payload.ConfiguredTarget(), protocmp.Transform()); diff != "" {
   842  				t.Errorf("configuredTarget differs (-want +got):\n%s", diff)
   843  			}
   844  		})
   845  	}
   846  }
   847  
   848  func TestOverallAction(t *testing.T) {
   849  	for _, tc := range []struct {
   850  		desc    string
   851  		payload *Payload
   852  		want    *resultstore.Action
   853  	}{
   854  		{
   855  			desc: "success",
   856  			payload: &Payload{
   857  				Job: &v1.ProwJob{
   858  					Spec: v1.ProwJobSpec{
   859  						Job: "spec-job",
   860  					},
   861  					Status: v1.ProwJobStatus{
   862  						State: v1.SuccessState,
   863  					},
   864  				},
   865  				Started: &metadata.Started{
   866  					Timestamp:  150,
   867  					RepoCommit: "repo-commit",
   868  					Repos: map[string]string{
   869  						"repo-key": "repo-value",
   870  					},
   871  				},
   872  				Finished: &metadata.Finished{
   873  					Timestamp: int64Pointer(250),
   874  				},
   875  			},
   876  			want: &resultstore.Action{
   877  				Id: &resultstore.Action_Id{
   878  					TargetId:        "spec-job",
   879  					ConfigurationId: "default",
   880  					ActionId:        "overall",
   881  				},
   882  				StatusAttributes: &resultstore.StatusAttributes{
   883  					Status: resultstore.Status_PASSED,
   884  				},
   885  				ActionType: &resultstore.Action_TestAction{},
   886  				Timing: &resultstore.Timing{
   887  					StartTime: &timestamppb.Timestamp{
   888  						Seconds: 150,
   889  					},
   890  					Duration: &durationpb.Duration{
   891  						Seconds: 100,
   892  					},
   893  				},
   894  			},
   895  		},
   896  		{
   897  			desc: "started nil",
   898  			payload: &Payload{
   899  				Job: &v1.ProwJob{
   900  					Spec: v1.ProwJobSpec{
   901  						Job: "spec-job",
   902  					},
   903  					Status: v1.ProwJobStatus{
   904  						State: v1.ErrorState,
   905  					},
   906  				},
   907  				Finished: &metadata.Finished{
   908  					Timestamp: int64Pointer(250),
   909  				},
   910  			},
   911  			want: &resultstore.Action{
   912  				Id: &resultstore.Action_Id{
   913  					TargetId:        "spec-job",
   914  					ConfigurationId: "default",
   915  					ActionId:        "overall",
   916  				},
   917  				StatusAttributes: &resultstore.StatusAttributes{
   918  					Status: resultstore.Status_INCOMPLETE,
   919  				},
   920  				ActionType: &resultstore.Action_TestAction{},
   921  			},
   922  		},
   923  		{
   924  			desc: "finished nil use completion time",
   925  			payload: &Payload{
   926  				Job: &v1.ProwJob{
   927  					Spec: v1.ProwJobSpec{
   928  						Job: "spec-job",
   929  					},
   930  					Status: v1.ProwJobStatus{
   931  						State: v1.FailureState,
   932  						CompletionTime: &metav1.Time{
   933  							Time: time.Unix(250, 0),
   934  						},
   935  					},
   936  				},
   937  				Started: &metadata.Started{
   938  					Timestamp:  150,
   939  					RepoCommit: "repo-commit",
   940  					Repos: map[string]string{
   941  						"repo-key": "repo-value",
   942  					},
   943  				},
   944  			},
   945  			want: &resultstore.Action{
   946  				Id: &resultstore.Action_Id{
   947  					TargetId:        "spec-job",
   948  					ConfigurationId: "default",
   949  					ActionId:        "overall",
   950  				},
   951  				StatusAttributes: &resultstore.StatusAttributes{
   952  					Status: resultstore.Status_FAILED,
   953  				},
   954  				ActionType: &resultstore.Action_TestAction{},
   955  				Timing: &resultstore.Timing{
   956  					StartTime: &timestamppb.Timestamp{
   957  						Seconds: 150,
   958  					},
   959  					Duration: &durationpb.Duration{
   960  						Seconds: 100,
   961  					},
   962  				},
   963  			},
   964  		},
   965  		{
   966  			desc: "finished and job completion time nil",
   967  			payload: &Payload{
   968  				Job: &v1.ProwJob{
   969  					Spec: v1.ProwJobSpec{
   970  						Job: "spec-job",
   971  					},
   972  					Status: v1.ProwJobStatus{
   973  						CompletionTime: nil,
   974  					},
   975  				},
   976  				Started: &metadata.Started{
   977  					Timestamp:  150,
   978  					RepoCommit: "repo-commit",
   979  					Repos: map[string]string{
   980  						"repo-key": "repo-value",
   981  					},
   982  				},
   983  			},
   984  			want: &resultstore.Action{
   985  				Id: &resultstore.Action_Id{
   986  					TargetId:        "spec-job",
   987  					ConfigurationId: "default",
   988  					ActionId:        "overall",
   989  				},
   990  				StatusAttributes: &resultstore.StatusAttributes{
   991  					Status: resultstore.Status_TOOL_FAILED,
   992  				},
   993  				ActionType: &resultstore.Action_TestAction{},
   994  			},
   995  		},
   996  		{
   997  			desc: "job nil",
   998  			payload: &Payload{
   999  				Started: &metadata.Started{
  1000  					Timestamp:  150,
  1001  					RepoCommit: "repo-commit",
  1002  					Repos: map[string]string{
  1003  						"repo-key": "repo-value",
  1004  					},
  1005  				},
  1006  				Finished: &metadata.Finished{
  1007  					Timestamp: int64Pointer(250),
  1008  				},
  1009  			},
  1010  			want: &resultstore.Action{
  1011  				Id: &resultstore.Action_Id{
  1012  					TargetId:        "Unknown",
  1013  					ConfigurationId: "default",
  1014  					ActionId:        "overall",
  1015  				},
  1016  				StatusAttributes: &resultstore.StatusAttributes{
  1017  					Status: resultstore.Status_TOOL_FAILED,
  1018  				},
  1019  				ActionType: &resultstore.Action_TestAction{},
  1020  				Timing: &resultstore.Timing{
  1021  					StartTime: &timestamppb.Timestamp{
  1022  						Seconds: 150,
  1023  					},
  1024  					Duration: &durationpb.Duration{
  1025  						Seconds: 100,
  1026  					},
  1027  				},
  1028  			},
  1029  		},
  1030  	} {
  1031  		t.Run(tc.desc, func(t *testing.T) {
  1032  			if diff := cmp.Diff(tc.want, tc.payload.OverallAction(), protocmp.Transform()); diff != "" {
  1033  				t.Errorf("overallAction differs (-want +got):\n%s", diff)
  1034  			}
  1035  		})
  1036  	}
  1037  }