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 }