blob: 9db79c414d80dd33e39fab58a2b81ea06fc725bc [file] [log] [blame]
// Copyright 2022 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpc
import (
"testing"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/timestamppb"
bbpb "go.chromium.org/luci/buildbucket/proto"
"go.chromium.org/luci/common/clock/testclock"
"go.chromium.org/luci/gae/service/datastore"
"go.chromium.org/luci/grpc/grpcutil"
"go.chromium.org/luci/server/auth"
"go.chromium.org/luci/server/auth/authtest"
cfgpb "go.chromium.org/luci/cv/api/config/v2"
apiv0pb "go.chromium.org/luci/cv/api/v0"
"go.chromium.org/luci/cv/internal/acls"
"go.chromium.org/luci/cv/internal/changelist"
"go.chromium.org/luci/cv/internal/common"
"go.chromium.org/luci/cv/internal/configs/prjcfg/prjcfgtest"
"go.chromium.org/luci/cv/internal/cvtesting"
"go.chromium.org/luci/cv/internal/run"
"go.chromium.org/luci/cv/internal/tryjob"
. "github.com/smartystreets/goconvey/convey"
. "go.chromium.org/luci/common/testing/assertions"
)
func TestGetRun(t *testing.T) {
t.Parallel()
Convey("GetRun", t, func() {
ct := cvtesting.Test{}
ctx, cancel := ct.SetUp()
defer cancel()
rs := RunsServer{}
rid := common.RunID("prj/123-deadbeef")
prjcfgtest.Create(ctx, "prj", &cfgpb.Config{
// TODO(crbug/1233963): remove once non-legacy ACLs are implemented.
CqStatusHost: "chromium-cq-status.appspot.com",
ConfigGroups: []*cfgpb.ConfigGroup{{
Name: "first",
}},
})
ctx = auth.WithState(ctx, &authtest.FakeState{
Identity: "user:admin@example.com",
IdentityGroups: []string{acls.V0APIAllowGroup},
})
saveAndGet := func(r *run.Run) *apiv0pb.Run {
So(datastore.Put(ctx, r), ShouldBeNil)
resp, err := rs.GetRun(ctx, &apiv0pb.GetRunRequest{Id: rid.PublicID()})
So(grpcutil.Code(err), ShouldEqual, codes.OK)
return resp
}
Convey("w/o access", func() {
ctx = auth.WithState(ctx, &authtest.FakeState{
Identity: "anonymous:anonymous",
})
_, err := rs.GetRun(ctx, &apiv0pb.GetRunRequest{Id: rid.PublicID()})
So(grpcutil.Code(err), ShouldEqual, codes.PermissionDenied)
})
Convey("w/ an invalid public ID", func() {
_, err := rs.GetRun(ctx, &apiv0pb.GetRunRequest{Id: "something valid"})
So(grpcutil.Code(err), ShouldEqual, codes.InvalidArgument)
})
Convey("w/ an non-existing Run ID", func() {
_, err := rs.GetRun(ctx, &apiv0pb.GetRunRequest{Id: rid.PublicID()})
So(grpcutil.Code(err), ShouldEqual, codes.NotFound)
})
Convey("w/ an existing RunID", func() {
const gHost = "r-review.example.com"
cl1 := changelist.MustGobID(gHost, 1).MustCreateIfNotExists(ctx)
cl2 := changelist.MustGobID(gHost, 2).MustCreateIfNotExists(ctx)
cl3 := changelist.MustGobID(gHost, 3).MustCreateIfNotExists(ctx)
epoch := testclock.TestRecentTimeUTC.Truncate(time.Millisecond)
r := &run.Run{
ID: rid,
Status: run.Status_SUCCEEDED,
CreateTime: epoch,
StartTime: epoch.Add(time.Second),
UpdateTime: epoch.Add(time.Minute),
EndTime: epoch.Add(time.Hour),
Owner: "user:foo@example.org",
CLs: common.MakeCLIDs(int64(cl1.ID), int64(cl2.ID), int64(cl3.ID)),
}
So(datastore.Put(
ctx,
&run.RunCL{
Run: datastore.MakeKey(ctx, common.RunKind, string(rid)),
ID: cl1.ID, IndexedID: cl1.ID,
ExternalID: cl1.ExternalID,
Detail: &changelist.Snapshot{
Patchset: 39,
},
},
&run.RunCL{
Run: datastore.MakeKey(ctx, common.RunKind, string(rid)),
ID: cl2.ID, IndexedID: cl2.ID,
ExternalID: cl2.ExternalID,
Detail: &changelist.Snapshot{
Patchset: 40,
},
},
&run.RunCL{
Run: datastore.MakeKey(ctx, common.RunKind, string(rid)),
ID: cl3.ID, IndexedID: cl3.ID,
ExternalID: cl3.ExternalID,
Detail: &changelist.Snapshot{
Patchset: 41,
},
},
), ShouldBeNil)
So(saveAndGet(r), ShouldResembleProto, &apiv0pb.Run{
Id: rid.PublicID(),
Status: apiv0pb.Run_SUCCEEDED,
CreateTime: timestamppb.New(epoch),
StartTime: timestamppb.New(epoch.Add(time.Second)),
UpdateTime: timestamppb.New(epoch.Add(time.Minute)),
EndTime: timestamppb.New(epoch.Add(time.Hour)),
Owner: "user:foo@example.org",
Cls: []*apiv0pb.GerritChange{
{Host: gHost, Change: int64(cl1.ID), Patchset: 39},
{Host: gHost, Change: int64(cl2.ID), Patchset: 40},
{Host: gHost, Change: int64(cl3.ID), Patchset: 41},
},
})
Convey("w/ tryjobs", func() {
def := &tryjob.Definition{
Backend: &tryjob.Definition_Buildbucket_{
Buildbucket: &tryjob.Definition_Buildbucket{
Host: "bb",
Builder: &bbpb.BuilderID{
Project: "prj",
Bucket: "ci",
Builder: "foo",
},
},
},
}
r.Tryjobs = &run.Tryjobs{Tryjobs: []*run.Tryjob{
// first tryjob was cancelled.
{
Id: 1,
ExternalId: string(tryjob.MustBuildbucketID("bb-host", 11)),
Definition: def,
Status: tryjob.Status_CANCELLED,
Result: &tryjob.Result{
Status: tryjob.Result_FAILED_TRANSIENTLY,
Backend: &tryjob.Result_Buildbucket_{
Buildbucket: &tryjob.Result_Buildbucket{
Id: 11,
Status: bbpb.Status_CANCELED,
},
},
},
Critical: true,
Reused: false,
},
// but the next one was succeeded.
{
Id: 2,
ExternalId: string(tryjob.MustBuildbucketID("bb-host", 12)),
Definition: def,
Status: tryjob.Status_ENDED,
Result: &tryjob.Result{
Status: tryjob.Result_SUCCEEDED,
Backend: &tryjob.Result_Buildbucket_{
Buildbucket: &tryjob.Result_Buildbucket{
Id: 12,
Status: bbpb.Status_SUCCESS,
},
},
},
Critical: false,
Reused: true,
},
}}
So(saveAndGet(r).Tryjobs, ShouldResembleProto, []*apiv0pb.Tryjob{
{
Status: apiv0pb.Tryjob_CANCELLED,
Result: &apiv0pb.Tryjob_Result{
Status: apiv0pb.Tryjob_Result_FAILED_TRANSIENTLY,
Backend: &apiv0pb.Tryjob_Result_Buildbucket_{
Buildbucket: &apiv0pb.Tryjob_Result_Buildbucket{Id: 11},
},
},
Critical: true,
Reuse: false,
},
{
Status: apiv0pb.Tryjob_ENDED,
Result: &apiv0pb.Tryjob_Result{
Status: apiv0pb.Tryjob_Result_SUCCEEDED,
Backend: &apiv0pb.Tryjob_Result_Buildbucket_{
Buildbucket: &apiv0pb.Tryjob_Result_Buildbucket{Id: 12},
},
},
Critical: false,
Reuse: true,
},
})
})
Convey("w/ submission", func() {
r.Submission = &run.Submission{
SubmittedCls: []int64{int64(cl1.ID)},
FailedCls: []int64{int64(cl3.ID)},
}
So(saveAndGet(r).Submission, ShouldResembleProto, &apiv0pb.Run_Submission{
SubmittedClIndexes: []int32{0},
FailedClIndexes: []int32{2},
})
})
})
})
}