go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/rpc/v0/get_run_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 rpc
    16  
    17  import (
    18  	"testing"
    19  	"time"
    20  
    21  	"google.golang.org/grpc/codes"
    22  	"google.golang.org/protobuf/types/known/timestamppb"
    23  
    24  	bbpb "go.chromium.org/luci/buildbucket/proto"
    25  	"go.chromium.org/luci/buildbucket/protoutil"
    26  	"go.chromium.org/luci/common/clock/testclock"
    27  	"go.chromium.org/luci/gae/service/datastore"
    28  	"go.chromium.org/luci/grpc/grpcutil"
    29  	"go.chromium.org/luci/server/auth"
    30  	"go.chromium.org/luci/server/auth/authtest"
    31  
    32  	cfgpb "go.chromium.org/luci/cv/api/config/v2"
    33  	apiv0pb "go.chromium.org/luci/cv/api/v0"
    34  	"go.chromium.org/luci/cv/internal/acls"
    35  	"go.chromium.org/luci/cv/internal/changelist"
    36  	"go.chromium.org/luci/cv/internal/common"
    37  	"go.chromium.org/luci/cv/internal/configs/prjcfg/prjcfgtest"
    38  	"go.chromium.org/luci/cv/internal/cvtesting"
    39  	"go.chromium.org/luci/cv/internal/run"
    40  	"go.chromium.org/luci/cv/internal/tryjob"
    41  
    42  	. "github.com/smartystreets/goconvey/convey"
    43  	. "go.chromium.org/luci/common/testing/assertions"
    44  )
    45  
    46  func TestGetRun(t *testing.T) {
    47  	t.Parallel()
    48  
    49  	Convey("GetRun", t, func() {
    50  		ct := cvtesting.Test{}
    51  		ctx, cancel := ct.SetUp(t)
    52  		defer cancel()
    53  
    54  		rs := RunsServer{}
    55  
    56  		const lProject = "infra"
    57  		rid := common.MakeRunID(lProject, ct.Clock.Now(), 1, []byte("deadbeef"))
    58  		builderFoo := &bbpb.BuilderID{
    59  			Project: lProject,
    60  			Bucket:  "try",
    61  			Builder: "foo",
    62  		}
    63  		prjcfgtest.Create(ctx, lProject, &cfgpb.Config{
    64  			// TODO(crbug/1233963): remove once non-legacy ACLs are implemented.
    65  			CqStatusHost: "chromium-cq-status.appspot.com",
    66  			ConfigGroups: []*cfgpb.ConfigGroup{{
    67  				Name: "first",
    68  				Verifiers: &cfgpb.Verifiers{
    69  					Tryjob: &cfgpb.Verifiers_Tryjob{
    70  						Builders: []*cfgpb.Verifiers_Tryjob_Builder{
    71  							{
    72  								Name: protoutil.FormatBuilderID(builderFoo),
    73  							},
    74  						},
    75  					},
    76  				},
    77  			}},
    78  		})
    79  
    80  		configGroupID := prjcfgtest.MustExist(ctx, lProject).ConfigGroupIDs[0]
    81  
    82  		ctx = auth.WithState(ctx, &authtest.FakeState{
    83  			Identity:       "user:admin@example.com",
    84  			IdentityGroups: []string{acls.V0APIAllowGroup},
    85  		})
    86  
    87  		saveAndGet := func(r *run.Run) *apiv0pb.Run {
    88  			So(datastore.Put(ctx, r), ShouldBeNil)
    89  			resp, err := rs.GetRun(ctx, &apiv0pb.GetRunRequest{Id: rid.PublicID()})
    90  			So(grpcutil.Code(err), ShouldEqual, codes.OK)
    91  			return resp
    92  		}
    93  
    94  		Convey("w/o access", func() {
    95  			ctx = auth.WithState(ctx, &authtest.FakeState{
    96  				Identity: "anonymous:anonymous",
    97  			})
    98  			_, err := rs.GetRun(ctx, &apiv0pb.GetRunRequest{Id: rid.PublicID()})
    99  			So(grpcutil.Code(err), ShouldEqual, codes.PermissionDenied)
   100  		})
   101  
   102  		Convey("w/ an invalid public ID", func() {
   103  			_, err := rs.GetRun(ctx, &apiv0pb.GetRunRequest{Id: "something valid"})
   104  			So(grpcutil.Code(err), ShouldEqual, codes.InvalidArgument)
   105  		})
   106  
   107  		Convey("w/ an non-existing Run ID", func() {
   108  			_, err := rs.GetRun(ctx, &apiv0pb.GetRunRequest{Id: rid.PublicID()})
   109  			So(grpcutil.Code(err), ShouldEqual, codes.NotFound)
   110  		})
   111  
   112  		Convey("w/ an existing RunID", func() {
   113  			const gHost = "r-review.example.com"
   114  			cl1 := changelist.MustGobID(gHost, 1).MustCreateIfNotExists(ctx)
   115  			cl2 := changelist.MustGobID(gHost, 2).MustCreateIfNotExists(ctx)
   116  			cl3 := changelist.MustGobID(gHost, 3).MustCreateIfNotExists(ctx)
   117  			epoch := testclock.TestRecentTimeUTC.Truncate(time.Millisecond)
   118  			r := &run.Run{
   119  				ID:            rid,
   120  				Status:        run.Status_SUCCEEDED,
   121  				ConfigGroupID: configGroupID,
   122  				CreateTime:    epoch,
   123  				StartTime:     epoch.Add(time.Second),
   124  				UpdateTime:    epoch.Add(time.Minute),
   125  				EndTime:       epoch.Add(time.Hour),
   126  				Owner:         "user:foo@example.org",
   127  				CreatedBy:     "user:bar@example.org",
   128  				BilledTo:      "user:bar@example.org",
   129  				CLs:           common.MakeCLIDs(int64(cl1.ID), int64(cl2.ID), int64(cl3.ID)),
   130  			}
   131  			So(datastore.Put(
   132  				ctx,
   133  				&run.RunCL{
   134  					Run: datastore.MakeKey(ctx, common.RunKind, string(rid)),
   135  					ID:  cl1.ID, IndexedID: cl1.ID,
   136  					ExternalID: cl1.ExternalID,
   137  					Detail: &changelist.Snapshot{
   138  						Patchset: 39,
   139  					},
   140  				},
   141  				&run.RunCL{
   142  					Run: datastore.MakeKey(ctx, common.RunKind, string(rid)),
   143  					ID:  cl2.ID, IndexedID: cl2.ID,
   144  					ExternalID: cl2.ExternalID,
   145  					Detail: &changelist.Snapshot{
   146  						Patchset: 40,
   147  					},
   148  				},
   149  				&run.RunCL{
   150  					Run: datastore.MakeKey(ctx, common.RunKind, string(rid)),
   151  					ID:  cl3.ID, IndexedID: cl3.ID,
   152  					ExternalID: cl3.ExternalID,
   153  					Detail: &changelist.Snapshot{
   154  						Patchset: 41,
   155  					},
   156  				},
   157  			), ShouldBeNil)
   158  
   159  			So(saveAndGet(r), ShouldResembleProto, &apiv0pb.Run{
   160  				Id:         rid.PublicID(),
   161  				Status:     apiv0pb.Run_SUCCEEDED,
   162  				CreateTime: timestamppb.New(epoch),
   163  				StartTime:  timestamppb.New(epoch.Add(time.Second)),
   164  				UpdateTime: timestamppb.New(epoch.Add(time.Minute)),
   165  				EndTime:    timestamppb.New(epoch.Add(time.Hour)),
   166  				Owner:      "user:foo@example.org",
   167  				CreatedBy:  "user:bar@example.org",
   168  				BilledTo:   "user:bar@example.org",
   169  				Cls: []*apiv0pb.GerritChange{
   170  					{Host: gHost, Change: int64(cl1.ID), Patchset: 39},
   171  					{Host: gHost, Change: int64(cl2.ID), Patchset: 40},
   172  					{Host: gHost, Change: int64(cl3.ID), Patchset: 41},
   173  				},
   174  			})
   175  
   176  			Convey("w/ tryjobs", func() {
   177  				r.Tryjobs = &run.Tryjobs{
   178  					State: &tryjob.ExecutionState{
   179  						Requirement: &tryjob.Requirement{
   180  							Definitions: []*tryjob.Definition{
   181  								{
   182  									Backend: &tryjob.Definition_Buildbucket_{
   183  										Buildbucket: &tryjob.Definition_Buildbucket{
   184  											Host:    "bb.example.com",
   185  											Builder: builderFoo,
   186  										},
   187  									},
   188  									Critical: true,
   189  								},
   190  							},
   191  						},
   192  						Executions: []*tryjob.ExecutionState_Execution{
   193  							{
   194  								Attempts: []*tryjob.ExecutionState_Execution_Attempt{
   195  									{
   196  										TryjobId:   1,
   197  										ExternalId: string(tryjob.MustBuildbucketID("bb.example.com", 11)),
   198  										Status:     tryjob.Status_ENDED,
   199  										Result: &tryjob.Result{
   200  											Status: tryjob.Result_SUCCEEDED,
   201  											Backend: &tryjob.Result_Buildbucket_{
   202  												Buildbucket: &tryjob.Result_Buildbucket{
   203  													Id:      11,
   204  													Status:  bbpb.Status_SUCCESS,
   205  													Builder: builderFoo,
   206  												},
   207  											},
   208  										},
   209  										Reused: true,
   210  									},
   211  								},
   212  							},
   213  						},
   214  					},
   215  				}
   216  				So(saveAndGet(r).TryjobInvocations, ShouldResembleProto, []*apiv0pb.TryjobInvocation{
   217  					{
   218  						BuilderConfig: &cfgpb.Verifiers_Tryjob_Builder{
   219  							Name: protoutil.FormatBuilderID(builderFoo),
   220  						},
   221  						Status:   apiv0pb.TryjobStatus_SUCCEEDED,
   222  						Critical: true,
   223  						Attempts: []*apiv0pb.TryjobInvocation_Attempt{
   224  							{
   225  								Status: apiv0pb.TryjobStatus_SUCCEEDED,
   226  								Result: &apiv0pb.TryjobResult{
   227  									Backend: &apiv0pb.TryjobResult_Buildbucket_{
   228  										Buildbucket: &apiv0pb.TryjobResult_Buildbucket{
   229  											Host:    "bb.example.com",
   230  											Id:      11,
   231  											Builder: builderFoo,
   232  										},
   233  									},
   234  								},
   235  								Reuse: true,
   236  							},
   237  						},
   238  					},
   239  				})
   240  			})
   241  
   242  			Convey("w/ submission", func() {
   243  				r.Submission = &run.Submission{
   244  					SubmittedCls: []int64{int64(cl1.ID)},
   245  					FailedCls:    []int64{int64(cl3.ID)},
   246  				}
   247  				So(saveAndGet(r).Submission, ShouldResembleProto, &apiv0pb.Run_Submission{
   248  					SubmittedClIndexes: []int32{0},
   249  					FailedClIndexes:    []int32{2},
   250  				})
   251  			})
   252  		})
   253  	})
   254  }