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

     1  // Copyright 2023 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  	"testing"
    20  
    21  	. "github.com/smartystreets/goconvey/convey"
    22  	"go.chromium.org/luci/auth/identity"
    23  	buildbucketpb "go.chromium.org/luci/buildbucket/proto"
    24  	. "go.chromium.org/luci/common/testing/assertions"
    25  	"go.chromium.org/luci/gae/impl/memory"
    26  	"go.chromium.org/luci/gae/service/datastore"
    27  	"go.chromium.org/luci/grpc/grpcutil"
    28  	"go.chromium.org/luci/milo/internal/projectconfig"
    29  	"go.chromium.org/luci/milo/internal/testutils"
    30  	"go.chromium.org/luci/milo/internal/utils"
    31  	projectconfigpb "go.chromium.org/luci/milo/proto/projectconfig"
    32  	milopb "go.chromium.org/luci/milo/proto/v1"
    33  	"go.chromium.org/luci/server/auth"
    34  	"go.chromium.org/luci/server/auth/authtest"
    35  	"go.chromium.org/luci/server/secrets"
    36  	"google.golang.org/grpc/codes"
    37  )
    38  
    39  func TestQueryConsoles(t *testing.T) {
    40  	t.Parallel()
    41  	Convey(`TestQueryConsoles`, t, func() {
    42  		ctx := memory.Use(context.Background())
    43  		ctx = testutils.SetUpTestGlobalCache(ctx)
    44  		ctx = secrets.GeneratePrimaryTinkAEADForTest(ctx)
    45  
    46  		datastore.GetTestable(ctx).AutoIndex(true)
    47  		datastore.GetTestable(ctx).Consistent(true)
    48  
    49  		err := datastore.Put(ctx, []*projectconfig.Project{
    50  			{
    51  				ID:  "allowed-project",
    52  				ACL: projectconfig.ACL{Identities: []identity.Identity{"user"}},
    53  			},
    54  			{
    55  				ID:  "other-allowed-project",
    56  				ACL: projectconfig.ACL{Identities: []identity.Identity{"user"}},
    57  			},
    58  		})
    59  		So(err, ShouldBeNil)
    60  
    61  		consoleIDs := []*projectconfig.ConsoleID{
    62  			{
    63  
    64  				Project: "allowed-project",
    65  				ID:      "con1",
    66  			},
    67  			{
    68  				Project: "allowed-project",
    69  				ID:      "con2",
    70  			},
    71  			{
    72  				Project: "allowed-project",
    73  				ID:      "con3",
    74  			},
    75  			{
    76  				Project: "other-allowed-project",
    77  				ID:      "con4",
    78  			},
    79  			{
    80  				Project: "forbidden-project",
    81  				ID:      "con5",
    82  			},
    83  		}
    84  		consoleBuilders := [][]*buildbucketpb.BuilderID{
    85  			{
    86  				{
    87  					Project: "allowed-project",
    88  					Bucket:  "bucket1",
    89  					Builder: "builder1",
    90  				},
    91  				{
    92  					Project: "allowed-project",
    93  					Bucket:  "bucket2",
    94  					Builder: "builder2",
    95  				},
    96  				{
    97  					Project: "allowed-project",
    98  					Bucket:  "bucket3",
    99  					Builder: "builder3",
   100  				},
   101  				{
   102  					Project: "forbidden-project",
   103  					Bucket:  "bucket",
   104  					Builder: "builder",
   105  				},
   106  			},
   107  			{
   108  				{
   109  					Project: "allowed-project",
   110  					Bucket:  "bucket1",
   111  					Builder: "builder1",
   112  				},
   113  				{
   114  					Project: "allowed-project",
   115  					Bucket:  "bucket2",
   116  					Builder: "builder2",
   117  				},
   118  			},
   119  			{
   120  				{
   121  					Project: "allowed-project",
   122  					Bucket:  "bucket2",
   123  					Builder: "builder2",
   124  				},
   125  				{
   126  					Project: "allowed-project",
   127  					Bucket:  "bucket3",
   128  					Builder: "builder3",
   129  				},
   130  			},
   131  			{
   132  				{
   133  					Project: "allowed-project",
   134  					Bucket:  "bucket1",
   135  					Builder: "builder1",
   136  				},
   137  				{
   138  					Project: "allowed-project",
   139  					Bucket:  "bucket3",
   140  					Builder: "builder3",
   141  				},
   142  			},
   143  			{
   144  				{
   145  					Project: "allowed-project",
   146  					Bucket:  "bucket1",
   147  					Builder: "builder1",
   148  				},
   149  				{
   150  					Project: "allowed-project",
   151  					Bucket:  "bucket3",
   152  					Builder: "builder3",
   153  				},
   154  			},
   155  		}
   156  
   157  		consoles := make([]*projectconfig.Console, 0, len(consoleIDs))
   158  		for i, conID := range consoleIDs {
   159  			console := conID.SetID(ctx, nil)
   160  			console.Builders = make([]string, 0, len(consoleBuilders[i]))
   161  			console.Def = projectconfigpb.Console{
   162  				Builders: make([]*projectconfigpb.Builder, 0, len(consoleBuilders[i])),
   163  			}
   164  			for _, builderID := range consoleBuilders[i] {
   165  				legacyID := utils.LegacyBuilderIDString(builderID)
   166  				console.Builders = append(console.Builders, legacyID)
   167  				console.Def.Builders = append(console.Def.Builders, &projectconfigpb.Builder{Name: legacyID})
   168  			}
   169  			consoles = append(consoles, console)
   170  		}
   171  
   172  		err = datastore.Put(ctx, consoles)
   173  		So(err, ShouldBeNil)
   174  
   175  		srv := &MiloInternalService{}
   176  
   177  		Convey(`e2e`, func() {
   178  			ctx := auth.WithState(ctx, &authtest.FakeState{Identity: "user"})
   179  
   180  			res, err := srv.QueryConsoles(ctx, &milopb.QueryConsolesRequest{
   181  				Predicate: &milopb.ConsolePredicate{
   182  					Builder: &buildbucketpb.BuilderID{
   183  						Project: "allowed-project",
   184  						Bucket:  "bucket1",
   185  						Builder: "builder1",
   186  					},
   187  				},
   188  				PageSize: 2,
   189  			})
   190  			So(err, ShouldBeNil)
   191  			So(res.Consoles, ShouldResembleProto, []*projectconfigpb.Console{
   192  				{
   193  					Id:    "con1",
   194  					Realm: "allowed-project:@root",
   195  				},
   196  				{
   197  					Id:    "con2",
   198  					Realm: "allowed-project:@root",
   199  				},
   200  			})
   201  			So(res.NextPageToken, ShouldNotBeEmpty)
   202  
   203  			res, err = srv.QueryConsoles(ctx, &milopb.QueryConsolesRequest{
   204  				Predicate: &milopb.ConsolePredicate{
   205  					Builder: &buildbucketpb.BuilderID{
   206  						Project: "allowed-project",
   207  						Bucket:  "bucket1",
   208  						Builder: "builder1",
   209  					},
   210  				},
   211  				PageSize:  2,
   212  				PageToken: res.NextPageToken,
   213  			})
   214  			So(err, ShouldBeNil)
   215  			So(res.Consoles, ShouldResembleProto, []*projectconfigpb.Console{
   216  				{
   217  					Id:    "con4",
   218  					Realm: "other-allowed-project:@root",
   219  				},
   220  			})
   221  			So(res.NextPageToken, ShouldBeEmpty)
   222  		})
   223  
   224  		Convey(`query project`, func() {
   225  			ctx := auth.WithState(ctx, &authtest.FakeState{Identity: "user"})
   226  
   227  			res, err := srv.QueryConsoles(ctx, &milopb.QueryConsolesRequest{
   228  				Predicate: &milopb.ConsolePredicate{
   229  					Project: "allowed-project",
   230  				},
   231  			})
   232  			So(err, ShouldBeNil)
   233  			So(res.Consoles, ShouldResembleProto, []*projectconfigpb.Console{
   234  				{
   235  					Id:    "con1",
   236  					Realm: "allowed-project:@root",
   237  				},
   238  				{
   239  					Id:    "con2",
   240  					Realm: "allowed-project:@root",
   241  				},
   242  				{
   243  					Id:    "con3",
   244  					Realm: "allowed-project:@root",
   245  				},
   246  			})
   247  		})
   248  
   249  		Convey(`query project and builder`, func() {
   250  			ctx := auth.WithState(ctx, &authtest.FakeState{Identity: "user"})
   251  
   252  			res, err := srv.QueryConsoles(ctx, &milopb.QueryConsolesRequest{
   253  				Predicate: &milopb.ConsolePredicate{
   254  					Project: "allowed-project",
   255  					Builder: &buildbucketpb.BuilderID{
   256  						Project: "allowed-project",
   257  						Bucket:  "bucket1",
   258  						Builder: "builder1",
   259  					},
   260  				},
   261  			})
   262  			So(err, ShouldBeNil)
   263  			So(res.Consoles, ShouldResembleProto, []*projectconfigpb.Console{
   264  				{
   265  					Id:    "con1",
   266  					Realm: "allowed-project:@root",
   267  				},
   268  				{
   269  					Id:    "con2",
   270  					Realm: "allowed-project:@root",
   271  				},
   272  			})
   273  		})
   274  
   275  		Convey(`query all`, func() {
   276  			ctx := auth.WithState(ctx, &authtest.FakeState{Identity: "user"})
   277  
   278  			res, err := srv.QueryConsoles(ctx, &milopb.QueryConsolesRequest{})
   279  			So(err, ShouldBeNil)
   280  			So(res.Consoles, ShouldResembleProto, []*projectconfigpb.Console{
   281  				{
   282  					Id:    "con1",
   283  					Realm: "allowed-project:@root",
   284  				},
   285  				{
   286  					Id:    "con2",
   287  					Realm: "allowed-project:@root",
   288  				},
   289  				{
   290  					Id:    "con3",
   291  					Realm: "allowed-project:@root",
   292  				},
   293  				{
   294  					Id:    "con4",
   295  					Realm: "other-allowed-project:@root",
   296  				},
   297  			})
   298  		})
   299  
   300  		Convey(`query forbidden project`, func() {
   301  			ctx := auth.WithState(ctx, &authtest.FakeState{Identity: "user"})
   302  
   303  			res, err := srv.QueryConsoles(ctx, &milopb.QueryConsolesRequest{
   304  				Predicate: &milopb.ConsolePredicate{
   305  					Project: "forbidden-project",
   306  				},
   307  				PageSize: 2,
   308  			})
   309  			So(res, ShouldBeNil)
   310  			So(err, ShouldNotBeNil)
   311  			So(grpcutil.Code(err), ShouldEqual, codes.PermissionDenied)
   312  		})
   313  
   314  		Convey(`query forbidden project with builder predicate`, func() {
   315  			ctx := auth.WithState(ctx, &authtest.FakeState{Identity: "user"})
   316  
   317  			res, err := srv.QueryConsoles(ctx, &milopb.QueryConsolesRequest{
   318  				Predicate: &milopb.ConsolePredicate{
   319  					Builder: &buildbucketpb.BuilderID{
   320  						Project: "forbidden-project",
   321  						Bucket:  "bucket1",
   322  						Builder: "builder1",
   323  					},
   324  				},
   325  				PageSize: 2,
   326  			})
   327  			So(res, ShouldBeNil)
   328  			So(err, ShouldNotBeNil)
   329  			So(grpcutil.Code(err), ShouldEqual, codes.PermissionDenied)
   330  		})
   331  	})
   332  }
   333  
   334  func TestValidateQueryConsolesQuery(t *testing.T) {
   335  	t.Parallel()
   336  	Convey(`TestValidateQueryConsolesRequest`, t, func() {
   337  		Convey(`negative page size`, func() {
   338  			err := validatesQueryConsolesRequest(&milopb.QueryConsolesRequest{
   339  				Predicate: &milopb.ConsolePredicate{
   340  					Builder: &buildbucketpb.BuilderID{
   341  						Project: "project",
   342  						Bucket:  "bucket",
   343  						Builder: "builder",
   344  					},
   345  				},
   346  				PageSize: -1,
   347  			})
   348  			So(err, ShouldNotBeNil)
   349  			So(err, ShouldErrLike, "page_size can not be negative")
   350  		})
   351  
   352  		Convey(`valid`, func() {
   353  			err := validatesQueryConsolesRequest(&milopb.QueryConsolesRequest{
   354  				Predicate: &milopb.ConsolePredicate{
   355  					Project: "project",
   356  					Builder: &buildbucketpb.BuilderID{
   357  						Project: "project",
   358  						Bucket:  "bucket",
   359  						Builder: "builder",
   360  					},
   361  				},
   362  				PageSize: 10,
   363  			})
   364  			So(err, ShouldBeNil)
   365  		})
   366  
   367  		Convey(`valid with no predicate`, func() {
   368  			err := validatesQueryConsolesRequest(&milopb.QueryConsolesRequest{
   369  				PageSize: 10,
   370  			})
   371  			So(err, ShouldBeNil)
   372  		})
   373  
   374  		Convey(`valid with only project`, func() {
   375  			err := validatesQueryConsolesRequest(&milopb.QueryConsolesRequest{
   376  				Predicate: &milopb.ConsolePredicate{
   377  					Project: "project",
   378  				},
   379  			})
   380  			So(err, ShouldBeNil)
   381  		})
   382  
   383  		Convey(`valid with only builder`, func() {
   384  			err := validatesQueryConsolesRequest(&milopb.QueryConsolesRequest{
   385  				Predicate: &milopb.ConsolePredicate{
   386  					Builder: &buildbucketpb.BuilderID{
   387  						Project: "project",
   388  						Bucket:  "bucket",
   389  						Builder: "builder",
   390  					},
   391  				},
   392  			})
   393  			So(err, ShouldBeNil)
   394  		})
   395  	})
   396  }