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

     1  /*
     2  Copyright 2022 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 tide
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"strconv"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/andygrunwald/go-gerrit"
    28  	"github.com/google/go-cmp/cmp"
    29  	githubql "github.com/shurcooL/githubv4"
    30  	"github.com/sirupsen/logrus"
    31  	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    32  	"sigs.k8s.io/prow/pkg/config"
    33  	"sigs.k8s.io/prow/pkg/git/types"
    34  	"sigs.k8s.io/prow/pkg/kube"
    35  	"sigs.k8s.io/prow/pkg/tide/blockers"
    36  
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  	fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    39  )
    40  
    41  var _ gerritClient = (*fakeGerritClient)(nil)
    42  
    43  type fakeGerritClient struct {
    44  	// map{org: map{project: []changes}}
    45  	changes map[string]map[string][]gerrit.ChangeInfo
    46  }
    47  
    48  func newFakeGerritClient() *fakeGerritClient {
    49  	return &fakeGerritClient{
    50  		changes: make(map[string]map[string][]gerrit.ChangeInfo),
    51  	}
    52  }
    53  
    54  func (f *fakeGerritClient) QueryChangesForProject(instance, project string, lastUpdate time.Time, rateLimit int, addtionalFilters ...string) ([]gerrit.ChangeInfo, error) {
    55  	if f.changes == nil || f.changes[instance] == nil || f.changes[instance][project] == nil {
    56  		return nil, errors.New("queries project doesn't exist")
    57  	}
    58  
    59  	return f.changes[instance][project], nil
    60  }
    61  
    62  func (f *fakeGerritClient) GetChange(instance, id string, addtionalFields ...string) (*gerrit.ChangeInfo, error) {
    63  	if f.changes == nil || f.changes[instance] == nil {
    64  		return nil, errors.New("instance not exist")
    65  	}
    66  	for _, prs := range f.changes[instance] {
    67  		for _, pr := range prs {
    68  			if pr.ID == id {
    69  				return &pr, nil
    70  			}
    71  		}
    72  	}
    73  	return nil, errors.New("change not exist")
    74  }
    75  
    76  func (f *fakeGerritClient) GetBranchRevision(instance, project, branch string) (string, error) {
    77  	return "abc", nil
    78  }
    79  
    80  func (f *fakeGerritClient) SubmitChange(instance, id string, wait bool) (*gerrit.ChangeInfo, error) {
    81  	return f.GetChange(instance, id)
    82  }
    83  
    84  func (f *fakeGerritClient) SetReview(instance, id, revision, message string, _ map[string]string) error {
    85  	change, err := f.GetChange(instance, id)
    86  	if err != nil {
    87  		return fmt.Errorf("change not found: %v", err)
    88  	}
    89  	revNum, err := strconv.Atoi(revision)
    90  	if err != nil {
    91  		return fmt.Errorf("failed converting revision '%s' to int: %v", revision, err)
    92  	}
    93  	change.Messages = append(change.Messages, gerrit.ChangeMessageInfo{
    94  		RevisionNumber: revNum,
    95  		Message:        message,
    96  	})
    97  
    98  	return nil
    99  }
   100  
   101  func (f *fakeGerritClient) addChanges(instance, project string, changes []gerrit.ChangeInfo) {
   102  	if _, ok := f.changes[instance]; !ok {
   103  		f.changes[instance] = make(map[string][]gerrit.ChangeInfo)
   104  	}
   105  	if _, ok := f.changes[instance][project]; !ok {
   106  		f.changes[instance][project] = []gerrit.ChangeInfo{}
   107  	}
   108  	f.changes[instance][project] = append(f.changes[instance][project], changes...)
   109  }
   110  
   111  func TestGerritQueryParam(t *testing.T) {
   112  	tests := []struct {
   113  		name  string
   114  		optIn bool
   115  		want  string
   116  	}{
   117  		{
   118  			name:  "default",
   119  			optIn: false,
   120  			want:  "status:open+-is:wip+is:submittable+-label:Prow-Auto-Submit=-1+label:Prow-Auto-Submit",
   121  		},
   122  		{
   123  			name:  "opt-in",
   124  			optIn: true,
   125  			want:  "status:open+-is:wip+is:submittable+-label:Prow-Auto-Submit=-1",
   126  		},
   127  	}
   128  
   129  	for _, tc := range tests {
   130  		tc := tc
   131  		t.Run(tc.name, func(t *testing.T) {
   132  			if want, got := tc.want, gerritQueryParam(tc.optIn); want != got {
   133  				t.Errorf("Wrong query param. Want: %s, got: %s", want, got)
   134  			}
   135  		})
   136  	}
   137  }
   138  
   139  func TestQuery(t *testing.T) {
   140  	tests := []struct {
   141  		name    string
   142  		queries config.GerritOrgRepoConfigs
   143  		prs     map[string]map[string][]gerrit.ChangeInfo
   144  		expect  map[string]CodeReviewCommon
   145  		wantErr bool
   146  	}{
   147  		{
   148  			name: "single",
   149  			queries: config.GerritOrgRepoConfigs{
   150  				{
   151  					Org:   "foo1",
   152  					Repos: []string{"bar1"},
   153  				},
   154  			},
   155  			prs: map[string]map[string][]gerrit.ChangeInfo{
   156  				"foo1": {
   157  					"bar1": {
   158  						gerrit.ChangeInfo{
   159  							Number:  1,
   160  							Project: "bar1",
   161  						},
   162  					},
   163  				},
   164  			},
   165  			expect: map[string]CodeReviewCommon{
   166  				"foo1/bar1#1": *CodeReviewCommonFromGerrit(&gerrit.ChangeInfo{Number: 1, Project: "bar1"}, "foo1"),
   167  			},
   168  		},
   169  		{
   170  			name: "multiple",
   171  			queries: config.GerritOrgRepoConfigs{
   172  				{
   173  					Org:   "foo1",
   174  					Repos: []string{"bar1", "bar2"},
   175  				},
   176  				{
   177  					Org:   "foo2",
   178  					Repos: []string{"bar3", "bar4"},
   179  				},
   180  			},
   181  			prs: map[string]map[string][]gerrit.ChangeInfo{
   182  				"foo1": {
   183  					"bar1": {
   184  						gerrit.ChangeInfo{
   185  							Number:  1,
   186  							Project: "bar1",
   187  						},
   188  					},
   189  					"bar2": {
   190  						gerrit.ChangeInfo{
   191  							Number:  2,
   192  							Project: "bar2",
   193  						},
   194  					},
   195  				},
   196  				"foo2": {
   197  					"bar3": {
   198  						gerrit.ChangeInfo{
   199  							Number:  1,
   200  							Project: "bar3",
   201  						},
   202  					},
   203  					"bar4": {
   204  						gerrit.ChangeInfo{
   205  							Number:  2,
   206  							Project: "bar4",
   207  						},
   208  					},
   209  				},
   210  			},
   211  			expect: map[string]CodeReviewCommon{
   212  				"foo1/bar1#1": *CodeReviewCommonFromGerrit(&gerrit.ChangeInfo{Number: 1, Project: "bar1"}, "foo1"),
   213  				"foo1/bar2#2": *CodeReviewCommonFromGerrit(&gerrit.ChangeInfo{Number: 2, Project: "bar2"}, "foo1"),
   214  				"foo2/bar3#1": *CodeReviewCommonFromGerrit(&gerrit.ChangeInfo{Number: 1, Project: "bar3"}, "foo2"),
   215  				"foo2/bar4#2": *CodeReviewCommonFromGerrit(&gerrit.ChangeInfo{Number: 2, Project: "bar4"}, "foo2"),
   216  			},
   217  		},
   218  		{
   219  			name: "not-configured",
   220  			queries: config.GerritOrgRepoConfigs{
   221  				{
   222  					Org:   "foo5",
   223  					Repos: []string{"bar1", "bar2"},
   224  				},
   225  				{
   226  					Org:   "foo6",
   227  					Repos: []string{"bar3", "bar4"},
   228  				},
   229  			},
   230  			prs: map[string]map[string][]gerrit.ChangeInfo{
   231  				"foo1": {
   232  					"bar1": {
   233  						gerrit.ChangeInfo{
   234  							Number:  1,
   235  							Project: "bar1",
   236  						},
   237  					},
   238  					"bar2": {
   239  						gerrit.ChangeInfo{
   240  							Number:  2,
   241  							Project: "bar2",
   242  						},
   243  					},
   244  				},
   245  				"foo2": {
   246  					"bar3": {
   247  						gerrit.ChangeInfo{
   248  							Number:  1,
   249  							Project: "bar3",
   250  						},
   251  					},
   252  					"bar4": {
   253  						gerrit.ChangeInfo{
   254  							Number:  2,
   255  							Project: "bar4",
   256  						},
   257  					},
   258  				},
   259  			},
   260  			wantErr: true,
   261  		},
   262  		{
   263  			name: "no-pr",
   264  			queries: config.GerritOrgRepoConfigs{
   265  				{
   266  					Org:   "foo1",
   267  					Repos: []string{"bar1"},
   268  				},
   269  			},
   270  			prs: map[string]map[string][]gerrit.ChangeInfo{
   271  				"foo1": {
   272  					"bar1": {},
   273  				},
   274  			},
   275  			expect: map[string]CodeReviewCommon{},
   276  		},
   277  	}
   278  
   279  	for _, tc := range tests {
   280  		tc := tc
   281  		t.Run(tc.name, func(t *testing.T) {
   282  			cfg := config.Config{
   283  				ProwConfig: config.ProwConfig{
   284  					Tide: config.Tide{
   285  						Gerrit: &config.TideGerritConfig{
   286  							Queries: tc.queries,
   287  						},
   288  					},
   289  				},
   290  			}
   291  
   292  			fc := newGerritProvider(logrus.WithContext(context.Background()), func() *config.Config { return &cfg }, nil, nil, "", "", 0, 0)
   293  			fgc := newFakeGerritClient()
   294  
   295  			for instance, projs := range tc.prs {
   296  				for project, changes := range projs {
   297  					fgc.addChanges(instance, project, changes)
   298  				}
   299  			}
   300  			fc.gc = fgc
   301  
   302  			got, err := fc.Query()
   303  			if (tc.wantErr && err == nil) || (!tc.wantErr && err != nil) {
   304  				t.Fatalf("Error mismatch. Want: %v, got: %v", tc.wantErr, err)
   305  			}
   306  			if diff := cmp.Diff(tc.expect, got); diff != "" {
   307  				t.Fatalf("Query result mismatch. Want(-), got(+):\n%s", diff)
   308  			}
   309  		})
   310  	}
   311  }
   312  
   313  func TestBlocker(t *testing.T) {
   314  	fc := &GerritProvider{}
   315  	want := blockers.Blockers{}
   316  	var wantErr error
   317  	got, gotErr := fc.blockers()
   318  	if diff := cmp.Diff(want, got); diff != "" {
   319  		t.Errorf("Blocker mismatch. Want(-), got(+):\n%s", diff)
   320  	}
   321  	if wantErr != gotErr {
   322  		t.Errorf("Error mismatch. Want: %v, got: %v", wantErr, gotErr)
   323  	}
   324  }
   325  
   326  func TestIsAllowedToMerge(t *testing.T) {
   327  	tests := []struct {
   328  		name      string
   329  		mergeable string
   330  		want      string
   331  		wantErr   error
   332  	}{
   333  		{
   334  			name:      "conflict",
   335  			mergeable: string(githubql.MergeableStateConflicting),
   336  			want:      "PR has a merge conflict.",
   337  		},
   338  		{
   339  			name:      "normal",
   340  			mergeable: string(githubql.MergeableStateMergeable),
   341  		},
   342  	}
   343  
   344  	for _, tc := range tests {
   345  		tc := tc
   346  		t.Run(tc.name, func(t *testing.T) {
   347  			fc := &GerritProvider{}
   348  			got, gotErr := fc.isAllowedToMerge(&CodeReviewCommon{Mergeable: tc.mergeable})
   349  
   350  			if diff := cmp.Diff(tc.want, got); diff != "" {
   351  				t.Errorf("Blocker mismatch. Want(-), got(+):\n%s", diff)
   352  			}
   353  			if tc.wantErr != gotErr {
   354  				t.Errorf("Error mismatch. Want: %v, got: %v", tc.wantErr, gotErr)
   355  			}
   356  		})
   357  	}
   358  }
   359  
   360  func TestGetRef(t *testing.T) {
   361  	fgc := newFakeGerritClient()
   362  	fc := &GerritProvider{gc: fgc}
   363  	got, _ := fc.GetRef("", "", "")
   364  
   365  	want := "abc"
   366  	if diff := cmp.Diff(want, got); diff != "" {
   367  		t.Errorf("Blocker mismatch. Want(-), got(+):\n%s", diff)
   368  	}
   369  }
   370  
   371  func TestGerritHeadContexts(t *testing.T) {
   372  	tests := []struct {
   373  		name    string
   374  		jobs    []prowapi.ProwJob
   375  		want    []Context
   376  		wantErr error
   377  	}{
   378  		{
   379  			name: "normal",
   380  			jobs: []prowapi.ProwJob{
   381  				{
   382  					ObjectMeta: metav1.ObjectMeta{
   383  						Name:      "not-important-1",
   384  						Namespace: "prowjobs",
   385  						Labels: map[string]string{
   386  							kube.GerritRevision:   "abc123",
   387  							kube.ProwJobTypeLabel: string(prowapi.PresubmitJob),
   388  							kube.OrgLabel:         "foo1",
   389  							kube.RepoLabel:        "bar1",
   390  							kube.PullLabel:        "1",
   391  						},
   392  					},
   393  					Spec: prowapi.ProwJobSpec{
   394  						Type:    prowapi.PresubmitJob,
   395  						Job:     "job-1",
   396  						Context: "job-1",
   397  						Refs: &prowapi.Refs{
   398  							BaseSHA: "def123",
   399  						},
   400  					},
   401  					Status: prowapi.ProwJobStatus{
   402  						State:       prowapi.SuccessState,
   403  						Description: "desc",
   404  					},
   405  				},
   406  			},
   407  			want: []Context{
   408  				{
   409  					Context:     "job-1",
   410  					Description: "desc\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\xe2\x80\x81\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001\u2001 BaseSHA:def123",
   411  					State:       "success",
   412  				},
   413  			},
   414  		},
   415  		{
   416  			name: "periodic",
   417  			jobs: []prowapi.ProwJob{
   418  				{
   419  					ObjectMeta: metav1.ObjectMeta{
   420  						Name:      "not-important-1",
   421  						Namespace: "prowjobs",
   422  						Labels: map[string]string{
   423  							kube.GerritRevision:   "abc123",
   424  							kube.ProwJobTypeLabel: string(prowapi.PeriodicJob),
   425  							kube.OrgLabel:         "foo1",
   426  							kube.RepoLabel:        "bar1",
   427  							kube.PullLabel:        "1",
   428  						},
   429  					},
   430  					Spec: prowapi.ProwJobSpec{
   431  						Type:    prowapi.PeriodicJob,
   432  						Job:     "job-1",
   433  						Context: "job-1",
   434  						Refs: &prowapi.Refs{
   435  							BaseSHA: "def123",
   436  						},
   437  					},
   438  					Status: prowapi.ProwJobStatus{
   439  						State:       prowapi.SuccessState,
   440  						Description: "desc",
   441  					},
   442  				},
   443  			},
   444  		},
   445  		{
   446  			name: "wrong-org",
   447  			jobs: []prowapi.ProwJob{
   448  				{
   449  					ObjectMeta: metav1.ObjectMeta{
   450  						Name:      "not-important-1",
   451  						Namespace: "prowjobs",
   452  						Labels: map[string]string{
   453  							kube.GerritRevision:   "abc123",
   454  							kube.ProwJobTypeLabel: string(prowapi.PresubmitJob),
   455  							kube.OrgLabel:         "foo2",
   456  							kube.RepoLabel:        "bar1",
   457  							kube.PullLabel:        "1",
   458  						},
   459  					},
   460  					Spec: prowapi.ProwJobSpec{
   461  						Type:    prowapi.PresubmitJob,
   462  						Job:     "job-1",
   463  						Context: "job-1",
   464  						Refs: &prowapi.Refs{
   465  							BaseSHA: "def123",
   466  						},
   467  					},
   468  					Status: prowapi.ProwJobStatus{
   469  						State:       prowapi.SuccessState,
   470  						Description: "desc",
   471  					},
   472  				},
   473  			},
   474  		},
   475  		{
   476  			name: "wrong-repo",
   477  			jobs: []prowapi.ProwJob{
   478  				{
   479  					ObjectMeta: metav1.ObjectMeta{
   480  						Name:      "not-important-1",
   481  						Namespace: "prowjobs",
   482  						Labels: map[string]string{
   483  							kube.GerritRevision:   "abc123",
   484  							kube.ProwJobTypeLabel: string(prowapi.PresubmitJob),
   485  							kube.OrgLabel:         "foo1",
   486  							kube.RepoLabel:        "bar2",
   487  							kube.PullLabel:        "1",
   488  						},
   489  					},
   490  					Spec: prowapi.ProwJobSpec{
   491  						Type:    prowapi.PresubmitJob,
   492  						Job:     "job-1",
   493  						Context: "job-1",
   494  						Refs: &prowapi.Refs{
   495  							BaseSHA: "def123",
   496  						},
   497  					},
   498  					Status: prowapi.ProwJobStatus{
   499  						State:       prowapi.SuccessState,
   500  						Description: "desc",
   501  					},
   502  				},
   503  			},
   504  		},
   505  		{
   506  			name: "wrong-revision",
   507  			jobs: []prowapi.ProwJob{
   508  				{
   509  					ObjectMeta: metav1.ObjectMeta{
   510  						Name:      "not-important-1",
   511  						Namespace: "prowjobs",
   512  						Labels: map[string]string{
   513  							kube.GerritRevision:   "abc456",
   514  							kube.ProwJobTypeLabel: string(prowapi.PresubmitJob),
   515  							kube.OrgLabel:         "foo1",
   516  							kube.RepoLabel:        "bar1",
   517  							kube.PullLabel:        "1",
   518  						},
   519  					},
   520  					Spec: prowapi.ProwJobSpec{
   521  						Type:    prowapi.PresubmitJob,
   522  						Job:     "job-1",
   523  						Context: "job-1",
   524  						Refs: &prowapi.Refs{
   525  							BaseSHA: "def123",
   526  						},
   527  					},
   528  					Status: prowapi.ProwJobStatus{
   529  						State:       prowapi.SuccessState,
   530  						Description: "desc",
   531  					},
   532  				},
   533  			},
   534  		},
   535  		{
   536  			name: "wrong-pull",
   537  			jobs: []prowapi.ProwJob{
   538  				{
   539  					ObjectMeta: metav1.ObjectMeta{
   540  						Name:      "not-important-1",
   541  						Namespace: "prowjobs",
   542  						Labels: map[string]string{
   543  							kube.GerritRevision:   "abc123",
   544  							kube.ProwJobTypeLabel: string(prowapi.PresubmitJob),
   545  							kube.OrgLabel:         "foo1",
   546  							kube.RepoLabel:        "bar1",
   547  							kube.PullLabel:        "2",
   548  						},
   549  					},
   550  					Spec: prowapi.ProwJobSpec{
   551  						Type:    prowapi.PresubmitJob,
   552  						Job:     "job-1",
   553  						Context: "job-1",
   554  						Refs: &prowapi.Refs{
   555  							BaseSHA: "def123",
   556  						},
   557  					},
   558  					Status: prowapi.ProwJobStatus{
   559  						State:       prowapi.SuccessState,
   560  						Description: "desc",
   561  					},
   562  				},
   563  			},
   564  		},
   565  	}
   566  
   567  	for _, tc := range tests {
   568  		tc := tc
   569  		t.Run(tc.name, func(t *testing.T) {
   570  			builder := fakectrlruntimeclient.NewClientBuilder()
   571  			for i := range tc.jobs {
   572  				job := tc.jobs[i]
   573  				complete := metav1.NewTime(time.Now().Add(-time.Millisecond))
   574  				if job.Status.State != prowapi.PendingState && job.Status.State != prowapi.TriggeredState {
   575  					job.Status.CompletionTime = &complete
   576  				}
   577  
   578  				builder.WithRuntimeObjects(&job)
   579  			}
   580  
   581  			fpjc := builder.Build()
   582  			fc := &GerritProvider{pjclientset: fpjc}
   583  
   584  			got, gotErr := fc.headContexts(&CodeReviewCommon{
   585  				HeadRefOID: "abc123",
   586  				Org:        "foo1",
   587  				Repo:       "bar1",
   588  				Number:     1,
   589  			})
   590  
   591  			if diff := cmp.Diff(tc.want, got); diff != "" {
   592  				t.Errorf("Blocker mismatch. Want(-), got(+):\n%s", diff)
   593  			}
   594  			if tc.wantErr != gotErr {
   595  				t.Errorf("Error mismatch. Want: %v, got: %v", tc.wantErr, gotErr)
   596  			}
   597  		})
   598  	}
   599  }
   600  
   601  func TestMergePR(t *testing.T) {
   602  	tests := []struct {
   603  		name          string
   604  		subpool       subpool
   605  		clientChanges map[string]map[string][]gerrit.ChangeInfo
   606  		prs           []gerrit.ChangeInfo
   607  		wantErr       error
   608  	}{
   609  		{
   610  			name: "single",
   611  			subpool: subpool{
   612  				org:  "org",
   613  				repo: "repo",
   614  			},
   615  			clientChanges: map[string]map[string][]gerrit.ChangeInfo{
   616  				"org": {
   617  					"repo": {
   618  						{
   619  							ID: "abc123",
   620  						},
   621  					},
   622  				},
   623  			},
   624  			prs: []gerrit.ChangeInfo{
   625  				{
   626  					ID: "abc123",
   627  				},
   628  			},
   629  			wantErr: nil,
   630  		},
   631  		{
   632  			name: "multiple",
   633  			subpool: subpool{
   634  				org:  "org",
   635  				repo: "repo",
   636  			},
   637  			clientChanges: map[string]map[string][]gerrit.ChangeInfo{
   638  				"org": {
   639  					"repo": {
   640  						{
   641  							ID: "abc123",
   642  						},
   643  						{
   644  							ID: "def456",
   645  						},
   646  					},
   647  				},
   648  			},
   649  			prs: []gerrit.ChangeInfo{
   650  				{
   651  					ID: "abc123",
   652  				},
   653  				{
   654  					ID: "def456",
   655  				},
   656  			},
   657  			wantErr: nil,
   658  		},
   659  		{
   660  			name: "single-error",
   661  			subpool: subpool{
   662  				org:  "org",
   663  				repo: "repo",
   664  			},
   665  			// Empty changes results in SubmitChange error.
   666  			clientChanges: map[string]map[string][]gerrit.ChangeInfo{},
   667  			prs: []gerrit.ChangeInfo{
   668  				{
   669  					ID: "abc123",
   670  				},
   671  			},
   672  			wantErr: errors.New("failed submitting change 'org' from org 'abc123': instance not exist"),
   673  		},
   674  		{
   675  			name: "multiple-error",
   676  			subpool: subpool{
   677  				org:  "org",
   678  				repo: "repo",
   679  			},
   680  			// Empty changes results in SubmitChange error.
   681  			clientChanges: map[string]map[string][]gerrit.ChangeInfo{},
   682  			prs: []gerrit.ChangeInfo{
   683  				{
   684  					ID: "abc123",
   685  				},
   686  				{
   687  					ID: "def456",
   688  				},
   689  			},
   690  			wantErr: errors.New("[failed submitting change 'org' from org 'abc123': instance not exist, failed submitting change 'org' from org 'def456': instance not exist]"),
   691  		},
   692  		{
   693  			name: "partial-error",
   694  			subpool: subpool{
   695  				org:  "org",
   696  				repo: "repo",
   697  			},
   698  			clientChanges: map[string]map[string][]gerrit.ChangeInfo{
   699  				"org": {
   700  					"repo": {
   701  						{
   702  							ID: "abc123",
   703  						},
   704  					},
   705  				},
   706  			},
   707  			prs: []gerrit.ChangeInfo{
   708  				{
   709  					ID: "abc123",
   710  				},
   711  				{
   712  					ID: "def456",
   713  				},
   714  			},
   715  			wantErr: errors.New("failed submitting change 'org' from org 'def456': change not exist"),
   716  		},
   717  	}
   718  
   719  	for _, tc := range tests {
   720  		tc := tc
   721  		t.Run(tc.name, func(t *testing.T) {
   722  			fgc := newFakeGerritClient()
   723  			fgc.changes = tc.clientChanges
   724  			cfg := config.Config{
   725  				ProwConfig: config.ProwConfig{
   726  					Gerrit: config.Gerrit{
   727  						DeckURL: "http://foo.bar",
   728  					},
   729  				},
   730  			}
   731  			fc := &GerritProvider{
   732  				logger: logrus.WithContext(context.Background()),
   733  				gc:     fgc,
   734  				cfg:    func() *config.Config { return &cfg },
   735  			}
   736  
   737  			var prsToMerge []CodeReviewCommon
   738  			for _, pr := range tc.prs {
   739  				prsToMerge = append(prsToMerge, *CodeReviewCommonFromGerrit(&pr, tc.subpool.org))
   740  			}
   741  
   742  			_, gotErr := fc.mergePRs(tc.subpool, prsToMerge, nil)
   743  			if tc.wantErr == nil {
   744  				if gotErr != nil {
   745  					t.Fatalf("Error mismatch. Want nil, got: %v", gotErr)
   746  				}
   747  				return
   748  			}
   749  			if gotErr == nil {
   750  				t.Fatalf("Error mismatch. Want %v, got nil", tc.wantErr)
   751  			}
   752  			if tc.wantErr.Error() != gotErr.Error() {
   753  				t.Fatalf("Error not matching. Want: %v, got: %v", tc.wantErr, gotErr)
   754  			}
   755  		})
   756  	}
   757  }
   758  
   759  func TestGetTideContextPolicy(t *testing.T) {
   760  	tests := []struct {
   761  		name       string
   762  		pr         gerrit.ChangeInfo
   763  		cloneURI   string
   764  		presubmits map[string][]config.Presubmit
   765  		want       contextChecker
   766  		wantErr    error
   767  	}{
   768  		{
   769  			name: "base",
   770  			pr: gerrit.ChangeInfo{
   771  				Project:         "bar1",
   772  				Branch:          "main",
   773  				CurrentRevision: "abc123",
   774  				Labels: map[string]gerrit.LabelInfo{
   775  					"Verified": {
   776  						Optional: false,
   777  					},
   778  				},
   779  			},
   780  			presubmits: map[string][]config.Presubmit{
   781  				"https://foo1/bar1": {
   782  					{
   783  						Reporter: config.Reporter{Context: "job-1"},
   784  						JobBase: config.JobBase{
   785  							Labels: map[string]string{
   786  								"prow.k8s.io/gerrit-report-label": "Verified",
   787  							},
   788  						},
   789  					},
   790  				},
   791  			},
   792  			want: &gerritContextChecker{},
   793  		},
   794  		{
   795  			name: "no-job",
   796  			pr: gerrit.ChangeInfo{
   797  				Project:         "bar1",
   798  				Branch:          "main",
   799  				CurrentRevision: "abc123",
   800  				Labels: map[string]gerrit.LabelInfo{
   801  					"Verified": {
   802  						Optional: false,
   803  					},
   804  				},
   805  			},
   806  			presubmits: map[string][]config.Presubmit{
   807  				"https://foo1/bar1": {},
   808  			},
   809  			want: &gerritContextChecker{},
   810  		},
   811  	}
   812  
   813  	for _, tc := range tests {
   814  		tc := tc
   815  		t.Run(tc.name, func(t *testing.T) {
   816  			cfg := config.Config{JobConfig: config.JobConfig{PresubmitsStatic: tc.presubmits}}
   817  			fc := &GerritProvider{cfg: func() *config.Config { return &cfg }}
   818  
   819  			got, gotErr := fc.GetTideContextPolicy("foo1", tc.pr.Project, tc.pr.Branch, nil, CodeReviewCommonFromGerrit(&tc.pr, "foo1"))
   820  
   821  			if diff := cmp.Diff(tc.want, got); diff != "" {
   822  				t.Errorf("Blocker mismatch. Want(-), got(+):\n%s", diff)
   823  			}
   824  			if tc.wantErr != gotErr {
   825  				t.Errorf("Error mismatch. Want: %v, got: %v", tc.wantErr, gotErr)
   826  			}
   827  		})
   828  	}
   829  }
   830  
   831  func TestPrMergeMethod(t *testing.T) {
   832  	tests := []struct {
   833  		name string
   834  		pr   gerrit.ChangeInfo
   835  		want types.PullRequestMergeType
   836  	}{
   837  		{
   838  			name: "MERGE_IF_NECESSARY",
   839  			pr: gerrit.ChangeInfo{
   840  				SubmitType: "MERGE_IF_NECESSARY",
   841  			},
   842  			want: types.MergeIfNecessary,
   843  		},
   844  		{
   845  			name: "FAST_FORWARD_ONLY",
   846  			pr: gerrit.ChangeInfo{
   847  				SubmitType: "FAST_FORWARD_ONLY",
   848  			},
   849  			want: types.MergeMerge,
   850  		},
   851  		{
   852  			name: "REBASE_IF_NECESSARY",
   853  			pr: gerrit.ChangeInfo{
   854  				SubmitType: "REBASE_IF_NECESSARY",
   855  			},
   856  			want: types.MergeRebase,
   857  		},
   858  		{
   859  			name: "REBASE_ALWAYS",
   860  			pr: gerrit.ChangeInfo{
   861  				SubmitType: "REBASE_ALWAYS",
   862  			},
   863  			want: types.MergeRebase,
   864  		},
   865  		{
   866  			name: "MERGE_ALWAYS",
   867  			pr: gerrit.ChangeInfo{
   868  				SubmitType: "MERGE_ALWAYS",
   869  			},
   870  			want: types.MergeMerge,
   871  		},
   872  		{
   873  			name: "NOT_EXIST",
   874  			pr: gerrit.ChangeInfo{
   875  				SubmitType: "NOT_EXIST",
   876  			},
   877  			want: types.MergeMerge,
   878  		},
   879  	}
   880  
   881  	for _, tc := range tests {
   882  		tc := tc
   883  		t.Run(tc.name, func(t *testing.T) {
   884  			fc := &GerritProvider{}
   885  
   886  			got := fc.prMergeMethod(CodeReviewCommonFromGerrit(&tc.pr, "foo1"))
   887  			if got == nil {
   888  				t.Error("Multiple conflicting merge methods assigned.")
   889  			} else {
   890  				if diff := cmp.Diff(tc.want, *got); diff != "" {
   891  					t.Errorf("Blocker mismatch. Want(-), got(+):\n%s", diff)
   892  				}
   893  			}
   894  		})
   895  	}
   896  }
   897  
   898  func TestJobIsRequiredByTide(t *testing.T) {
   899  	tests := []struct {
   900  		name string
   901  		ps   *config.Presubmit
   902  		crc  *CodeReviewCommon
   903  		want bool
   904  	}{
   905  		{
   906  			name: "default",
   907  			ps:   &config.Presubmit{},
   908  			crc: &CodeReviewCommon{
   909  				Gerrit: &gerrit.ChangeInfo{Labels: map[string]gerrit.LabelInfo{}},
   910  			},
   911  		},
   912  		{
   913  			name: "run-before-merge",
   914  			ps:   &config.Presubmit{RunBeforeMerge: true},
   915  			crc: &CodeReviewCommon{
   916  				Gerrit: &gerrit.ChangeInfo{Labels: map[string]gerrit.LabelInfo{}},
   917  			},
   918  			want: true,
   919  		},
   920  		{
   921  			name: "required-label",
   922  			ps: &config.Presubmit{JobBase: config.JobBase{Labels: map[string]string{
   923  				"prow.k8s.io/gerrit-report-label": "Verified-By-Prow",
   924  			}}},
   925  			crc: &CodeReviewCommon{
   926  				Gerrit: &gerrit.ChangeInfo{Labels: map[string]gerrit.LabelInfo{"Verified-By-Prow": {}}},
   927  			},
   928  			want: true,
   929  		},
   930  		{
   931  			name: "optional-label",
   932  			ps: &config.Presubmit{JobBase: config.JobBase{Labels: map[string]string{
   933  				"prow.k8s.io/gerrit-report-label": "Verified-By-Prow",
   934  			}}},
   935  			crc: &CodeReviewCommon{
   936  				Gerrit: &gerrit.ChangeInfo{Labels: map[string]gerrit.LabelInfo{"Verified-By-Prow": {Optional: true}}},
   937  			},
   938  		},
   939  	}
   940  
   941  	for _, tc := range tests {
   942  		tc := tc
   943  		t.Run(tc.name, func(t *testing.T) {
   944  			fc := &GerritProvider{}
   945  			if want, got := tc.want, fc.jobIsRequiredByTide(tc.ps, tc.crc); want != got {
   946  				t.Errorf("Wrong. Want: %v, got: %v", want, got)
   947  			}
   948  		})
   949  	}
   950  }