go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/quota/manager_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 quota 16 17 import ( 18 "crypto/md5" 19 "encoding/hex" 20 "fmt" 21 "testing" 22 "time" 23 24 "github.com/alicebob/miniredis/v2" 25 "github.com/gomodule/redigo/redis" 26 27 "go.chromium.org/luci/auth/identity" 28 "go.chromium.org/luci/common/clock" 29 "go.chromium.org/luci/server/auth" 30 "go.chromium.org/luci/server/auth/authtest" 31 "go.chromium.org/luci/server/quota" 32 "go.chromium.org/luci/server/quota/quotapb" 33 _ "go.chromium.org/luci/server/quota/quotatestmonkeypatch" 34 "go.chromium.org/luci/server/redisconn" 35 36 cfgpb "go.chromium.org/luci/cv/api/config/v2" 37 "go.chromium.org/luci/cv/internal/common" 38 "go.chromium.org/luci/cv/internal/configs/prjcfg" 39 "go.chromium.org/luci/cv/internal/configs/prjcfg/prjcfgtest" 40 "go.chromium.org/luci/cv/internal/cvtesting" 41 "go.chromium.org/luci/cv/internal/metrics" 42 "go.chromium.org/luci/cv/internal/run" 43 44 . "github.com/smartystreets/goconvey/convey" 45 . "go.chromium.org/luci/common/testing/assertions" 46 ) 47 48 func TestManager(t *testing.T) { 49 t.Parallel() 50 51 Convey("Manager", t, func() { 52 ct := cvtesting.Test{} 53 ctx, cancel := ct.SetUp(t) 54 defer cancel() 55 56 s, err := miniredis.Run() 57 So(err, ShouldBeNil) 58 defer s.Close() 59 s.SetTime(clock.Now(ctx)) 60 61 ctx = redisconn.UsePool(ctx, &redis.Pool{ 62 Dial: func() (redis.Conn, error) { 63 return redis.Dial("tcp", s.Addr()) 64 }, 65 }) 66 67 makeIdentity := func(email string) identity.Identity { 68 id, err := identity.MakeIdentity(fmt.Sprintf("%s:%s", identity.User, email)) 69 So(err, ShouldBeNil) 70 return id 71 } 72 73 const tEmail = "t@example.org" 74 ct.AddMember(tEmail, "googlers") 75 ct.AddMember(tEmail, "partners") 76 77 const lProject = "chromium" 78 cg := &cfgpb.ConfigGroup{Name: "infra"} 79 cfg := &cfgpb.Config{ConfigGroups: []*cfgpb.ConfigGroup{cg}} 80 prjcfgtest.Create(ctx, lProject, cfg) 81 82 genUserLimit := func(name string, limit int64, principals []string) *cfgpb.UserLimit { 83 userLimit := &cfgpb.UserLimit{ 84 Name: name, 85 Principals: principals, 86 Run: &cfgpb.UserLimit_Run{ 87 MaxActive: &cfgpb.UserLimit_Limit{}, 88 }, 89 } 90 91 if limit == 0 { 92 userLimit.Run.MaxActive.Limit = &cfgpb.UserLimit_Limit_Unlimited{ 93 Unlimited: true, 94 } 95 } else { 96 userLimit.Run.MaxActive.Limit = &cfgpb.UserLimit_Limit_Value{ 97 Value: limit, 98 } 99 } 100 101 return userLimit 102 } 103 104 qm := NewManager() 105 106 Convey("WritePolicy() with config groups but no run limit", func() { 107 pid, err := qm.WritePolicy(ctx, lProject) 108 So(err, ShouldBeNil) 109 So(pid, ShouldBeNil) 110 }) 111 112 Convey("WritePolicy() with run limit values", func() { 113 cg.UserLimits = append(cg.UserLimits, 114 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 115 genUserLimit("googlers-limit", 5, []string{"group:googlers"}), 116 genUserLimit("partners-limit", 10, []string{"group:partners"}), 117 ) 118 prjcfgtest.Update(ctx, lProject, cfg) 119 pid, err := qm.WritePolicy(ctx, lProject) 120 121 So(err, ShouldBeNil) 122 So(pid, ShouldResembleProto, "apb.PolicyConfigID{ 123 AppId: "cv", 124 Realm: "chromium", 125 Version: pid.Version, 126 }) 127 }) 128 129 Convey("WritePolicy() with run limit defaults", func() { 130 cg.UserLimits = append(cg.UserLimits, 131 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 132 ) 133 cg.UserLimitDefault = genUserLimit("chromies-limit", 5, []string{"group:chromies", "group:chrome-infra"}) 134 prjcfgtest.Update(ctx, lProject, cfg) 135 pid, err := qm.WritePolicy(ctx, lProject) 136 137 So(err, ShouldBeNil) 138 So(pid, ShouldResembleProto, "apb.PolicyConfigID{ 139 AppId: "cv", 140 Realm: "chromium", 141 Version: pid.Version, 142 }) 143 }) 144 145 Convey("WritePolicy() with run limit set to unlimited", func() { 146 cg.UserLimits = append(cg.UserLimits, 147 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 148 genUserLimit("googlers-limit", 0, []string{"group:googlers"}), 149 ) 150 prjcfgtest.Update(ctx, lProject, cfg) 151 pid, err := qm.WritePolicy(ctx, lProject) 152 153 So(err, ShouldBeNil) 154 So(pid, ShouldResembleProto, "apb.PolicyConfigID{ 155 AppId: "cv", 156 Realm: "chromium", 157 Version: pid.Version, 158 }) 159 }) 160 161 Convey("findRunLimit() returns first valid user_limit", func() { 162 googlerLimit := genUserLimit("googlers-limit", 5, []string{"group:googlers"}) 163 cg.UserLimits = append(cg.UserLimits, 164 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 165 googlerLimit, 166 genUserLimit("partners-limit", 10, []string{"group:partners"}), 167 ) 168 prjcfgtest.Update(ctx, lProject, cfg) 169 170 r := &run.Run{ 171 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 172 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 173 BilledTo: makeIdentity(tEmail), 174 } 175 176 res, err := findRunLimit(ctx, r) 177 So(err, ShouldBeNil) 178 So(res, ShouldResembleProto, googlerLimit) 179 }) 180 181 Convey("findRunLimit() works with user entry in principals", func() { 182 exampleLimit := genUserLimit("example-limit", 10, []string{"group:chromies", "user:t@example.org"}) 183 cg.UserLimits = append(cg.UserLimits, 184 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 185 exampleLimit, 186 genUserLimit("googlers-limit", 5, []string{"group:googlers"}), 187 genUserLimit("partners-limit", 10, []string{"group:partners"}), 188 ) 189 prjcfgtest.Update(ctx, lProject, cfg) 190 191 r := &run.Run{ 192 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 193 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 194 BilledTo: makeIdentity(tEmail), 195 } 196 res, err := findRunLimit(ctx, r) 197 So(err, ShouldBeNil) 198 So(res, ShouldResembleProto, exampleLimit) 199 }) 200 201 Convey("findRunLimit() returns default user_limit if no valid user_limit is found", func() { 202 cg.UserLimits = append(cg.UserLimits, 203 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 204 genUserLimit("googlers-limit", 5, []string{"group:invalid"}), 205 genUserLimit("partners-limit", 10, []string{"group:invalid"}), 206 ) 207 cg.UserLimitDefault = genUserLimit("", 5, nil) 208 prjcfgtest.Update(ctx, lProject, cfg) 209 210 r := &run.Run{ 211 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 212 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 213 BilledTo: makeIdentity(tEmail), 214 } 215 216 res, err := findRunLimit(ctx, r) 217 So(err, ShouldBeNil) 218 So(res, ShouldResembleProto, genUserLimit("default", 5, nil)) // default name is overriden. 219 }) 220 221 Convey("findRunLimit() returns nil when no valid policy is found", func() { 222 cg.UserLimits = append(cg.UserLimits, 223 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 224 genUserLimit("googlers-limit", 5, []string{"group:invalid"}), 225 genUserLimit("partners-limit", 10, []string{"group:invalid"}), 226 ) 227 prjcfgtest.Update(ctx, lProject, cfg) 228 229 r := &run.Run{ 230 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 231 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 232 BilledTo: makeIdentity(tEmail), 233 } 234 235 res, err := findRunLimit(ctx, r) 236 So(err, ShouldBeNil) 237 So(res, ShouldBeNil) 238 }) 239 240 Convey("DebitRunQuota() debits quota for a given run state", func() { 241 googlerLimit := genUserLimit("googlers-limit", 5, []string{"group:googlers"}) 242 cg.UserLimits = append(cg.UserLimits, 243 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 244 googlerLimit, 245 genUserLimit("partners-limit", 10, []string{"group:partners"}), 246 ) 247 prjcfgtest.Update(ctx, lProject, cfg) 248 249 r := &run.Run{ 250 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 251 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 252 BilledTo: makeIdentity(tEmail), 253 } 254 255 res, userLimit, err := qm.DebitRunQuota(ctx, r) 256 So(err, ShouldBeNil) 257 So(userLimit, ShouldResembleProto, googlerLimit) 258 So(res, ShouldResembleProto, "apb.OpResult{ 259 NewBalance: 4, 260 AccountStatus: quotapb.OpResult_CREATED, 261 }) 262 So(ct.TSMonSentValue( 263 ctx, 264 metrics.Internal.QuotaOp, 265 lProject, 266 "infra", 267 "googlers-limit", 268 "runs", 269 "debit", 270 "SUCCESS", 271 ), ShouldEqual, 1) 272 }) 273 274 Convey("CreditRunQuota() credits quota for a given run state", func() { 275 googlerLimit := genUserLimit("googlers-limit", 5, []string{"group:googlers"}) 276 cg.UserLimits = append(cg.UserLimits, 277 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 278 googlerLimit, 279 genUserLimit("partners-limit", 10, []string{"group:partners"}), 280 ) 281 prjcfgtest.Update(ctx, lProject, cfg) 282 283 r := &run.Run{ 284 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 285 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 286 BilledTo: makeIdentity(tEmail), 287 } 288 289 res, userLimit, err := qm.CreditRunQuota(ctx, r) 290 So(err, ShouldBeNil) 291 So(userLimit, ShouldResembleProto, googlerLimit) 292 So(res, ShouldResembleProto, "apb.OpResult{ 293 NewBalance: 5, 294 AccountStatus: quotapb.OpResult_CREATED, 295 }) 296 So(ct.TSMonSentValue( 297 ctx, 298 metrics.Internal.QuotaOp, 299 lProject, 300 "infra", 301 "googlers-limit", 302 "runs", 303 "credit", 304 "SUCCESS", 305 ), ShouldEqual, 1) 306 }) 307 308 Convey("runQuotaOp() updates the same account on multiple ops", func() { 309 googlerLimit := genUserLimit("googlers-limit", 5, []string{"group:googlers"}) 310 cg.UserLimits = append(cg.UserLimits, 311 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 312 googlerLimit, 313 genUserLimit("partners-limit", 10, []string{"group:partners"}), 314 ) 315 prjcfgtest.Update(ctx, lProject, cfg) 316 317 r := &run.Run{ 318 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 319 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 320 BilledTo: makeIdentity(tEmail), 321 } 322 323 res, userLimit, err := qm.runQuotaOp(ctx, r, "foo1", -1) 324 So(err, ShouldBeNil) 325 So(userLimit, ShouldResembleProto, googlerLimit) 326 So(res, ShouldResembleProto, "apb.OpResult{ 327 NewBalance: 4, 328 AccountStatus: quotapb.OpResult_CREATED, 329 }) 330 So(ct.TSMonSentValue( 331 ctx, 332 metrics.Internal.QuotaOp, 333 lProject, 334 "infra", 335 "googlers-limit", 336 "runs", 337 "foo1", 338 "SUCCESS", 339 ), ShouldEqual, 1) 340 341 res, userLimit, err = qm.runQuotaOp(ctx, r, "foo2", -2) 342 So(err, ShouldBeNil) 343 So(userLimit, ShouldResembleProto, googlerLimit) 344 So(res, ShouldResembleProto, "apb.OpResult{ 345 NewBalance: 2, 346 PreviousBalance: 4, 347 PreviousBalanceAdjusted: 4, 348 AccountStatus: quotapb.OpResult_ALREADY_EXISTS, 349 }) 350 So(ct.TSMonSentValue( 351 ctx, 352 metrics.Internal.QuotaOp, 353 lProject, 354 "infra", 355 "googlers-limit", 356 "runs", 357 "foo2", 358 "SUCCESS", 359 ), ShouldEqual, 1) 360 }) 361 362 Convey("runQuotaOp() respects unlimited policy", func() { 363 googlerLimit := genUserLimit("googlers-limit", 0, []string{"group:googlers"}) 364 cg.UserLimits = append(cg.UserLimits, googlerLimit) 365 prjcfgtest.Update(ctx, lProject, cfg) 366 367 r := &run.Run{ 368 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 369 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 370 BilledTo: makeIdentity(tEmail), 371 } 372 373 res, userLimit, err := qm.runQuotaOp(ctx, r, "", -1) 374 So(err, ShouldBeNil) 375 So(userLimit, ShouldResembleProto, googlerLimit) 376 So(res, ShouldResembleProto, "apb.OpResult{ 377 NewBalance: -1, 378 AccountStatus: quotapb.OpResult_CREATED, 379 }) 380 }) 381 382 Convey("runQuotaOp() bound checks", func() { 383 googlerLimit := genUserLimit("googlers-limit", 1, []string{"group:googlers"}) 384 cg.UserLimits = append(cg.UserLimits, googlerLimit) 385 prjcfgtest.Update(ctx, lProject, cfg) 386 387 r := &run.Run{ 388 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 389 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 390 BilledTo: makeIdentity(tEmail), 391 } 392 393 Convey("quota underflow", func() { 394 res, userLimit, err := qm.runQuotaOp(ctx, r, "debit", -2) 395 So(err, ShouldEqual, quota.ErrQuotaApply) 396 So(userLimit, ShouldResembleProto, googlerLimit) 397 So(res, ShouldResembleProto, "apb.OpResult{ 398 AccountStatus: quotapb.OpResult_CREATED, 399 Status: quotapb.OpResult_ERR_UNDERFLOW, 400 }) 401 So(ct.TSMonSentValue( 402 ctx, 403 metrics.Internal.QuotaOp, 404 lProject, 405 "infra", 406 "googlers-limit", 407 "runs", 408 "debit", 409 "ERR_UNDERFLOW", 410 ), ShouldEqual, 1) 411 }) 412 413 Convey("quota overflow", func() { 414 // overflow doesn't err but gets capped. 415 res, userLimit, err := qm.runQuotaOp(ctx, r, "credit", 10) 416 So(err, ShouldBeNil) 417 So(userLimit, ShouldResembleProto, googlerLimit) 418 So(res, ShouldResembleProto, "apb.OpResult{ 419 AccountStatus: quotapb.OpResult_CREATED, 420 NewBalance: 1, 421 }) 422 So(ct.TSMonSentValue( 423 ctx, 424 metrics.Internal.QuotaOp, 425 lProject, 426 "infra", 427 "googlers-limit", 428 "runs", 429 "credit", 430 "SUCCESS", 431 ), ShouldEqual, 1) 432 }) 433 }) 434 435 Convey("runQuotaOp() on policy change", func() { 436 chromiesLimit := genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}) 437 googlerLimit := genUserLimit("googlers-limit", 5, []string{"group:googlers"}) 438 partnerLimit := genUserLimit("partners-limit", 2, []string{"group:partners"}) 439 cg.UserLimits = append(cg.UserLimits, chromiesLimit, googlerLimit, partnerLimit) 440 prjcfgtest.Update(ctx, lProject, cfg) 441 442 r := &run.Run{ 443 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 444 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 445 BilledTo: makeIdentity(tEmail), 446 } 447 448 res, userLimit, err := qm.runQuotaOp(ctx, r, "", -1) 449 So(err, ShouldBeNil) 450 So(userLimit, ShouldResembleProto, googlerLimit) 451 So(res, ShouldResembleProto, "apb.OpResult{ 452 NewBalance: 4, 453 AccountStatus: quotapb.OpResult_CREATED, 454 }) 455 456 Convey("decrease in quota allowance results in underflow", func() { 457 // Update policy 458 ctx = auth.WithState(ctx, &authtest.FakeState{ 459 Identity: makeIdentity(tEmail), 460 IdentityGroups: []string{"partners"}, 461 }) 462 463 // This is not a real scenario within CV but just checks 464 // extreme examples. 465 res, userLimit, err = qm.runQuotaOp(ctx, r, "", -2) 466 So(err, ShouldEqual, quota.ErrQuotaApply) 467 So(userLimit, ShouldResembleProto, partnerLimit) 468 So(res, ShouldResembleProto, "apb.OpResult{ 469 PreviousBalance: 4, 470 PreviousBalanceAdjusted: 1, 471 Status: quotapb.OpResult_ERR_UNDERFLOW, 472 }) 473 }) 474 475 Convey("decrease in quota allowance within bounds", func() { 476 // Update policy 477 ctx = auth.WithState(ctx, &authtest.FakeState{ 478 Identity: makeIdentity(tEmail), 479 IdentityGroups: []string{"partners"}, 480 }) 481 482 res, userLimit, err = qm.runQuotaOp(ctx, r, "", -1) 483 So(err, ShouldBeNil) 484 So(userLimit, ShouldResembleProto, partnerLimit) 485 So(res, ShouldResembleProto, "apb.OpResult{ 486 NewBalance: 0, 487 PreviousBalance: 4, 488 PreviousBalanceAdjusted: 1, 489 AccountStatus: quotapb.OpResult_ALREADY_EXISTS, 490 }) 491 }) 492 493 Convey("increase in quota allowance", func() { 494 // Update policy 495 ctx = auth.WithState(ctx, &authtest.FakeState{ 496 Identity: makeIdentity(tEmail), 497 IdentityGroups: []string{"chromies"}, 498 }) 499 500 res, userLimit, err = qm.runQuotaOp(ctx, r, "", -1) 501 So(err, ShouldBeNil) 502 So(userLimit, ShouldResembleProto, chromiesLimit) 503 So(res, ShouldResembleProto, "apb.OpResult{ 504 NewBalance: 8, 505 PreviousBalance: 4, 506 PreviousBalanceAdjusted: 9, 507 AccountStatus: quotapb.OpResult_ALREADY_EXISTS, 508 }) 509 }) 510 511 Convey("increase in quota allowance in the overflow case is bounded by the new limit", func() { 512 // Update policy 513 ctx = auth.WithState(ctx, &authtest.FakeState{ 514 Identity: makeIdentity(tEmail), 515 IdentityGroups: []string{"chromies"}, 516 }) 517 518 // This is not a real scenario within CV but just checks 519 // extreme examples. 520 res, userLimit, err = qm.runQuotaOp(ctx, r, "", 2) 521 So(err, ShouldBeNil) 522 So(userLimit, ShouldResembleProto, chromiesLimit) 523 So(res, ShouldResembleProto, "apb.OpResult{ 524 NewBalance: 10, 525 PreviousBalance: 4, 526 PreviousBalanceAdjusted: 9, 527 AccountStatus: quotapb.OpResult_ALREADY_EXISTS, 528 }) 529 }) 530 }) 531 532 Convey("runQuotaOp() is idempotent", func() { 533 googlerLimit := genUserLimit("googlers-limit", 5, []string{"group:googlers"}) 534 cg.UserLimits = append(cg.UserLimits, 535 genUserLimit("chromies-limit", 10, []string{"group:chromies", "group:chrome-infra"}), 536 googlerLimit, 537 genUserLimit("partners-limit", 10, []string{"group:partners"}), 538 ) 539 prjcfgtest.Update(ctx, lProject, cfg) 540 541 r := &run.Run{ 542 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 543 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 544 BilledTo: makeIdentity(tEmail), 545 } 546 547 res, userLimit, err := qm.runQuotaOp(ctx, r, "foo", -1) 548 So(err, ShouldBeNil) 549 So(userLimit, ShouldResembleProto, googlerLimit) 550 So(res, ShouldResembleProto, "apb.OpResult{ 551 NewBalance: 4, 552 AccountStatus: quotapb.OpResult_CREATED, 553 }) 554 555 res, userLimit, err = qm.runQuotaOp(ctx, r, "foo", -1) 556 So(err, ShouldBeNil) 557 So(userLimit, ShouldResembleProto, googlerLimit) 558 So(res, ShouldResembleProto, "apb.OpResult{ 559 NewBalance: 4, 560 AccountStatus: quotapb.OpResult_CREATED, 561 }) 562 563 res, userLimit, err = qm.runQuotaOp(ctx, r, "foo2", -2) 564 So(err, ShouldBeNil) 565 So(userLimit, ShouldResembleProto, googlerLimit) 566 So(res, ShouldResembleProto, "apb.OpResult{ 567 NewBalance: 2, 568 PreviousBalance: 4, 569 PreviousBalanceAdjusted: 4, 570 AccountStatus: quotapb.OpResult_ALREADY_EXISTS, 571 }) 572 573 res, userLimit, err = qm.runQuotaOp(ctx, r, "foo2", -2) 574 So(err, ShouldBeNil) 575 So(userLimit, ShouldResembleProto, googlerLimit) 576 So(res, ShouldResembleProto, "apb.OpResult{ 577 NewBalance: 2, 578 PreviousBalance: 4, 579 PreviousBalanceAdjusted: 4, 580 AccountStatus: quotapb.OpResult_ALREADY_EXISTS, 581 }) 582 }) 583 584 Convey("RunQuotaAccountID() hashes emailID", func() { 585 r := &run.Run{ 586 ID: common.MakeRunID(lProject, time.Now(), 1, []byte{}), 587 ConfigGroupID: prjcfg.MakeConfigGroupID(prjcfg.ComputeHash(cfg), "infra"), 588 BilledTo: makeIdentity(tEmail), 589 } 590 591 emailHash := md5.Sum([]byte(tEmail)) 592 593 So(qm.RunQuotaAccountID(r), ShouldResembleProto, "apb.AccountID{ 594 AppId: "cv", 595 Realm: "chromium", 596 Namespace: "infra", 597 Name: hex.EncodeToString(emailHash[:]), 598 ResourceType: "runs", 599 }) 600 }) 601 }) 602 }