go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/rpc/query_recent_builds_test.go (about)

     1  // Copyright 2021 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  	"context"
    19  	"fmt"
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/smartystreets/goconvey/convey"
    24  	"go.chromium.org/luci/buildbucket/bbperms"
    25  	buildbucketpb "go.chromium.org/luci/buildbucket/proto"
    26  	. "go.chromium.org/luci/common/testing/assertions"
    27  	"go.chromium.org/luci/gae/impl/memory"
    28  	"go.chromium.org/luci/gae/service/datastore"
    29  	"go.chromium.org/luci/milo/internal/model"
    30  	"go.chromium.org/luci/milo/internal/model/milostatus"
    31  	"go.chromium.org/luci/milo/internal/testutils"
    32  	"go.chromium.org/luci/milo/internal/utils"
    33  	milopb "go.chromium.org/luci/milo/proto/v1"
    34  	"go.chromium.org/luci/server/auth"
    35  	"go.chromium.org/luci/server/auth/authtest"
    36  	"go.chromium.org/luci/server/secrets"
    37  	"google.golang.org/protobuf/types/known/timestamppb"
    38  )
    39  
    40  func TestQueryRecentBuilds(t *testing.T) {
    41  	t.Parallel()
    42  	Convey(`TestQueryRecentBuilds`, t, func() {
    43  		ctx := memory.Use(context.Background())
    44  		ctx = testutils.SetUpTestGlobalCache(ctx)
    45  		ctx = secrets.GeneratePrimaryTinkAEADForTest(ctx)
    46  
    47  		datastore.GetTestable(ctx).AddIndexes(&datastore.IndexDefinition{
    48  			Kind: "BuildSummary",
    49  			SortBy: []datastore.IndexColumn{
    50  				{Property: "BuilderID"},
    51  				{Property: "Created", Descending: true},
    52  			},
    53  		})
    54  		datastore.GetTestable(ctx).Consistent(true)
    55  
    56  		srv := &MiloInternalService{}
    57  
    58  		builder1 := &buildbucketpb.BuilderID{
    59  			Project: "fake_project",
    60  			Bucket:  "fake_bucket",
    61  			Builder: "fake_builder1",
    62  		}
    63  		builder2 := &buildbucketpb.BuilderID{
    64  			Project: "fake_project",
    65  			Bucket:  "fake_bucket",
    66  			Builder: "fake_builder2",
    67  		}
    68  
    69  		createFakeBuild := func(builder *buildbucketpb.BuilderID, buildNum int, buildID int64, createdAt time.Time, status milostatus.Status) *model.BuildSummary {
    70  			builderID := utils.LegacyBuilderIDString(builder)
    71  			bsBuildID := fmt.Sprintf("%s/%d", builderID, buildNum)
    72  			if buildID != 0 {
    73  				bsBuildID = fmt.Sprintf("buildbucket/%d", buildID)
    74  			}
    75  			return &model.BuildSummary{
    76  				BuildKey:  datastore.MakeKey(ctx, "buildbucket.Build", bsBuildID),
    77  				ProjectID: builder.Project,
    78  				BuilderID: builderID,
    79  				BuildID:   bsBuildID,
    80  				Summary: model.Summary{
    81  					Status: status,
    82  				},
    83  				Created: createdAt,
    84  			}
    85  		}
    86  
    87  		baseTime := time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC)
    88  		builds := []*model.BuildSummary{
    89  			createFakeBuild(builder1, 1, 0, baseTime.AddDate(0, 0, -5), milostatus.Running),
    90  			createFakeBuild(builder1, 2, 0, baseTime.AddDate(0, 0, -4), milostatus.Success),
    91  			createFakeBuild(builder2, 1, 0, baseTime.AddDate(0, 0, -3), milostatus.Success),
    92  			createFakeBuild(builder1, 0, 999, baseTime.AddDate(0, 0, -2), milostatus.Failure),
    93  			createFakeBuild(builder1, 0, 998, baseTime.AddDate(0, 0, -1), milostatus.InfraFailure),
    94  		}
    95  
    96  		err := datastore.Put(ctx, builds)
    97  		So(err, ShouldBeNil)
    98  
    99  		Convey(`get all recent builds`, func() {
   100  			ctx := auth.WithState(ctx, &authtest.FakeState{
   101  				Identity: "user",
   102  				IdentityPermissions: []authtest.RealmPermission{
   103  					{
   104  						Realm:      "fake_project:fake_bucket",
   105  						Permission: bbperms.BuildsList,
   106  					},
   107  				},
   108  			})
   109  
   110  			res, err := srv.QueryRecentBuilds(ctx, &milopb.QueryRecentBuildsRequest{
   111  				Builder:  builder1,
   112  				PageSize: 2,
   113  			})
   114  			So(err, ShouldBeNil)
   115  			So(res.Builds, ShouldResemble, []*buildbucketpb.Build{
   116  				{
   117  					Builder:    builder1,
   118  					Id:         998,
   119  					Status:     buildbucketpb.Status_INFRA_FAILURE,
   120  					CreateTime: timestamppb.New(builds[4].Created),
   121  				},
   122  				{
   123  					Builder:    builder1,
   124  					Id:         999,
   125  					Status:     buildbucketpb.Status_FAILURE,
   126  					CreateTime: timestamppb.New(builds[3].Created),
   127  				},
   128  			})
   129  			So(res.NextPageToken, ShouldNotBeEmpty)
   130  
   131  			res, err = srv.QueryRecentBuilds(ctx, &milopb.QueryRecentBuildsRequest{
   132  				Builder:   builder1,
   133  				PageSize:  2,
   134  				PageToken: res.NextPageToken,
   135  			})
   136  			So(err, ShouldBeNil)
   137  			So(res.Builds, ShouldResemble, []*buildbucketpb.Build{
   138  				{
   139  					Builder:    builder1,
   140  					Number:     2,
   141  					Status:     buildbucketpb.Status_SUCCESS,
   142  					CreateTime: timestamppb.New(builds[1].Created),
   143  				},
   144  			})
   145  			So(res.NextPageToken, ShouldBeEmpty)
   146  		})
   147  
   148  		Convey(`reject users with no access`, func() {
   149  			ctx := auth.WithState(ctx, &authtest.FakeState{
   150  				Identity: "user",
   151  			})
   152  			_, err := srv.QueryRecentBuilds(ctx, &milopb.QueryRecentBuildsRequest{
   153  				Builder:  builder1,
   154  				PageSize: 2,
   155  			})
   156  			So(err, ShouldNotBeNil)
   157  		})
   158  	})
   159  }
   160  
   161  func TestValidatesQueryRecentBuildsRequest(t *testing.T) {
   162  	t.Parallel()
   163  	Convey("validatesQueryRecentBuildsRequest", t, func() {
   164  		Convey("negative page size builder", func() {
   165  			err := validatesQueryRecentBuildsRequest(&milopb.QueryRecentBuildsRequest{
   166  				Builder: &buildbucketpb.BuilderID{
   167  					Project: "fake_project",
   168  					Bucket:  "fake_bucket",
   169  					Builder: "fake_builder1",
   170  				},
   171  				PageSize: -10,
   172  			})
   173  			So(err, ShouldErrLike, "page_size can not be negative")
   174  		})
   175  
   176  		Convey("no builder", func() {
   177  			err := validatesQueryRecentBuildsRequest(&milopb.QueryRecentBuildsRequest{})
   178  			So(err, ShouldErrLike, "builder: project must match")
   179  		})
   180  
   181  		Convey("invalid builder", func() {
   182  			err := validatesQueryRecentBuildsRequest(&milopb.QueryRecentBuildsRequest{
   183  				Builder: &buildbucketpb.BuilderID{
   184  					Project: "fake_proj",
   185  					Bucket:  "fake[]ucket",
   186  					Builder: "fake_/uilder1",
   187  				},
   188  			})
   189  			So(err, ShouldErrLike, "builder: bucket must match")
   190  		})
   191  
   192  		Convey("valid", func() {
   193  			err := validatesQueryRecentBuildsRequest(&milopb.QueryRecentBuildsRequest{
   194  				Builder: &buildbucketpb.BuilderID{
   195  					Project: "fake_project",
   196  					Bucket:  "fake_bucket",
   197  					Builder: "fake_builder1",
   198  				},
   199  			})
   200  			So(err, ShouldBeNil)
   201  		})
   202  	})
   203  }