go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/internal/gerrit/utils_test.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gerrit
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	. "github.com/smartystreets/goconvey/convey"
    23  	"google.golang.org/protobuf/types/known/timestamppb"
    24  
    25  	"go.chromium.org/luci/common/clock"
    26  	"go.chromium.org/luci/common/clock/testclock"
    27  	gerritpb "go.chromium.org/luci/common/proto/gerrit"
    28  	. "go.chromium.org/luci/common/testing/assertions"
    29  	"go.chromium.org/luci/gae/impl/memory"
    30  )
    31  
    32  func TestGetHost(t *testing.T) {
    33  	t.Parallel()
    34  	ctx := context.Background()
    35  
    36  	Convey("Empty string returns error", t, func() {
    37  		host, err := GetHost(ctx, "")
    38  		So(err, ShouldErrLike, "could not find Gerrit host")
    39  		So(host, ShouldEqual, "")
    40  	})
    41  
    42  	Convey("Non-URL format returns error", t, func() {
    43  		host, err := GetHost(ctx, "[Test] This is a review title, not a URL")
    44  		So(err, ShouldErrLike, "could not find Gerrit host")
    45  		So(host, ShouldEqual, "")
    46  	})
    47  
    48  	Convey("Gets host from review URL", t, func() {
    49  		host, err := GetHost(ctx, " https://chromium-test-review.googlesource.com/c/proj/+/1200003\n")
    50  		So(err, ShouldBeNil)
    51  		So(host, ShouldEqual, "chromium-test-review.googlesource.com")
    52  	})
    53  }
    54  
    55  func TestHasLUCIBisectionComment(t *testing.T) {
    56  	t.Parallel()
    57  	ctx := memory.Use(context.Background())
    58  
    59  	Convey("No comments", t, func() {
    60  		hasLBComment, err := HasLUCIBisectionComment(ctx, &gerritpb.ChangeInfo{})
    61  		So(err, ShouldBeNil)
    62  		So(hasLBComment, ShouldEqual, false)
    63  	})
    64  
    65  	Convey("No LUCI Bisection comment", t, func() {
    66  		change := &gerritpb.ChangeInfo{
    67  			Messages: []*gerritpb.ChangeMessageInfo{
    68  				{
    69  					Id:      "19283",
    70  					Message: "Automated message from Gerrit with no author",
    71  					Tag:     "autogenerated:gerrit:test",
    72  				},
    73  				{
    74  					Id: "19284",
    75  					Author: &gerritpb.AccountInfo{
    76  						Name:      "John Doe",
    77  						Email:     "jdoe@example.com",
    78  						Username:  "jdoe",
    79  						AccountId: 100001,
    80  					},
    81  				},
    82  			},
    83  		}
    84  		hasLBComment, err := HasLUCIBisectionComment(ctx, change)
    85  		So(err, ShouldBeNil)
    86  		So(hasLBComment, ShouldEqual, false)
    87  	})
    88  
    89  	Convey("LUCI Bisection has commented", t, func() {
    90  		// Get the service account email value in this test context
    91  		testEmail, err := ServiceAccountEmail(ctx)
    92  		So(err, ShouldBeNil)
    93  
    94  		change := &gerritpb.ChangeInfo{
    95  			Messages: []*gerritpb.ChangeMessageInfo{
    96  				{
    97  					Id:      "19285",
    98  					Message: "Automated message from Gerrit with no author",
    99  					Tag:     "autogenerated:gerrit:test",
   100  				},
   101  				{
   102  					Id: "19286",
   103  					Author: &gerritpb.AccountInfo{
   104  						Name:      "LUCI Bisection",
   105  						Email:     testEmail,
   106  						Username:  "gae_service_account",
   107  						AccountId: 900009,
   108  					},
   109  				},
   110  				{
   111  					Id: "19287",
   112  					Author: &gerritpb.AccountInfo{
   113  						Name:      "John Doe",
   114  						Email:     "jdoe@example.com",
   115  						Username:  "jdoe",
   116  						AccountId: 100001,
   117  					},
   118  				},
   119  			},
   120  		}
   121  		hasLBComment, err := HasLUCIBisectionComment(ctx, change)
   122  		So(err, ShouldBeNil)
   123  		So(hasLBComment, ShouldEqual, true)
   124  	})
   125  }
   126  
   127  func TestIsOwnedByLUCIBisection(t *testing.T) {
   128  	t.Parallel()
   129  	ctx := memory.Use(context.Background())
   130  
   131  	Convey("No owner", t, func() {
   132  		change := &gerritpb.ChangeInfo{
   133  			Project: "chromium/test/src",
   134  			Number:  615243,
   135  		}
   136  		lbOwned, err := IsOwnedByLUCIBisection(ctx, change)
   137  		So(err, ShouldBeNil)
   138  		So(lbOwned, ShouldEqual, false)
   139  	})
   140  
   141  	Convey("Change is not owned by LUCI Bisection", t, func() {
   142  		change := &gerritpb.ChangeInfo{
   143  			Project: "chromium/test/src",
   144  			Number:  615243,
   145  			Owner: &gerritpb.AccountInfo{
   146  				Name:      "John Doe",
   147  				Email:     "jdoe@example.com",
   148  				Username:  "jdoe",
   149  				AccountId: 100001,
   150  			},
   151  		}
   152  		lbOwned, err := IsOwnedByLUCIBisection(ctx, change)
   153  		So(err, ShouldBeNil)
   154  		So(lbOwned, ShouldEqual, false)
   155  	})
   156  
   157  	Convey("Change is owned by LUCI Bisection", t, func() {
   158  		// Get the service account email value in this test context
   159  		testEmail, err := ServiceAccountEmail(ctx)
   160  		So(err, ShouldBeNil)
   161  
   162  		change := &gerritpb.ChangeInfo{
   163  			Project: "chromium/test/src",
   164  			Number:  615243,
   165  			Owner: &gerritpb.AccountInfo{
   166  				Name:      "LUCI Bisection",
   167  				Email:     testEmail,
   168  				Username:  "gae_service_account",
   169  				AccountId: 900009,
   170  			},
   171  		}
   172  		lbOwned, err := IsOwnedByLUCIBisection(ctx, change)
   173  		So(err, ShouldBeNil)
   174  		So(lbOwned, ShouldEqual, true)
   175  	})
   176  }
   177  
   178  func TestIsRecentSubmit(t *testing.T) {
   179  	t.Parallel()
   180  	ctx := context.Background()
   181  
   182  	// Set test clock
   183  	cl := testclock.New(testclock.TestTimeUTC)
   184  	ctx = clock.Set(ctx, cl)
   185  
   186  	Convey("IsRecentSubmit", t, func() {
   187  		change := &gerritpb.ChangeInfo{
   188  			Project: "chromium/test/src",
   189  			Number:  234567,
   190  		}
   191  		maxAge := time.Duration(6) * time.Hour
   192  
   193  		Convey("change submitted now is recent", func() {
   194  			change.Submitted = timestamppb.New(clock.Now(ctx))
   195  			So(IsRecentSubmit(ctx, change, maxAge), ShouldEqual, true)
   196  		})
   197  
   198  		Convey("change submitted at threshold time is recent", func() {
   199  			change.Submitted = timestamppb.New(clock.Now(ctx).Add(-time.Hour * 6))
   200  			So(IsRecentSubmit(ctx, change, maxAge), ShouldEqual, true)
   201  		})
   202  
   203  		Convey("change submitted a while ago is not recent", func() {
   204  			change.Submitted = timestamppb.New(clock.Now(ctx).Add(-time.Hour * 30))
   205  			So(IsRecentSubmit(ctx, change, maxAge), ShouldEqual, false)
   206  		})
   207  	})
   208  }
   209  
   210  func TestHasAutoRevertOffFlagSet(t *testing.T) {
   211  	t.Parallel()
   212  	ctx := context.Background()
   213  
   214  	Convey("change does not have enough information", t, func() {
   215  		change := &gerritpb.ChangeInfo{
   216  			Project: "chromium/test/src",
   217  			Number:  234567,
   218  		}
   219  		_, err := HasAutoRevertOffFlagSet(ctx, change)
   220  		So(err, ShouldErrLike, "could not get", "info")
   221  	})
   222  
   223  	Convey("change does not mention flag", t, func() {
   224  		change := &gerritpb.ChangeInfo{
   225  			Project:         "chromium/test/src",
   226  			Number:          234567,
   227  			CurrentRevision: "deadbeef",
   228  			Revisions: map[string]*gerritpb.RevisionInfo{
   229  				"deadbeef": {
   230  					Number: 1,
   231  					Kind:   gerritpb.RevisionInfo_REWORK,
   232  					Uploader: &gerritpb.AccountInfo{
   233  						AccountId:       1000096,
   234  						Name:            "John Doe",
   235  						Email:           "jdoe@example.com",
   236  						SecondaryEmails: []string{"johndoe@chromium.org"},
   237  						Username:        "jdoe",
   238  					},
   239  					Ref:         "refs/changes/123",
   240  					Description: "first upload",
   241  					Files: map[string]*gerritpb.FileInfo{
   242  						"go/to/file.go": {
   243  							LinesInserted: 32,
   244  							LinesDeleted:  44,
   245  							SizeDelta:     -567,
   246  							Size:          11984,
   247  						},
   248  					},
   249  					Commit: &gerritpb.CommitInfo{
   250  						Id:      "",
   251  						Message: "Title.\n\nBody is here.\n\nChange-Id: I100deadbeef",
   252  						Parents: []*gerritpb.CommitInfo_Parent{
   253  							{Id: "deadbeef00"},
   254  						},
   255  					},
   256  				},
   257  			},
   258  		}
   259  
   260  		hasFlag, err := HasAutoRevertOffFlagSet(ctx, change)
   261  		So(err, ShouldBeNil)
   262  		So(hasFlag, ShouldEqual, false)
   263  	})
   264  
   265  	Convey("change has flag set", t, func() {
   266  		change := &gerritpb.ChangeInfo{
   267  			Project:         "chromium/test/src",
   268  			Number:          234567,
   269  			CurrentRevision: "deadbeef",
   270  			Revisions: map[string]*gerritpb.RevisionInfo{
   271  				"deadbeef": {
   272  					Number: 1,
   273  					Kind:   gerritpb.RevisionInfo_REWORK,
   274  					Uploader: &gerritpb.AccountInfo{
   275  						AccountId:       1000096,
   276  						Name:            "John Doe",
   277  						Email:           "jdoe@example.com",
   278  						SecondaryEmails: []string{"johndoe@chromium.org"},
   279  						Username:        "jdoe",
   280  					},
   281  					Ref:         "refs/changes/123",
   282  					Description: "first upload",
   283  					Files: map[string]*gerritpb.FileInfo{
   284  						"go/to/file.go": {
   285  							LinesInserted: 32,
   286  							LinesDeleted:  44,
   287  							SizeDelta:     -567,
   288  							Size:          11984,
   289  						},
   290  					},
   291  					Commit: &gerritpb.CommitInfo{
   292  						Id:      "",
   293  						Message: "Title.\n\nBody is here.\n\nNOAUTOREVERT=true\n\nChange-Id: I100deadbeef",
   294  						Parents: []*gerritpb.CommitInfo_Parent{
   295  							{Id: "deadbeef00"},
   296  						},
   297  					},
   298  				},
   299  			},
   300  		}
   301  
   302  		hasFlag, err := HasAutoRevertOffFlagSet(ctx, change)
   303  		So(err, ShouldBeNil)
   304  		So(hasFlag, ShouldEqual, true)
   305  	})
   306  
   307  	Convey("change has flag set with extra whitespace", t, func() {
   308  		change := &gerritpb.ChangeInfo{
   309  			Project:         "chromium/test/src",
   310  			Number:          234567,
   311  			CurrentRevision: "deadbeef",
   312  			Revisions: map[string]*gerritpb.RevisionInfo{
   313  				"deadbeef": {
   314  					Number: 1,
   315  					Kind:   gerritpb.RevisionInfo_REWORK,
   316  					Uploader: &gerritpb.AccountInfo{
   317  						AccountId:       1000096,
   318  						Name:            "John Doe",
   319  						Email:           "jdoe@example.com",
   320  						SecondaryEmails: []string{"johndoe@chromium.org"},
   321  						Username:        "jdoe",
   322  					},
   323  					Ref:         "refs/changes/123",
   324  					Description: "first upload",
   325  					Files: map[string]*gerritpb.FileInfo{
   326  						"go/to/file.go": {
   327  							LinesInserted: 32,
   328  							LinesDeleted:  44,
   329  							SizeDelta:     -567,
   330  							Size:          11984,
   331  						},
   332  					},
   333  					Commit: &gerritpb.CommitInfo{
   334  						Id:      "",
   335  						Message: "Title.\n\nBody is here.\n\nNOAUTOREVERT\t=   true\n\nChange-Id: I100deadbeef",
   336  						Parents: []*gerritpb.CommitInfo_Parent{
   337  							{Id: "deadbeef00"},
   338  						},
   339  					},
   340  				},
   341  			},
   342  		}
   343  
   344  		hasFlag, err := HasAutoRevertOffFlagSet(ctx, change)
   345  		So(err, ShouldBeNil)
   346  		So(hasFlag, ShouldEqual, true)
   347  	})
   348  
   349  	Convey("change has flag set to false", t, func() {
   350  		change := &gerritpb.ChangeInfo{
   351  			Project:         "chromium/test/src",
   352  			Number:          234567,
   353  			CurrentRevision: "deadbeef",
   354  			Revisions: map[string]*gerritpb.RevisionInfo{
   355  				"deadbeef": {
   356  					Number: 1,
   357  					Kind:   gerritpb.RevisionInfo_REWORK,
   358  					Uploader: &gerritpb.AccountInfo{
   359  						AccountId:       1000096,
   360  						Name:            "John Doe",
   361  						Email:           "jdoe@example.com",
   362  						SecondaryEmails: []string{"johndoe@chromium.org"},
   363  						Username:        "jdoe",
   364  					},
   365  					Ref:         "refs/changes/123",
   366  					Description: "first upload",
   367  					Files: map[string]*gerritpb.FileInfo{
   368  						"go/to/file.go": {
   369  							LinesInserted: 32,
   370  							LinesDeleted:  44,
   371  							SizeDelta:     -567,
   372  							Size:          11984,
   373  						},
   374  					},
   375  					Commit: &gerritpb.CommitInfo{
   376  						Id:      "",
   377  						Message: "Title.\n\nBody is here.\n\nNOAUTOREVERT=false SOMEOTHERFLAG=true\n\nChange-Id: I100deadbeef",
   378  						Parents: []*gerritpb.CommitInfo_Parent{
   379  							{Id: "deadbeef00"},
   380  						},
   381  					},
   382  				},
   383  			},
   384  		}
   385  
   386  		hasFlag, err := HasAutoRevertOffFlagSet(ctx, change)
   387  		So(err, ShouldBeNil)
   388  		So(hasFlag, ShouldEqual, false)
   389  	})
   390  }
   391  
   392  func TestAuthorEmail(t *testing.T) {
   393  	t.Parallel()
   394  	ctx := context.Background()
   395  
   396  	Convey("change does not have enough information", t, func() {
   397  		change := &gerritpb.ChangeInfo{
   398  			Project: "chromium/test/src",
   399  			Number:  234567,
   400  		}
   401  		_, err := AuthorEmail(ctx, change)
   402  		So(err, ShouldErrLike, "could not get", "info")
   403  	})
   404  
   405  	Convey("change does not have an author", t, func() {
   406  		change := &gerritpb.ChangeInfo{
   407  			Project:         "chromium/test/src",
   408  			Number:          234567,
   409  			CurrentRevision: "deadbeef",
   410  			Revisions: map[string]*gerritpb.RevisionInfo{
   411  				"deadbeef": {
   412  					Number: 1,
   413  					Kind:   gerritpb.RevisionInfo_REWORK,
   414  					Uploader: &gerritpb.AccountInfo{
   415  						AccountId:       1000096,
   416  						Name:            "John Doe",
   417  						Email:           "jdoe@example.com",
   418  						SecondaryEmails: []string{"johndoe@chromium.org"},
   419  						Username:        "jdoe",
   420  					},
   421  					Ref:         "refs/changes/123",
   422  					Description: "first upload",
   423  					Files: map[string]*gerritpb.FileInfo{
   424  						"go/to/file.go": {
   425  							LinesInserted: 32,
   426  							LinesDeleted:  44,
   427  							SizeDelta:     -567,
   428  							Size:          11984,
   429  						},
   430  					},
   431  					Commit: &gerritpb.CommitInfo{
   432  						Id:      "",
   433  						Message: "Title.\n\nBody is here.\n\nChange-Id: I100deadbeef",
   434  						Parents: []*gerritpb.CommitInfo_Parent{
   435  							{Id: "deadbeef00"},
   436  						},
   437  					},
   438  				},
   439  			},
   440  		}
   441  
   442  		_, err := AuthorEmail(ctx, change)
   443  		So(err, ShouldErrLike, "no author in commit info")
   444  	})
   445  
   446  	Convey("author email is returned", t, func() {
   447  		change := &gerritpb.ChangeInfo{
   448  			Project:         "chromium/test/src",
   449  			Number:          234567,
   450  			CurrentRevision: "deadbeef",
   451  			Revisions: map[string]*gerritpb.RevisionInfo{
   452  				"deadbeef": {
   453  					Number: 1,
   454  					Kind:   gerritpb.RevisionInfo_REWORK,
   455  					Uploader: &gerritpb.AccountInfo{
   456  						AccountId:       1000096,
   457  						Name:            "John Doe",
   458  						Email:           "jdoe@example.com",
   459  						SecondaryEmails: []string{"johndoe@chromium.org"},
   460  						Username:        "jdoe",
   461  					},
   462  					Ref:         "refs/changes/123",
   463  					Description: "first upload",
   464  					Files: map[string]*gerritpb.FileInfo{
   465  						"go/to/file.go": {
   466  							LinesInserted: 32,
   467  							LinesDeleted:  44,
   468  							SizeDelta:     -567,
   469  							Size:          11984,
   470  						},
   471  					},
   472  					Commit: &gerritpb.CommitInfo{
   473  						Id:      "",
   474  						Message: "Title.\n\nBody is here.\n\nNOAUTOREVERT=true\n\nChange-Id: I100deadbeef",
   475  						Parents: []*gerritpb.CommitInfo_Parent{
   476  							{Id: "deadbeef00"},
   477  						},
   478  						Author: &gerritpb.GitPersonInfo{
   479  							Name:  "John Doe",
   480  							Email: "jdoe@example.com",
   481  						},
   482  					},
   483  				},
   484  			},
   485  		}
   486  
   487  		author, err := AuthorEmail(ctx, change)
   488  		So(err, ShouldBeNil)
   489  		So(author, ShouldEqual, "jdoe@example.com")
   490  	})
   491  }
   492  
   493  func TestCommitMessage(t *testing.T) {
   494  	t.Parallel()
   495  	ctx := context.Background()
   496  
   497  	Convey("change does not have enough information", t, func() {
   498  		change := &gerritpb.ChangeInfo{
   499  			Project: "chromium/test/src",
   500  			Number:  234567,
   501  		}
   502  		_, err := CommitMessage(ctx, change)
   503  		So(err, ShouldErrLike, "could not get", "info")
   504  	})
   505  
   506  	Convey("commit message is returned", t, func() {
   507  		expectedMessage := "Title.\n\nBody is here.\n\nNOAUTOREVERT=true\n\nChange-Id: I100deadbeef"
   508  		change := &gerritpb.ChangeInfo{
   509  			Project:         "chromium/test/src",
   510  			Number:          234567,
   511  			CurrentRevision: "deadbeef",
   512  			Revisions: map[string]*gerritpb.RevisionInfo{
   513  				"deadbeef": {
   514  					Number: 1,
   515  					Kind:   gerritpb.RevisionInfo_REWORK,
   516  					Uploader: &gerritpb.AccountInfo{
   517  						AccountId:       1000096,
   518  						Name:            "John Doe",
   519  						Email:           "jdoe@example.com",
   520  						SecondaryEmails: []string{"johndoe@chromium.org"},
   521  						Username:        "jdoe",
   522  					},
   523  					Ref:         "refs/changes/123",
   524  					Description: "first upload",
   525  					Files: map[string]*gerritpb.FileInfo{
   526  						"go/to/file.go": {
   527  							LinesInserted: 32,
   528  							LinesDeleted:  44,
   529  							SizeDelta:     -567,
   530  							Size:          11984,
   531  						},
   532  					},
   533  					Commit: &gerritpb.CommitInfo{
   534  						Id:      "",
   535  						Message: expectedMessage,
   536  						Parents: []*gerritpb.CommitInfo_Parent{
   537  							{Id: "deadbeef00"},
   538  						},
   539  						Author: &gerritpb.GitPersonInfo{
   540  							Name:  "John Doe",
   541  							Email: "jdoe@example.com",
   542  						},
   543  					},
   544  				},
   545  			},
   546  		}
   547  
   548  		message, err := CommitMessage(ctx, change)
   549  		So(err, ShouldBeNil)
   550  		So(message, ShouldEqual, expectedMessage)
   551  	})
   552  }