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 }