satellite/{db,console,billing}: fix legal/violation freeze

This change fixes the behavior of legal freeze; it now does not allow
lists and deletes for users in that freeze. It also fixes an issue
where users who have been legal/violation frozen will have their limits
upgraded, partially exiting freeze status when they pay with STORJ
tokens.

Issue: storj/storj-private/#517
storj/storj-private/#515

Change-Id: I6fa2b6353159984883c60a2888617da1ee51ce0a
This commit is contained in:
Wilfred Asomani 2023-12-01 12:25:13 +00:00 committed by Storj Robot
parent e1c12674c5
commit 0590eadc17
11 changed files with 181 additions and 38 deletions

View File

@ -199,7 +199,8 @@ func TestProjectLimitCache(t *testing.T) {
require.EqualValues(t, expectedSegmentLimit, *actualSegmentLimitFromDB) require.EqualValues(t, expectedSegmentLimit, *actualSegmentLimitFromDB)
// rate and burst limit // rate and burst limit
require.NoError(t, projects.UpdateRateLimit(ctx, secondTestProject.ID, expectedRateLimit)) rateLimit := expectedRateLimit
require.NoError(t, projects.UpdateRateLimit(ctx, secondTestProject.ID, &rateLimit))
require.NoError(t, projects.UpdateBurstLimit(ctx, secondTestProject.ID, expectedBurstLimit)) require.NoError(t, projects.UpdateBurstLimit(ctx, secondTestProject.ID, expectedBurstLimit))
limits, err := projectLimitCache.GetLimits(ctx, secondTestProject.ID) limits, err := projectLimitCache.GetLimits(ctx, secondTestProject.ID)

View File

@ -248,7 +248,7 @@ func (server *Server) putProjectLimit(w http.ResponseWriter, r *http.Request) {
return return
} }
err = server.db.Console().Projects().UpdateRateLimit(ctx, project.ID, *arguments.Rate) err = server.db.Console().Projects().UpdateRateLimit(ctx, project.ID, arguments.Rate)
if err != nil { if err != nil {
sendJSONError(w, "failed to update rate", sendJSONError(w, "failed to update rate",
err.Error(), http.StatusInternalServerError) err.Error(), http.StatusInternalServerError)

View File

@ -556,6 +556,11 @@ func (s *AccountFreezeService) LegalFreezeUser(ctx context.Context, userID uuid.
return errs.New("User is already frozen due to ToS violation") return errs.New("User is already frozen due to ToS violation")
} }
var limits *AccountFreezeEventLimits
if freezes.BillingFreeze != nil {
limits = freezes.BillingFreeze.Limits
}
userLimits := UsageLimits{ userLimits := UsageLimits{
Storage: user.ProjectStorageLimit, Storage: user.ProjectStorageLimit,
Bandwidth: user.ProjectBandwidthLimit, Bandwidth: user.ProjectBandwidthLimit,
@ -564,13 +569,16 @@ func (s *AccountFreezeService) LegalFreezeUser(ctx context.Context, userID uuid.
legalFreeze := freezes.LegalFreeze legalFreeze := freezes.LegalFreeze
if legalFreeze == nil { if legalFreeze == nil {
if limits == nil {
limits = &AccountFreezeEventLimits{
User: userLimits,
Projects: make(map[uuid.UUID]UsageLimits),
}
}
legalFreeze = &AccountFreezeEvent{ legalFreeze = &AccountFreezeEvent{
UserID: userID, UserID: userID,
Type: LegalFreeze, Type: LegalFreeze,
Limits: &AccountFreezeEventLimits{ Limits: limits,
User: userLimits,
Projects: make(map[uuid.UUID]UsageLimits),
},
} }
} }
@ -595,9 +603,15 @@ func (s *AccountFreezeService) LegalFreezeUser(ctx context.Context, userID uuid.
projLimits.Segment = *p.SegmentLimit projLimits.Segment = *p.SegmentLimit
} }
// If project limits have been zeroed already, we should not override what is in the freeze table. // If project limits have been zeroed already, we should not override what is in the freeze table.
if projLimits != (UsageLimits{}) { if projLimits == (UsageLimits{}) {
legalFreeze.Limits.Projects[p.ID] = projLimits if freezes.BillingFreeze == nil {
continue
}
// if limits were zeroed in a billing freeze, we should use those
projLimits = freezes.BillingFreeze.Limits.Projects[p.ID]
} }
projLimits.RateLimit = p.RateLimit
legalFreeze.Limits.Projects[p.ID] = projLimits
} }
_, err = tx.AccountFreezeEvents().Upsert(ctx, legalFreeze) _, err = tx.AccountFreezeEvents().Upsert(ctx, legalFreeze)
@ -615,6 +629,13 @@ func (s *AccountFreezeService) LegalFreezeUser(ctx context.Context, userID uuid.
if err != nil { if err != nil {
return err return err
} }
// zero project's rate limit to prevent lists/deletes
zeroLimit := 0
err = tx.Projects().UpdateRateLimit(ctx, proj.ID, &zeroLimit)
if err != nil {
return ErrAccountFreeze.Wrap(err)
}
} }
if freezes.BillingWarning != nil { if freezes.BillingWarning != nil {
@ -662,6 +683,12 @@ func (s *AccountFreezeService) LegalUnfreezeUser(ctx context.Context, userID uui
if err != nil { if err != nil {
return err return err
} }
// remove rate limit
err = tx.Projects().UpdateRateLimit(ctx, id, limits.RateLimit)
if err != nil {
return ErrAccountFreeze.Wrap(err)
}
} }
err = tx.Users().UpdateUserProjectLimits(ctx, userID, event.Limits.User) err = tx.Users().UpdateUserProjectLimits(ctx, userID, event.Limits.User)

View File

@ -16,6 +16,7 @@ import (
"storj.io/storj/private/testplanet" "storj.io/storj/private/testplanet"
"storj.io/storj/satellite" "storj.io/storj/satellite"
"storj.io/storj/satellite/console" "storj.io/storj/satellite/console"
"storj.io/uplink"
) )
func getUserLimits(u *console.User) console.UsageLimits { func getUserLimits(u *console.User) console.UsageLimits {
@ -31,6 +32,7 @@ func getProjectLimits(p *console.Project) console.UsageLimits {
Storage: p.StorageLimit.Int64(), Storage: p.StorageLimit.Int64(),
Bandwidth: p.BandwidthLimit.Int64(), Bandwidth: p.BandwidthLimit.Int64(),
Segment: *p.SegmentLimit, Segment: *p.SegmentLimit,
RateLimit: p.RateLimit,
} }
} }
@ -261,18 +263,26 @@ func TestAccountLegalFreeze(t *testing.T) {
require.NoError(t, usersDB.UpdateUserProjectLimits(ctx, user.ID, userLimits)) require.NoError(t, usersDB.UpdateUserProjectLimits(ctx, user.ID, userLimits))
projLimits := randUsageLimits() projLimits := randUsageLimits()
rateLimit := 20000
projLimits.RateLimit = &rateLimit
proj, err := sat.AddProject(ctx, user.ID, "") proj, err := sat.AddProject(ctx, user.ID, "")
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, projectsDB.UpdateUsageLimits(ctx, proj.ID, projLimits)) require.NoError(t, projectsDB.UpdateUsageLimits(ctx, proj.ID, projLimits))
require.NoError(t, projectsDB.UpdateRateLimit(ctx, proj.ID, projLimits.RateLimit))
checkLimits := func(testT *testing.T) { checkLimits := func(t *testing.T) {
user, err = usersDB.Get(ctx, user.ID) user, err = usersDB.Get(ctx, user.ID)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, getUserLimits(user)) require.Zero(t, getUserLimits(user))
proj, err = projectsDB.Get(ctx, proj.ID) proj, err = projectsDB.Get(ctx, proj.ID)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, getProjectLimits(proj)) usageLimits := getProjectLimits(proj)
require.Zero(t, usageLimits.Segment)
require.Zero(t, usageLimits.Storage)
require.Zero(t, usageLimits.Bandwidth)
zeroLimit := 0
require.Equal(t, &zeroLimit, usageLimits.RateLimit)
} }
frozen, err := service.IsUserFrozen(ctx, user.ID, console.LegalFreeze) frozen, err := service.IsUserFrozen(ctx, user.ID, console.LegalFreeze)
@ -322,6 +332,16 @@ func TestAccountLegalFreeze(t *testing.T) {
require.Nil(t, freezes.LegalFreeze.DaysTillEscalation) require.Nil(t, freezes.LegalFreeze.DaysTillEscalation)
checkLimits(t) checkLimits(t)
require.NoError(t, service.LegalUnfreezeUser(ctx, user.ID))
user, err = usersDB.Get(ctx, user.ID)
require.NoError(t, err)
require.Equal(t, userLimits, getUserLimits(user))
proj, err = projectsDB.Get(ctx, proj.ID)
require.NoError(t, err)
require.Equal(t, projLimits, getProjectLimits(proj))
}) })
} }
@ -490,6 +510,9 @@ func TestFreezeEffects(t *testing.T) {
Reconfigure: testplanet.Reconfigure{ Reconfigure: testplanet.Reconfigure{
Satellite: func(log *zap.Logger, index int, config *satellite.Config) { Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
config.AccountFreeze.Enabled = true config.AccountFreeze.Enabled = true
// disable limit caching
config.ProjectLimit.CacheCapacity = 0
config.Metainfo.RateLimiter.CacheCapacity = 0
}, },
}, },
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
@ -520,67 +543,86 @@ func TestFreezeEffects(t *testing.T) {
shouldNotUploadAndDownload := func(testT *testing.T) { shouldNotUploadAndDownload := func(testT *testing.T) {
// Should not be able to upload because account is frozen. // Should not be able to upload because account is frozen.
err = uplink1.Upload(ctx, sat, bucketName, path, expectedData) err = uplink1.Upload(ctx, sat, bucketName, path, expectedData)
require.Error(t, err) require.Error(testT, err)
// Should not be able to download because account is frozen. // Should not be able to download because account is frozen.
_, err = uplink1.Download(ctx, sat, bucketName, path) _, err = uplink1.Download(ctx, sat, bucketName, path)
require.Error(t, err) require.Error(testT, err)
// Should not be able to create bucket because account is frozen. // Should not be able to create bucket because account is frozen.
err = uplink1.CreateBucket(ctx, sat, "anotherBucket") err = uplink1.CreateBucket(ctx, sat, "anotherBucket")
require.Error(t, err) require.Error(testT, err)
} }
shouldListAndDelete := func(testT *testing.T) { shouldListAndDelete := func(testT *testing.T) {
// Should be able to list even if frozen. // Should be able to list even if frozen.
objects, err := uplink1.ListObjects(ctx, sat, bucketName) _, err := uplink1.ListObjects(ctx, sat, bucketName)
require.NoError(t, err) require.NoError(testT, err)
require.Len(t, objects, 1)
// Should be able to delete even if frozen. // Should be able to delete even if frozen.
err = uplink1.DeleteObject(ctx, sat, bucketName, path) err = uplink1.DeleteObject(ctx, sat, bucketName, path)
require.NoError(t, err) require.NoError(testT, err)
}
shouldNotListAndDelete := func(testT *testing.T) {
// Should not be able to list.
_, err := uplink1.ListObjects(ctx, sat, bucketName)
require.Error(testT, err)
require.ErrorIs(testT, err, uplink.ErrTooManyRequests)
// Should not be able to delete.
err = uplink1.DeleteObject(ctx, sat, bucketName, path)
require.Error(testT, err)
require.ErrorIs(testT, err, uplink.ErrTooManyRequests)
} }
t.Run("BillingFreeze effect on project owner", func(t *testing.T) { t.Run("BillingFreeze effect on project owner", func(t *testing.T) {
shouldUploadAndDownload(t) shouldUploadAndDownload(t)
shouldListAndDelete(t)
require.NoError(t, freezeService.BillingWarnUser(ctx, user1.ID)) require.NoError(t, freezeService.BillingWarnUser(ctx, user1.ID))
// Should be able to download because account is not frozen. // Should be able to download and list because account is not frozen.
shouldUploadAndDownload(t) shouldUploadAndDownload(t)
shouldListAndDelete(t)
require.NoError(t, freezeService.BillingFreezeUser(ctx, user1.ID)) require.NoError(t, freezeService.BillingFreezeUser(ctx, user1.ID))
shouldNotUploadAndDownload(t) shouldNotUploadAndDownload(t)
shouldListAndDelete(t) shouldListAndDelete(t)
require.NoError(t, freezeService.BillingUnfreezeUser(ctx, user1.ID)) require.NoError(t, freezeService.BillingUnfreezeUser(ctx, user1.ID))
shouldUploadAndDownload(t)
}) })
t.Run("ViolationFreeze effect on project owner", func(t *testing.T) { t.Run("ViolationFreeze effect on project owner", func(t *testing.T) {
shouldUploadAndDownload(t) shouldUploadAndDownload(t)
shouldListAndDelete(t)
require.NoError(t, freezeService.ViolationFreezeUser(ctx, user1.ID)) require.NoError(t, freezeService.ViolationFreezeUser(ctx, user1.ID))
shouldNotUploadAndDownload(t) shouldNotUploadAndDownload(t)
shouldListAndDelete(t) shouldListAndDelete(t)
require.NoError(t, freezeService.ViolationUnfreezeUser(ctx, user1.ID)) require.NoError(t, freezeService.ViolationUnfreezeUser(ctx, user1.ID))
shouldUploadAndDownload(t)
}) })
t.Run("LegalFreeze effect on project owner", func(t *testing.T) { t.Run("LegalFreeze effect on project owner", func(t *testing.T) {
shouldUploadAndDownload(t) shouldUploadAndDownload(t)
shouldListAndDelete(t)
require.NoError(t, freezeService.LegalFreezeUser(ctx, user1.ID)) require.NoError(t, freezeService.LegalFreezeUser(ctx, user1.ID))
shouldNotUploadAndDownload(t) shouldNotUploadAndDownload(t)
shouldNotListAndDelete(t)
shouldListAndDelete(t)
require.NoError(t, freezeService.LegalUnfreezeUser(ctx, user1.ID)) require.NoError(t, freezeService.LegalUnfreezeUser(ctx, user1.ID))
shouldListAndDelete(t)
shouldUploadAndDownload(t)
}) })
}) })
} }

View File

@ -18,15 +18,17 @@ type UpgradeUserObserver struct {
transactionsDB billing.TransactionsDB transactionsDB billing.TransactionsDB
usageLimitsConfig UsageLimitsConfig usageLimitsConfig UsageLimitsConfig
userBalanceForUpgrade int64 userBalanceForUpgrade int64
freezeService *AccountFreezeService
} }
// NewUpgradeUserObserver creates new observer instance. // NewUpgradeUserObserver creates new observer instance.
func NewUpgradeUserObserver(consoleDB DB, transactionsDB billing.TransactionsDB, usageLimitsConfig UsageLimitsConfig, userBalanceForUpgrade int64) *UpgradeUserObserver { func NewUpgradeUserObserver(consoleDB DB, transactionsDB billing.TransactionsDB, usageLimitsConfig UsageLimitsConfig, userBalanceForUpgrade int64, freezeService *AccountFreezeService) *UpgradeUserObserver {
return &UpgradeUserObserver{ return &UpgradeUserObserver{
consoleDB: consoleDB, consoleDB: consoleDB,
transactionsDB: transactionsDB, transactionsDB: transactionsDB,
usageLimitsConfig: usageLimitsConfig, usageLimitsConfig: usageLimitsConfig,
userBalanceForUpgrade: userBalanceForUpgrade, userBalanceForUpgrade: userBalanceForUpgrade,
freezeService: freezeService,
} }
} }
@ -34,6 +36,16 @@ func NewUpgradeUserObserver(consoleDB DB, transactionsDB billing.TransactionsDB,
func (o *UpgradeUserObserver) Process(ctx context.Context, transaction billing.Transaction) (err error) { func (o *UpgradeUserObserver) Process(ctx context.Context, transaction billing.Transaction) (err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)
freezes, err := o.freezeService.GetAll(ctx, transaction.UserID)
if err != nil {
return err
}
if freezes.LegalFreeze != nil || freezes.ViolationFreeze != nil {
// user can not exit these freezes by paying with tokens
return nil
}
user, err := o.consoleDB.Users().Get(ctx, transaction.UserID) user, err := o.consoleDB.Users().Get(ctx, transaction.UserID)
if err != nil { if err != nil {
return err return err

View File

@ -43,7 +43,7 @@ type Projects interface {
ListByOwnerID(ctx context.Context, userID uuid.UUID, cursor ProjectsCursor) (ProjectsPage, error) ListByOwnerID(ctx context.Context, userID uuid.UUID, cursor ProjectsCursor) (ProjectsPage, error)
// UpdateRateLimit is a method for updating projects rate limit. // UpdateRateLimit is a method for updating projects rate limit.
UpdateRateLimit(ctx context.Context, id uuid.UUID, newLimit int) error UpdateRateLimit(ctx context.Context, id uuid.UUID, newLimit *int) error
// UpdateBurstLimit is a method for updating projects burst limit. // UpdateBurstLimit is a method for updating projects burst limit.
UpdateBurstLimit(ctx context.Context, id uuid.UUID, newLimit int) error UpdateBurstLimit(ctx context.Context, id uuid.UUID, newLimit int) error

View File

@ -442,7 +442,7 @@ func TestRateLimit_ProjectRateLimitZero(t *testing.T) {
require.Len(t, projects, 1) require.Len(t, projects, 1)
zeroRateLimit := 0 zeroRateLimit := 0
err = satellite.DB.Console().Projects().UpdateRateLimit(ctx, projects[0].ID, zeroRateLimit) err = satellite.DB.Console().Projects().UpdateRateLimit(ctx, projects[0].ID, &zeroRateLimit)
require.NoError(t, err) require.NoError(t, err)
var group errs2.Group var group errs2.Group

View File

@ -24,4 +24,5 @@ type UsageLimits struct {
Storage int64 `json:"storage"` Storage int64 `json:"storage"`
Bandwidth int64 `json:"bandwidth"` Bandwidth int64 `json:"bandwidth"`
Segment int64 `json:"segment"` Segment int64 `json:"segment"`
RateLimit *int `json:"rateLimit"`
} }

View File

@ -525,15 +525,12 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB,
debug.Cycle("Payments Storjscan", peer.Payments.StorjscanChore.TransactionCycle), debug.Cycle("Payments Storjscan", peer.Payments.StorjscanChore.TransactionCycle),
) )
freezeService := console.NewAccountFreezeService(peer.DB.Console(), peer.Analytics.Service, config.Console.AccountFreeze)
choreObservers := billing.ChoreObservers{ choreObservers := billing.ChoreObservers{
UpgradeUser: console.NewUpgradeUserObserver(peer.DB.Console(), peer.DB.Billing(), config.Console.UsageLimits, config.Console.UserBalanceForUpgrade), UpgradeUser: console.NewUpgradeUserObserver(peer.DB.Console(), peer.DB.Billing(), config.Console.UsageLimits, config.Console.UserBalanceForUpgrade, freezeService),
PayInvoices: console.NewInvoiceTokenPaymentObserver( PayInvoices: console.NewInvoiceTokenPaymentObserver(
peer.DB.Console(), peer.Payments.Accounts.Invoices(), peer.DB.Console(), peer.Payments.Accounts.Invoices(),
console.NewAccountFreezeService( freezeService,
peer.DB.Console(),
peer.Analytics.Service,
config.Console.AccountFreeze,
),
), ),
} }

View File

@ -76,7 +76,7 @@ func TestChore(t *testing.T) {
assert.Equal(t, expected, actual, "unexpected balance for user %s (%q)", userID, names[userID]) assert.Equal(t, expected, actual, "unexpected balance for user %s (%q)", userID, names[userID])
} }
runTest := func(ctx *testcontext.Context, t *testing.T, consoleDB console.DB, db billing.TransactionsDB, bonusRate int64, mikeTXs, joeTXs, robertTXs []billing.Transaction, mikeBalance, joeBalance, robertBalance currency.Amount, usageLimitsConfig console.UsageLimitsConfig, userBalanceForUpgrade int64) { runTest := func(ctx *testcontext.Context, t *testing.T, consoleDB console.DB, db billing.TransactionsDB, bonusRate int64, mikeTXs, joeTXs, robertTXs []billing.Transaction, mikeBalance, joeBalance, robertBalance currency.Amount, usageLimitsConfig console.UsageLimitsConfig, userBalanceForUpgrade int64, freezeService *console.AccountFreezeService) {
paymentTypes := []billing.PaymentType{ paymentTypes := []billing.PaymentType{
newFakePaymentType(billing.StorjScanSource, newFakePaymentType(billing.StorjScanSource,
[]billing.Transaction{mike1, joe1, joe2}, []billing.Transaction{mike1, joe1, joe2},
@ -88,7 +88,7 @@ func TestChore(t *testing.T) {
} }
choreObservers := billing.ChoreObservers{ choreObservers := billing.ChoreObservers{
UpgradeUser: console.NewUpgradeUserObserver(consoleDB, db, usageLimitsConfig, userBalanceForUpgrade), UpgradeUser: console.NewUpgradeUserObserver(consoleDB, db, usageLimitsConfig, userBalanceForUpgrade, freezeService),
} }
chore := billing.NewChore(zaptest.NewLogger(t), paymentTypes, db, time.Hour, false, bonusRate, choreObservers) chore := billing.NewChore(zaptest.NewLogger(t), paymentTypes, db, time.Hour, false, bonusRate, choreObservers)
@ -118,6 +118,8 @@ func TestChore(t *testing.T) {
sat := planet.Satellites[0] sat := planet.Satellites[0]
db := sat.DB db := sat.DB
freezeService := console.NewAccountFreezeService(db.Console(), sat.Core.Analytics.Service, sat.Config.Console.AccountFreeze)
runTest(ctx, t, db.Console(), db.Billing(), 0, runTest(ctx, t, db.Console(), db.Billing(), 0,
[]billing.Transaction{mike2, mike1}, []billing.Transaction{mike2, mike1},
[]billing.Transaction{joe1, joe2}, []billing.Transaction{joe1, joe2},
@ -127,6 +129,7 @@ func TestChore(t *testing.T) {
currency.AmountFromBaseUnits(30000000, currency.USDollarsMicro), currency.AmountFromBaseUnits(30000000, currency.USDollarsMicro),
sat.Config.Console.UsageLimits, sat.Config.Console.UsageLimits,
sat.Config.Console.UserBalanceForUpgrade, sat.Config.Console.UserBalanceForUpgrade,
freezeService,
) )
}) })
}) })
@ -138,6 +141,8 @@ func TestChore(t *testing.T) {
sat := planet.Satellites[0] sat := planet.Satellites[0]
db := sat.DB db := sat.DB
freezeService := console.NewAccountFreezeService(db.Console(), sat.Core.Analytics.Service, sat.Config.Console.AccountFreeze)
runTest(ctx, t, db.Console(), db.Billing(), 10, runTest(ctx, t, db.Console(), db.Billing(), 10,
[]billing.Transaction{mike2, mike2Bonus, mike1, mike1Bonus}, []billing.Transaction{mike2, mike2Bonus, mike1, mike1Bonus},
[]billing.Transaction{joe1, joe1Bonus, joe2}, []billing.Transaction{joe1, joe1Bonus, joe2},
@ -147,6 +152,7 @@ func TestChore(t *testing.T) {
currency.AmountFromBaseUnits(30000000, currency.USDollarsMicro), currency.AmountFromBaseUnits(30000000, currency.USDollarsMicro),
sat.Config.Console.UsageLimits, sat.Config.Console.UsageLimits,
sat.Config.Console.UserBalanceForUpgrade, sat.Config.Console.UserBalanceForUpgrade,
freezeService,
) )
}) })
}) })
@ -161,29 +167,48 @@ func TestChore_UpgradeUserObserver(t *testing.T) {
usageLimitsConfig := sat.Config.Console.UsageLimits usageLimitsConfig := sat.Config.Console.UsageLimits
ts := makeTimestamp() ts := makeTimestamp()
freezeService := console.NewAccountFreezeService(db.Console(), sat.Core.Analytics.Service, sat.Config.Console.AccountFreeze)
user, err := sat.AddUser(ctx, console.CreateUser{ user, err := sat.AddUser(ctx, console.CreateUser{
FullName: "Test User", FullName: "Test User",
Email: "choreobserver@mail.test", Email: "choreobserver@mail.test",
}, 1) }, 1)
require.NoError(t, err) require.NoError(t, err)
user2, err := sat.AddUser(ctx, console.CreateUser{
FullName: "Test User",
Email: "choreobserver2@mail.test",
}, 1)
require.NoError(t, err)
user3, err := sat.AddUser(ctx, console.CreateUser{
FullName: "Test User",
Email: "choreobserver3@mail.test",
}, 1)
require.NoError(t, err)
_, err = sat.AddProject(ctx, user.ID, "Test Project") _, err = sat.AddProject(ctx, user.ID, "Test Project")
require.NoError(t, err) require.NoError(t, err)
choreObservers := billing.ChoreObservers{ choreObservers := billing.ChoreObservers{
UpgradeUser: console.NewUpgradeUserObserver(db.Console(), db.Billing(), sat.Config.Console.UsageLimits, sat.Config.Console.UserBalanceForUpgrade), UpgradeUser: console.NewUpgradeUserObserver(db.Console(), db.Billing(), sat.Config.Console.UsageLimits, sat.Config.Console.UserBalanceForUpgrade, freezeService),
} }
amount1 := int64(200) // $2 amount1 := int64(200) // $2
amount2 := int64(800) // $8 amount2 := int64(800) // $8
transaction1 := makeFakeTransaction(user.ID, billing.StorjScanSource, billing.TransactionTypeCredit, amount1, ts, `{"fake": "transaction1"}`) transaction1 := makeFakeTransaction(user.ID, billing.StorjScanSource, billing.TransactionTypeCredit, amount1, ts, `{"fake": "transaction1"}`)
transaction2 := makeFakeTransaction(user.ID, billing.StorjScanSource, billing.TransactionTypeCredit, amount2, ts.Add(time.Second*2), `{"fake": "transaction2"}`) transaction2 := makeFakeTransaction(user.ID, billing.StorjScanSource, billing.TransactionTypeCredit, amount2, ts.Add(time.Second*2), `{"fake": "transaction2"}`)
transaction3 := makeFakeTransaction(user2.ID, billing.StorjScanSource, billing.TransactionTypeCredit, amount1+amount2, ts, `{"fake": "transaction3"}`)
transaction4 := makeFakeTransaction(user3.ID, billing.StorjScanSource, billing.TransactionTypeCredit, amount1+amount2, ts.Add(time.Second*2), `{"fake": "transaction4"}`)
paymentTypes := []billing.PaymentType{ paymentTypes := []billing.PaymentType{
newFakePaymentType(billing.StorjScanSource, newFakePaymentType(billing.StorjScanSource,
[]billing.Transaction{transaction1}, []billing.Transaction{transaction1},
[]billing.Transaction{}, []billing.Transaction{},
[]billing.Transaction{transaction2}, []billing.Transaction{transaction2},
[]billing.Transaction{}, []billing.Transaction{},
[]billing.Transaction{transaction3},
[]billing.Transaction{transaction4},
[]billing.Transaction{},
), ),
} }
@ -241,6 +266,39 @@ func TestChore_UpgradeUserObserver(t *testing.T) {
require.Equal(t, usageLimitsConfig.Segment.Paid, *p.SegmentLimit) require.Equal(t, usageLimitsConfig.Segment.Paid, *p.SegmentLimit)
} }
}) })
t.Run("no upgrade for legal/violation freeze", func(t *testing.T) {
require.NoError(t, freezeService.LegalFreezeUser(ctx, user2.ID))
require.NoError(t, freezeService.ViolationFreezeUser(ctx, user3.ID))
chore.TransactionCycle.TriggerWait()
chore.TransactionCycle.Pause()
expected := currency.AmountFromBaseUnits((amount1+amount2)*int64(10000), currency.USDollarsMicro)
chore.TransactionCycle.TriggerWait()
chore.TransactionCycle.Pause()
balance, err := db.Billing().GetBalance(ctx, user2.ID)
require.NoError(t, err)
require.True(t, expected.Equal(balance))
chore.TransactionCycle.TriggerWait()
balance, err = db.Billing().GetBalance(ctx, user3.ID)
require.NoError(t, err)
require.True(t, expected.Equal(balance))
// users should not be upgraded though they have enough balance
// since they are in legal/violation freeze.
user, err = db.Console().Users().Get(ctx, user2.ID)
require.NoError(t, err)
require.False(t, user.PaidTier)
user, err = db.Console().Users().Get(ctx, user3.ID)
require.NoError(t, err)
require.False(t, user.PaidTier)
})
}) })
} }
@ -275,7 +333,7 @@ func TestChore_PayInvoiceObserver(t *testing.T) {
freezeService := console.NewAccountFreezeService(consoleDB, sat.Core.Analytics.Service, sat.Config.Console.AccountFreeze) freezeService := console.NewAccountFreezeService(consoleDB, sat.Core.Analytics.Service, sat.Config.Console.AccountFreeze)
choreObservers := billing.ChoreObservers{ choreObservers := billing.ChoreObservers{
UpgradeUser: console.NewUpgradeUserObserver(consoleDB, db.Billing(), sat.Config.Console.UsageLimits, sat.Config.Console.UserBalanceForUpgrade), UpgradeUser: console.NewUpgradeUserObserver(consoleDB, db.Billing(), sat.Config.Console.UsageLimits, sat.Config.Console.UserBalanceForUpgrade, freezeService),
PayInvoices: console.NewInvoiceTokenPaymentObserver(consoleDB, sat.Core.Payments.Accounts.Invoices(), freezeService), PayInvoices: console.NewInvoiceTokenPaymentObserver(consoleDB, sat.Core.Payments.Accounts.Invoices(), freezeService),
} }

View File

@ -265,17 +265,22 @@ func (projects *projects) Update(ctx context.Context, project *console.Project)
} }
// UpdateRateLimit is a method for updating projects rate limit. // UpdateRateLimit is a method for updating projects rate limit.
func (projects *projects) UpdateRateLimit(ctx context.Context, id uuid.UUID, newLimit int) (err error) { func (projects *projects) UpdateRateLimit(ctx context.Context, id uuid.UUID, newLimit *int) (err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)
if newLimit < 0 { if newLimit != nil && *newLimit < 0 {
return Error.New("limit can't be set to negative value") return Error.New("limit can't be set to negative value")
} }
rateLimit := dbx.Project_RateLimit_Null()
if newLimit != nil {
rateLimit = dbx.Project_RateLimit(*newLimit)
}
_, err = projects.db.Update_Project_By_Id(ctx, _, err = projects.db.Update_Project_By_Id(ctx,
dbx.Project_Id(id[:]), dbx.Project_Id(id[:]),
dbx.Project_Update_Fields{ dbx.Project_Update_Fields{
RateLimit: dbx.Project_RateLimit(newLimit), RateLimit: rateLimit,
}) })
return err return err