go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/quota/integration_test.go (about)

     1  // Copyright 2022 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_test
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/alicebob/miniredis/v2"
    23  	"github.com/gomodule/redigo/redis"
    24  	"google.golang.org/protobuf/types/known/durationpb"
    25  	"google.golang.org/protobuf/types/known/timestamppb"
    26  
    27  	"go.chromium.org/luci/common/clock"
    28  	"go.chromium.org/luci/common/clock/testclock"
    29  
    30  	"go.chromium.org/luci/server/auth"
    31  	"go.chromium.org/luci/server/quota"
    32  	"go.chromium.org/luci/server/quota/internal/quotakeys"
    33  	"go.chromium.org/luci/server/quota/quotapb"
    34  	"go.chromium.org/luci/server/redisconn"
    35  
    36  	_ "go.chromium.org/luci/server/quota/quotatestmonkeypatch"
    37  
    38  	. "github.com/smartystreets/goconvey/convey"
    39  	. "go.chromium.org/luci/common/testing/assertions"
    40  )
    41  
    42  var integrationTestApp = quota.Register("ita", &quota.ApplicationOptions{
    43  	ResourceTypes: []string{
    44  		"qps",
    45  		"thingBytes",
    46  	},
    47  })
    48  
    49  func TestFullFlow(t *testing.T) {
    50  	Convey(`FullFlow`, t, func() {
    51  		ctx := context.Background()
    52  
    53  		s, err := miniredis.Run()
    54  		So(err, ShouldBeNil)
    55  		defer s.Close()
    56  
    57  		tc := testclock.New(testclock.TestRecentTimeUTC.Round(time.Microsecond))
    58  		ctx = clock.Set(ctx, tc)
    59  
    60  		s.SetTime(clock.Now(ctx))
    61  
    62  		ctx = redisconn.UsePool(ctx, &redis.Pool{
    63  			Dial: func() (redis.Conn, error) {
    64  				return redis.Dial("tcp", s.Addr())
    65  			},
    66  		})
    67  
    68  		policy := &quotapb.PolicyConfig{
    69  			Policies: []*quotapb.PolicyConfig_Entry{
    70  				{
    71  					Key: &quotapb.PolicyKey{Namespace: "ns1", Name: "cool people", ResourceType: "qps"},
    72  					Policy: &quotapb.Policy{
    73  						Default: 123,
    74  						Limit:   5156,
    75  						Refill: &quotapb.Policy_Refill{
    76  							Units:    1,
    77  							Interval: 3,
    78  						},
    79  					},
    80  				},
    81  			},
    82  		}
    83  		Convey(`policy`, func() {
    84  			Convey(`valid`, func() {
    85  				policyConfigID, err := integrationTestApp.LoadPoliciesAuto(ctx, "@internal:integrationTestApp", policy)
    86  				So(err, ShouldBeNil)
    87  				So(policyConfigID, ShouldResembleProto, &quotapb.PolicyConfigID{
    88  					AppId:         "ita",
    89  					Realm:         "@internal:integrationTestApp",
    90  					VersionScheme: 1,
    91  					Version:       "W`-@.nn^o,_kP)j_BSM_.:jhMfP]d`mj]J/kKTpa",
    92  				})
    93  
    94  				cfgKey := quotakeys.PolicyConfigID(policyConfigID)
    95  				So(s.Keys(), ShouldResemble, []string{cfgKey})
    96  				hkeys, err := s.HKeys(cfgKey)
    97  				So(err, ShouldBeNil)
    98  				polKey := &quotapb.PolicyKey{
    99  					Namespace:    "ns1",
   100  					Name:         "cool people",
   101  					ResourceType: "qps",
   102  				}
   103  				polKeyStr := quotakeys.PolicyKey(polKey)
   104  				So(hkeys, ShouldResemble, []string{polKeyStr, "~loaded_time"})
   105  				So(s.HGet(cfgKey, polKeyStr), ShouldEqual, string([]byte{
   106  					147, 123, 205, 20, 36, 146, 1, 3,
   107  				}))
   108  
   109  				Convey(`account`, func() {
   110  					requestId := "somereq"
   111  					rsp, err := quota.ApplyOps(ctx, requestId, durationpb.New(time.Hour), []*quotapb.Op{
   112  						{
   113  							AccountId:  integrationTestApp.AccountID("project:realm", "cvgroup1", "username", "qps"),
   114  							PolicyId:   &quotapb.PolicyID{Config: policyConfigID, Key: polKey},
   115  							RelativeTo: quotapb.Op_CURRENT_BALANCE, Delta: 5,
   116  						},
   117  					})
   118  					So(err, ShouldBeNil)
   119  					So(rsp, ShouldResembleProto, &quotapb.ApplyOpsResponse{
   120  						Results: []*quotapb.OpResult{
   121  							{NewBalance: 128, AccountStatus: quotapb.OpResult_CREATED},
   122  						},
   123  						OriginallySet: timestamppb.New(clock.Now(ctx)),
   124  					})
   125  					So(s.TTL(quotakeys.RequestDedupKey(&quotapb.RequestDedupKey{
   126  						Ident:     string(auth.CurrentIdentity(ctx)),
   127  						RequestId: requestId,
   128  					})), ShouldEqual, time.Hour)
   129  				})
   130  			})
   131  
   132  			Convey(`bad resource`, func() {
   133  				policy.Policies[0].Key.ResourceType = "fakey fake"
   134  				_, err := integrationTestApp.LoadPoliciesAuto(ctx, "@internal:integrationTestApp", policy)
   135  				So(err, ShouldErrLike, "unknown resource type")
   136  			})
   137  		})
   138  
   139  		Convey(`GetAccounts`, func() {
   140  			existingAccountID := &quotapb.AccountID{
   141  				AppId:        "foo",
   142  				Realm:        "bar",
   143  				Namespace:    "baz",
   144  				Name:         "qux",
   145  				ResourceType: "quux",
   146  			}
   147  
   148  			nonExistingAccountID := &quotapb.AccountID{
   149  				AppId:        "foo1",
   150  				Realm:        "bar1",
   151  				Namespace:    "baz1",
   152  				Name:         "qux1",
   153  				ResourceType: "quux1",
   154  			}
   155  
   156  			// Add test value to retrieve.
   157  			_, err := quota.ApplyOps(ctx, "", nil, []*quotapb.Op{
   158  				{
   159  					AccountId:  existingAccountID,
   160  					RelativeTo: quotapb.Op_ZERO,
   161  					Delta:      1,
   162  					Options:    uint32(quotapb.Op_IGNORE_POLICY_BOUNDS),
   163  				},
   164  			})
   165  			So(err, ShouldBeNil)
   166  
   167  			res, err := quota.GetAccounts(ctx, []*quotapb.AccountID{existingAccountID, nonExistingAccountID})
   168  			So(err, ShouldBeNil)
   169  			So(res, ShouldResembleProto, &quotapb.GetAccountsResponse{
   170  				Accounts: []*quotapb.GetAccountsResponse_AccountState{
   171  					{
   172  						Id: existingAccountID,
   173  						Account: &quotapb.Account{
   174  							Balance:   1,
   175  							UpdatedTs: timestamppb.New(clock.Now(ctx)),
   176  						},
   177  					},
   178  					{
   179  						Id: nonExistingAccountID,
   180  					},
   181  				},
   182  			})
   183  		})
   184  
   185  	})
   186  }