go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/quota/internal/luatest/testdata/account_test.lua (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  
    16  require 'test_fixtures'
    17  
    18  Account = G.Account
    19  Policy = G.Policy
    20  Utils = G.Utils
    21  
    22  local DO_NOT_CAP_PROPOSED = PB.E["go.chromium.org.luci.server.quota.quotapb.Op.Options"].DO_NOT_CAP_PROPOSED
    23  local IGNORE_POLICY_BOUNDS = PB.E["go.chromium.org.luci.server.quota.quotapb.Op.Options"].IGNORE_POLICY_BOUNDS
    24  local WITH_POLICY_LIMIT_DELTA = PB.E["go.chromium.org.luci.server.quota.quotapb.Op.Options"].WITH_POLICY_LIMIT_DELTA
    25  
    26  function enumMap(e)
    27    local ret = {}
    28    for k, v in pairs(PB.E[e]) do
    29      if type(k) == "string" then
    30        ret[k] = k
    31      end
    32    end
    33    return ret
    34  end
    35  
    36  local AccountStatus = enumMap("go.chromium.org.luci.server.quota.quotapb.OpResult.AccountStatus")
    37  local OpStatus = enumMap("go.chromium.org.luci.server.quota.quotapb.OpResult.OpStatus")
    38  
    39  function mkOp(fields)
    40    return PB.new("go.chromium.org.luci.server.quota.quotapb.RawOp", fields)
    41  end
    42  
    43  function setPolicy(policy_config, policy_key, policy_params)
    44    local ref = PB.new("go.chromium.org.luci.server.quota.quotapb.PolicyRef", {
    45      config = policy_config,
    46      key = policy_key,
    47    })
    48    local policy = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy", policy_params)
    49  
    50    redis.call("HSET", ref.config, ref.key, PB.marshal(policy))
    51  
    52    return ref
    53  end
    54  
    55  function testAccountGetEmpty()
    56    local account = Account:get(KEYS[1])
    57    lu.assertEquals(account.account_status, AccountStatus.CREATED)
    58    lu.assertEquals(account.key, KEYS[1], "account key is saved")
    59  end
    60  
    61  function testSaveEmptyAccount()
    62    local account = Account:get(KEYS[1])
    63    account:write()
    64    lu.assertEquals(#redis.DATA, 0, "save is a NOOP until we set a value.")
    65  end
    66  
    67  -- Use `./datatool -type Account -out msgpackpb+lua`
    68  --
    69  -- {
    70  --  "balance": 100,
    71  --  "updated_ts": "2022-12-12T08:41:26+00:00"
    72  -- }
    73  --
    74  -- The msgpackpb+pretty of this is:
    75  -- [
    76  --   8i100,
    77  --   [
    78  --     32u1670834486,
    79  --   ],
    80  -- ]
    81  local packedAccount = "\146d\145\206c\150\2336"
    82  
    83  function testAccountSetValue()
    84    local account = Account:get(KEYS[1])
    85    account.pb.balance = 100
    86  
    87    account:write()
    88    lu.assertEquals(redis.DATA[KEYS[1]], packedAccount, "data is saved in packed form")
    89  end
    90  
    91  function testAccountLoad()
    92    redis.call("SET", KEYS[1], packedAccount)
    93    local account = Account:get(KEYS[1])
    94  
    95    lu.assertEquals(account.pb.balance, 100)
    96    lu.assertEquals(account.pb.updated_ts.seconds, 1670834486)
    97  end
    98  
    99  function testNilAccountSetPolicy()
   100    local account = Account:get(KEYS[1])
   101    local policy = {
   102      policy_ref = PB.new("go.chromium.org.luci.server.quota.quotapb.PolicyRef", {
   103        config = "policy_key",
   104        key = "policy_name",
   105      }),
   106      pb = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy", {
   107        default = 100,
   108        limit = 1000,
   109        refill = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy.Refill", {
   110          units = 1,
   111          interval = 1,
   112        }),
   113        lifetime = PB.new("google.protobuf.Duration", {
   114          seconds = 123,
   115        })
   116      }),
   117    }
   118  
   119    account:setPolicy(policy)
   120  
   121    lu.assertEquals(account.pb.balance, policy.pb.default)
   122  
   123    account:write()
   124  
   125    lu.assertEquals(account.pb.updated_ts, Utils.NOW)
   126  
   127    lu.assertEquals(redis.DATA[KEYS[1]], "\149d\145\206c\150\2336\145\206c\150\2336\146\170policy_key\171policy_name\132\001d\002\205\003\232\003\146\001\001\005\145{")
   128    lu.assertEquals(redis.TTL[KEYS[1]], 123000)
   129  end
   130  
   131  function testNilAccountSetPolicyNoRefill()
   132    local account = Account:get(KEYS[1])
   133    local policy = {
   134      policy_ref = PB.new("go.chromium.org.luci.server.quota.quotapb.PolicyRef", {
   135        config = "policy_key",
   136        key = "policy_name",
   137      }),
   138      pb = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy", {
   139        default = 100,
   140        limit = 1000,
   141        lifetime = PB.new("google.protobuf.Duration", {
   142          seconds = 123,
   143        })
   144      }),
   145    }
   146  
   147    account:setPolicy(policy)
   148  
   149    lu.assertEquals(account.pb.balance, policy.pb.default)
   150    lu.assertEquals(account.pb.updated_ts, Utils.NOW)
   151  
   152    account:write()
   153  
   154    lu.assertEquals(redis.DATA[KEYS[1]], "\149d\145\206c\150\2336\145\206c\150\2336\146\170policy_key\171policy_name\131\001d\002\205\003\232\005\145{")
   155    lu.assertEquals(redis.TTL[KEYS[1]], 123000)
   156  end
   157  
   158  function testNilAccountSetPolicyInfinite()
   159    local account = Account:get(KEYS[1])
   160    local policy = {
   161      policy_ref = PB.new("go.chromium.org.luci.server.quota.quotapb.PolicyRef", {
   162        config = "policy_key",
   163        key = "policy_name",
   164      }),
   165      pb = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy", {
   166        default = 100,
   167        limit = 1000,
   168        refill = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy.Refill", {
   169          units = 1,
   170          interval = 0, -- infinity
   171        }),
   172        lifetime = PB.new("google.protobuf.Duration", {
   173          seconds = 123,
   174        })
   175      }),
   176    }
   177  
   178    account:setPolicy(policy)
   179    lu.assertEquals(account.pb.balance, policy.pb.limit)
   180  
   181    -- note; this is not quite real, ApplyOps would be the only way to do this,
   182    -- and it will explicitly avoid decreasing the balance. However applyRefill
   183    -- on get should restore this.
   184    account.pb.balance = 2
   185    lu.assertEquals(account.pb.balance, 2)
   186  
   187    account:write()
   188    Account.CACHE = {}
   189    account = Account:get(KEYS[1])
   190    lu.assertEquals(account.pb.balance, policy.pb.limit)
   191  
   192    lu.assertEquals(redis.DATA[KEYS[1]], "\149\002\145\206c\150\2336\145\206c\150\2336\146\170policy_key\171policy_name\132\001d\002\205\003\232\003\145\001\005\145{")
   193    lu.assertEquals(redis.TTL[KEYS[1]], 123000)
   194  end
   195  
   196  function testAccountSetPolicyRefill()
   197    local account = Account:get(KEYS[1])
   198    account.pb.balance = 100
   199    account:write()
   200  
   201    lu.assertEquals(account.pb.balance, 100)
   202    lu.assertEquals(account.pb.updated_ts, Utils.NOW)
   203  
   204    Utils.NOW.seconds = Utils.NOW.seconds + 3
   205  
   206    local policy = {
   207      policy_ref = PB.new("go.chromium.org.luci.server.quota.quotapb.PolicyRef", {
   208        config = "policy_key",
   209        key = "policy_name",
   210      }),
   211      pb = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy", {
   212        default = 100,
   213        limit = 1000,
   214        refill = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy.Refill", {
   215          units = 1,
   216          interval = 2,
   217        }),
   218        lifetime = PB.new("google.protobuf.Duration", {
   219          seconds = 123,
   220        })
   221      }),
   222    }
   223  
   224    account:setPolicy(policy)
   225  
   226    lu.assertEquals(account.pb.balance, 100)
   227    lu.assertEquals(account.pb.policy_ref.config, "policy_key")
   228    lu.assertEquals(account.pb.policy_ref.key, "policy_name")
   229    lu.assertEquals(account.pb.policy.limit, 1000)
   230    lu.assertEquals(account.pb.policy.refill.interval, 2)
   231    lu.assertEquals(account.pb.policy_change_ts, Utils.NOW)
   232  
   233    account:write()
   234  
   235    Utils.NOW.seconds = Utils.NOW.seconds + 3
   236    Account.CACHE = {}
   237    account = Account:get(KEYS[1])
   238    lu.assertEquals(account.pb.balance, 102)
   239  
   240    Utils.NOW.seconds = Utils.NOW.seconds + 1
   241    Account.CACHE = {}
   242    account = Account:get(KEYS[1])
   243    lu.assertEquals(account.pb.balance, 102)
   244  
   245    Utils.NOW.seconds = Utils.NOW.seconds + 1
   246    Account.CACHE = {}
   247    account = Account:get(KEYS[1])
   248    lu.assertEquals(account.pb.balance, 103)
   249  
   250    account:write()
   251    lu.assertEquals(redis.DATA[KEYS[1]], "\149g\145\206c\150\233>\145\206c\150\2339\146\170policy_key\171policy_name\132\001d\002\205\003\232\003\146\001\002\005\145{")
   252    lu.assertEquals(redis.TTL[KEYS[1]], 123000)
   253  end
   254  
   255  function testAccountBalanceExtreme()
   256    local account = Account:get(KEYS[1])
   257    account.pb.balance = 9007199254740991
   258    account:write() -- ok
   259  
   260    account.pb.balance = 9007199254740991 + 1
   261    lu.assertErrorMsgContains("overflow", account.write, account)
   262  
   263    account.pb.balance = -9007199254740991
   264    account:write() -- ok
   265  
   266    account.pb.balance = -9007199254740991 - 1
   267    lu.assertErrorMsgContains("underflows", account.write, account)
   268  end
   269  
   270  function testAccountApplyOpFinitePolicy()
   271    local account = Account:get(KEYS[1])
   272  
   273    local updateAccountPolicy = function(config, key, limit)
   274      local p = {
   275        pb = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy", {
   276          limit = limit,
   277        }),
   278        policy_ref = PB.new("go.chromium.org.luci.server.quota.quotapb.PolicyRef", {
   279          config = config,
   280          key = key,
   281        }),
   282      }
   283      account:setPolicy(p)
   284    end
   285  
   286    local setOp = function(cur, amt, options, relative_to, ref)
   287      account.pb.balance = cur
   288      return mkOp({
   289        delta = amt,
   290        relative_to = relative_to or "ZERO",
   291        options = options or 0,
   292        policy_ref = ref,
   293      })
   294    end
   295  
   296    updateAccountPolicy("policy_key", "policy_name", 1000)
   297  
   298    lu.assertEquals(account:applyOp(setOp(20, 300)), nil)  -- increase < limit
   299    lu.assertEquals(account.pb.balance, 300)
   300    lu.assertEquals(account:applyOp(setOp(20, 5)), nil)    -- decrease > 0
   301    lu.assertEquals(account.pb.balance, 5)
   302    lu.assertEquals(account:applyOp(setOp(20, 1000)), nil) -- increase == limit
   303    lu.assertEquals(account.pb.balance, 1000)
   304    lu.assertEquals(account:applyOp(setOp(20, 0)), nil)    -- decrease == 0
   305    lu.assertEquals(account.pb.balance, 0)
   306  
   307    lu.assertEquals(account:applyOp(setOp(-100, 300)), nil)  -- increase < limit
   308    lu.assertEquals(account.pb.balance, 300)
   309    lu.assertEquals(account:applyOp(setOp(-100, -50)), nil)  -- increase < limit
   310    lu.assertEquals(account.pb.balance, -50)
   311    lu.assertEquals(account:applyOp(setOp(-100, 0)), nil)    -- increase == 0
   312    lu.assertEquals(account.pb.balance, 0)
   313    lu.assertEquals(account:applyOp(setOp(-100, 1000)), nil) -- increase == limit
   314    lu.assertEquals(account.pb.balance, 1000)
   315  
   316    lu.assertEquals(account:applyOp(setOp(2000, 300)), nil)  -- decrease < limit
   317    lu.assertEquals(account.pb.balance, 300)
   318    lu.assertEquals(account:applyOp(setOp(2000, 1000)), nil) -- decrease == limit
   319    lu.assertEquals(account.pb.balance, 1000)
   320    lu.assertEquals(account:applyOp(setOp(2000, 0)), nil)    -- decrease == 0
   321    lu.assertEquals(account.pb.balance, 0)
   322  
   323    -- soft cap
   324    lu.assertEquals(account:applyOp(setOp(2000, 1500)), nil) -- decrease < limit
   325    lu.assertEquals(account.pb.balance, 1000)
   326    -- hard cap
   327    lu.assertEquals(account:applyOp(setOp(2000, 1500, DO_NOT_CAP_PROPOSED)), nil) -- decrease < limit
   328    lu.assertEquals(account.pb.balance, 1500)
   329  
   330    local rslt = {}
   331    Account.applyOp(account, setOp(20, -1), rslt)
   332    lu.assertEquals(rslt.status, "ERR_UNDERFLOW")
   333    lu.assertEquals(account.pb.balance, 20)
   334  
   335    local rslt = {}
   336    Account.applyOp(account, setOp(-100, -200), rslt)
   337    lu.assertEquals(rslt.status, "ERR_UNDERFLOW")
   338    lu.assertEquals(account.pb.balance, -100)
   339  
   340    local rslt = {}
   341    Account.applyOp(account, setOp(20, 1001, DO_NOT_CAP_PROPOSED), rslt)
   342    lu.assertEquals(rslt.status, "ERR_OVERFLOW")
   343    lu.assertEquals(account.pb.balance, 20)
   344  
   345    local rslt = {}
   346    Account.applyOp(account, setOp(1500, 2000, DO_NOT_CAP_PROPOSED), rslt)
   347    lu.assertEquals(rslt.status, "ERR_OVERFLOW")
   348    lu.assertEquals(account.pb.balance, 1500)
   349  
   350    -- WITH_POLICY_LIMIT_DELTA test cases
   351    local rslt = {}
   352    updateAccountPolicy("config1", "key1", 5)
   353    local ref = setPolicy("config2", "key2", {limit = 10})
   354    Account.applyOp(account, setOp(3, -2, WITH_POLICY_LIMIT_DELTA, "CURRENT_BALANCE", ref), rslt) -- policy limit increase 5 < 10
   355    lu.assertEquals(rslt.previous_balance_adjusted, 8)
   356    lu.assertEquals(account.pb.balance, 6)
   357  
   358    local rslt = {}
   359    local ref = setPolicy("config3", "key3", {limit = 5})
   360    Account.applyOp(account, setOp(3, 1, WITH_POLICY_LIMIT_DELTA, "CURRENT_BALANCE", ref), rslt) -- policy limit decrease 10 > 5
   361    lu.assertEquals(rslt.previous_balance_adjusted, -2)
   362    lu.assertEquals(account.pb.balance, -1)
   363  
   364    local rslt = {}
   365    local ref = setPolicy("config4", "key4", {limit = 5})
   366    Account.applyOp(account, setOp(3, 1, WITH_POLICY_LIMIT_DELTA, "CURRENT_BALANCE", ref), rslt) -- old limit == new limit
   367    lu.assertEquals(rslt.previous_balance_adjusted, 3)
   368    lu.assertEquals(account.pb.balance, 4)
   369  
   370    account:setPolicy(nil) -- unset policy
   371    local rslt = {}
   372    local ref = setPolicy("config5", "key5", {limit = 5})
   373    Account.applyOp(account, setOp(3, 1, WITH_POLICY_LIMIT_DELTA, "CURRENT_BALANCE", ref), rslt) -- no-op since old policy_Ref == nil
   374    lu.assertEquals(rslt.previous_balance_adjusted, nil)
   375    lu.assertEquals(account.pb.balance, 4)
   376  end
   377  
   378  function testAccountApplyOpInfinitePolicy()
   379    local account = Account:get(KEYS[1])
   380  
   381    local p = {
   382      pb = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy", {
   383        limit = 1000,
   384        refill = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy.Refill", {
   385          units = 1,
   386        })
   387      }),
   388      policy_ref = PB.new("go.chromium.org.luci.server.quota.quotapb.PolicyRef", {
   389        config = "policy_key",
   390        key = "policy_name",
   391      }),
   392    }
   393    account:setPolicy(p)
   394  
   395    -- note: we do not need to test cases where the current balance is something
   396    -- other than `pb.policy.limit`. We rely on Account construction and setPolicy
   397    -- to call applyRefill(), and for applyOp to always leave balance >= limit.
   398    local setOp = function(amt, options)
   399      account.pb.balance = p.pb.limit
   400      return mkOp({
   401        delta = amt,
   402        relative_to = "ZERO",
   403        options = options or 0,
   404      })
   405    end
   406  
   407    lu.assertEquals(account:applyOp(setOp(0)), nil)
   408    lu.assertEquals(account.pb.balance, 1000)
   409    lu.assertEquals(account:applyOp(setOp(1000)), nil)
   410    lu.assertEquals(account.pb.balance, 1000)
   411    lu.assertEquals(account:applyOp(setOp(500)), nil)
   412    lu.assertEquals(account.pb.balance, 1000)
   413    lu.assertEquals(account:applyOp(setOp(1234)), nil)
   414    lu.assertEquals(account.pb.balance, 1000)
   415  
   416    -- we can, however, start with a higher than limit balance.
   417    local op = setOp(1234, DO_NOT_CAP_PROPOSED)
   418    account.pb.balance = 2000
   419    lu.assertEquals(account:applyOp(op), nil)
   420    lu.assertEquals(account.pb.balance, 1234)
   421  
   422    local rslt = {}
   423    Account.applyOp(account, setOp(-1000), rslt)
   424    lu.assertEquals(rslt.status, "ERR_UNDERFLOW")
   425    lu.assertEquals(account.pb.balance, 1000)
   426  
   427    local rslt = {}
   428    Account.applyOp(account, setOp(1001, DO_NOT_CAP_PROPOSED), rslt)
   429    lu.assertEquals(rslt.status, "ERR_OVERFLOW")
   430    lu.assertEquals(account.pb.balance, 1000)
   431  
   432    local rslt = {}
   433    Account.applyOp(account, setOp(-1), rslt)
   434    lu.assertEquals(rslt.status, "ERR_UNDERFLOW")
   435    lu.assertEquals(account.pb.balance, 1000)
   436  end
   437  
   438  function testAccountApplyOpZeroPolicy()
   439    local account = Account:get(KEYS[1])
   440  
   441    local p = {
   442      pb = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy", {
   443        limit = 0,
   444      }),
   445      policy_ref = PB.new("go.chromium.org.luci.server.quota.quotapb.PolicyRef", {
   446        config = "policy_key",
   447        key = "policy_name",
   448      }),
   449    }
   450    account:setPolicy(p)
   451  
   452    local setOp = function(cur, amt, options)
   453      account.pb.balance = cur
   454      return mkOp({
   455        delta = amt,
   456        relative_to = "ZERO",
   457        options = options,
   458      })
   459    end
   460  
   461    lu.assertEquals(account:applyOp(setOp(0, 0)), nil)
   462    lu.assertEquals(account.pb.balance, 0)
   463    lu.assertEquals(account:applyOp(setOp(100, 1)), nil)
   464    lu.assertEquals(account.pb.balance, 0)
   465    lu.assertEquals(account:applyOp(setOp(100, 1, DO_NOT_CAP_PROPOSED)), nil)
   466    lu.assertEquals(account.pb.balance, 1)
   467    lu.assertEquals(account:applyOp(setOp(2000, 1000)), nil)
   468    lu.assertEquals(account.pb.balance, 0)
   469    lu.assertEquals(account:applyOp(setOp(2000, 1000, DO_NOT_CAP_PROPOSED)), nil)
   470    lu.assertEquals(account.pb.balance, 1000)
   471    lu.assertEquals(account:applyOp(setOp(-2000, -1000)), nil)
   472    lu.assertEquals(account.pb.balance, -1000)
   473  
   474    local rslt = {}
   475    Account.applyOp(account, setOp(0, 1000, DO_NOT_CAP_PROPOSED), rslt)
   476    lu.assertEquals(rslt.status, "ERR_OVERFLOW")
   477    lu.assertEquals(account.pb.balance, 0)
   478  
   479    local rslt = {}
   480    Account.applyOp(account, setOp(-100, 1, DO_NOT_CAP_PROPOSED), rslt)
   481    lu.assertEquals(rslt.status, "ERR_OVERFLOW")
   482    lu.assertEquals(account.pb.balance, -100)
   483  
   484    local rslt = {}
   485    Account.applyOp(account, setOp(0, 1, DO_NOT_CAP_PROPOSED), rslt)
   486    lu.assertEquals(rslt.status, "ERR_OVERFLOW")
   487    lu.assertEquals(account.pb.balance, 0)
   488  
   489    local rslt = {}
   490    Account.applyOp(account, setOp(0, -1), rslt)
   491    lu.assertEquals(rslt.status, "ERR_UNDERFLOW")
   492    lu.assertEquals(account.pb.balance, 0)
   493  end
   494  
   495  function testAccountApplyOpAddPolicy()
   496    local ref = setPolicy("policy_config", "policy_name", {
   497      default = 20,
   498      limit = 100,
   499      refill = PB.new("go.chromium.org.luci.server.quota.quotapb.Policy.Refill",  {
   500        units = 1,
   501        interval = 100,
   502      }),
   503      lifetime = PB.new("google.protobuf.Duration", {
   504        seconds = 3600,
   505      }),
   506    })
   507  
   508    local account = Account:get(KEYS[1])
   509  
   510    account:applyOp(mkOp({
   511      policy_ref = ref,
   512      relative_to = "CURRENT_BALANCE",
   513      delta = 13,
   514    }))
   515  
   516    lu.assertEquals(account.pb.balance, 33)
   517  end
   518  
   519  function testApplyOpsOK()
   520    local pol_one = setPolicy("policy_config", "one", {
   521      default = 10,
   522      limit = 100,
   523    })
   524    local pol_two = setPolicy("policy_config", "two", {
   525      default = 20,
   526      limit = 200,
   527    })
   528  
   529    local rsp, allOK = Account.ApplyOps({
   530      mkOp({
   531        account_ref = "acct1",
   532        policy_ref = pol_one,
   533        relative_to = "CURRENT_BALANCE",
   534        delta = 100, -- should hit limit, stop at 100.
   535      }),
   536      mkOp({
   537        account_ref = "acct2",
   538        policy_ref = pol_two,
   539        relative_to = "CURRENT_BALANCE",
   540        delta = 100, -- stop at 120 under limit.
   541      }),
   542    })
   543  
   544    lu.assertTrue(allOK)
   545    lu.assertEquals(#rsp.results, 2)
   546    lu.assertEquals(rsp.results[1].account_status, AccountStatus.CREATED)
   547    lu.assertEquals(rsp.results[1].status_msg, "")
   548    lu.assertEquals(rsp.results[1].status, OpStatus.SUCCESS)
   549    lu.assertEquals(rsp.results[1].previous_balance, 0)
   550    lu.assertEquals(rsp.results[1].new_balance, 100)
   551  
   552    lu.assertEquals(rsp.results[2].account_status, AccountStatus.CREATED)
   553    lu.assertEquals(rsp.results[2].status_msg, "")
   554    lu.assertEquals(rsp.results[2].status, OpStatus.SUCCESS)
   555    lu.assertEquals(rsp.results[2].previous_balance, 0)
   556    lu.assertEquals(rsp.results[2].new_balance, 120)
   557  end
   558  
   559  function testApplyOpsFail()
   560    local pol_one = setPolicy("policy_config", "one", {
   561      default = 10,
   562      limit = 100,
   563    })
   564  
   565    local rsp, allOK = Account.ApplyOps({
   566      mkOp({
   567        account_ref = "acct1",
   568        relative_to = "CURRENT_BALANCE",
   569        options = DO_NOT_CAP_PROPOSED + IGNORE_POLICY_BOUNDS,
   570        delta = 1000,
   571      }),
   572  
   573      mkOp({
   574        account_ref = "acct2",
   575        policy_ref = pol_one,
   576        relative_to = "CURRENT_BALANCE",
   577        options = DO_NOT_CAP_PROPOSED,
   578        delta = 1000,
   579      }),
   580  
   581      mkOp({
   582        account_ref = "acct3",
   583        policy_ref = pol_one,
   584        relative_to = "CURRENT_BALANCE",
   585        delta = -1000,
   586      }),
   587  
   588      mkOp({
   589        account_ref = "acct4",
   590        policy_ref = {config = "nope", key = "missing"},
   591        relative_to = "CURRENT_BALANCE",
   592        delta = 100,
   593      }),
   594  
   595      mkOp({
   596        account_ref = "acct5",
   597        relative_to = "CURRENT_BALANCE",
   598        delta = 100,
   599      }),
   600  
   601      mkOp({
   602        account_ref = "acct6",
   603        relative_to = "ZERO",
   604        delta = 100,
   605      }),
   606    })
   607  
   608    lu.assertFalse(allOK)
   609    lu.assertEquals(#rsp.results, 6)
   610    lu.assertEquals(rsp.results[1].account_status, AccountStatus.CREATED)
   611    lu.assertStrContains(rsp.results[1].status_msg, "IGNORE_POLICY_BOUNDS and DO_NOT_CAP_PROPOSED both set")
   612    lu.assertEquals(rsp.results[1].status, OpStatus.ERR_UNKNOWN)
   613  
   614    lu.assertEquals(rsp.results[2].account_status, AccountStatus.CREATED)
   615    lu.assertEquals(rsp.results[2].status_msg, "")
   616    lu.assertEquals(rsp.results[2].status, OpStatus.ERR_OVERFLOW)
   617  
   618    lu.assertEquals(rsp.results[3].account_status, AccountStatus.CREATED)
   619    lu.assertEquals(rsp.results[3].status, OpStatus.ERR_UNDERFLOW)
   620    lu.assertEquals(rsp.results[3].status_msg, "")
   621  
   622    lu.assertEquals(rsp.results[4].account_status, AccountStatus.CREATED)
   623    lu.assertEquals(rsp.results[4].status, OpStatus.ERR_UNKNOWN_POLICY)
   624    lu.assertEquals(rsp.results[4].status_msg, "")
   625  
   626    lu.assertEquals(rsp.results[5].account_status, AccountStatus.CREATED)
   627    lu.assertEquals(rsp.results[5].status_msg, "")
   628    lu.assertEquals(rsp.results[5].status, OpStatus.ERR_POLICY_REQUIRED)
   629  end
   630  
   631  function testApplyOpsMultiSameAccount()
   632    local req = PB.new("go.chromium.org.luci.server.quota.quotapb.ApplyOpsRequest", {
   633  
   634    })
   635  end
   636  
   637  return lu.LuaUnit.run("-v")