satellite/{payment,console,analytics} extend freeze functionality for legal freeze
This change extends the account freeze functionality account for legal freezes as well. This is a freeze event for accounts to be put on hold for legal review. It also sets the LegalHold status on the affected user. Issue: storj/storj-private#492 Change-Id: I8c733269b5cfb647c840379a6bb033da120c8280
This commit is contained in:
parent
a053b210a5
commit
cd8e9bd044
@ -88,6 +88,7 @@ const (
|
||||
eventUnpaidLargeInvoice = "Large Invoice Unpaid"
|
||||
eventUnpaidStorjscanInvoice = "Storjscan Invoice Unpaid"
|
||||
eventPendingDeletionUnpaidInvoice = "Pending Deletion Invoice Open"
|
||||
eventLegalHoldUnpaidInvoice = "Legal Hold Invoice Open"
|
||||
eventExpiredCreditNeedsRemoval = "Expired Credit Needs Removal"
|
||||
eventExpiredCreditRemoved = "Expired Credit Removed"
|
||||
eventProjectInvitationAccepted = "Project Invitation Accepted"
|
||||
@ -454,7 +455,7 @@ func (service *Service) TrackLargeUnpaidInvoice(invID string, userID uuid.UUID,
|
||||
})
|
||||
}
|
||||
|
||||
// TrackViolationFrozenUnpaidInvoice sends an event to Segment indicating that a user has not paid a large invoice.
|
||||
// TrackViolationFrozenUnpaidInvoice sends an event to Segment indicating that a violation frozen user has not paid an invoice.
|
||||
func (service *Service) TrackViolationFrozenUnpaidInvoice(invID string, userID uuid.UUID, email string) {
|
||||
if !service.config.Enabled {
|
||||
return
|
||||
@ -471,6 +472,24 @@ func (service *Service) TrackViolationFrozenUnpaidInvoice(invID string, userID u
|
||||
})
|
||||
}
|
||||
|
||||
// TrackLegalHoldUnpaidInvoice sends an event to Segment indicating that a user has not paid an invoice
|
||||
// but is in legal hold.
|
||||
func (service *Service) TrackLegalHoldUnpaidInvoice(invID string, userID uuid.UUID, email string) {
|
||||
if !service.config.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
props := segment.NewProperties()
|
||||
props.Set("email", email)
|
||||
props.Set("invoice", invID)
|
||||
|
||||
service.enqueueMessage(segment.Track{
|
||||
UserId: userID.String(),
|
||||
Event: service.satelliteName + " " + eventLegalHoldUnpaidInvoice,
|
||||
Properties: props,
|
||||
})
|
||||
}
|
||||
|
||||
// TrackStorjscanUnpaidInvoice sends an event to Segment indicating that a user has not paid an invoice, but has storjscan transaction history.
|
||||
func (service *Service) TrackStorjscanUnpaidInvoice(invID string, userID uuid.UUID, email string) {
|
||||
if !service.config.Enabled {
|
||||
|
@ -74,7 +74,7 @@ type FreezeEventsPage struct {
|
||||
|
||||
// UserFreezeEvents holds the freeze events for a user.
|
||||
type UserFreezeEvents struct {
|
||||
BillingFreeze, BillingWarning, ViolationFreeze *AccountFreezeEvent
|
||||
BillingFreeze, BillingWarning, ViolationFreeze, LegalFreeze *AccountFreezeEvent
|
||||
}
|
||||
|
||||
// AccountFreezeEventType is used to indicate the account freeze event's type.
|
||||
@ -88,6 +88,8 @@ const (
|
||||
BillingWarning AccountFreezeEventType = 1
|
||||
// ViolationFreeze signifies that the user has been frozen due to ToS violation.
|
||||
ViolationFreeze AccountFreezeEventType = 2
|
||||
// LegalFreeze signifies that the user has been frozen for legal review.
|
||||
LegalFreeze AccountFreezeEventType = 3
|
||||
)
|
||||
|
||||
// String returns a string representation of this event.
|
||||
@ -99,6 +101,8 @@ func (et AccountFreezeEventType) String() string {
|
||||
return "Billing Warning"
|
||||
case ViolationFreeze:
|
||||
return "Violation Freeze"
|
||||
case LegalFreeze:
|
||||
return "Legal Freeze"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@ -133,24 +137,19 @@ func NewAccountFreezeService(freezeEventsDB AccountFreezeEvents, usersDB Users,
|
||||
// IsUserBillingFrozen returns whether the user specified by the given ID is frozen
|
||||
// due to nonpayment of invoices.
|
||||
func (s *AccountFreezeService) IsUserBillingFrozen(ctx context.Context, userID uuid.UUID) (_ bool, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
_, err = s.freezeEventsDB.Get(ctx, userID, BillingFreeze)
|
||||
switch {
|
||||
case errors.Is(err, sql.ErrNoRows):
|
||||
return false, nil
|
||||
case err != nil:
|
||||
return false, ErrAccountFreeze.Wrap(err)
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
return s.IsUserFrozen(ctx, userID, BillingFreeze)
|
||||
}
|
||||
|
||||
// IsUserViolationFrozen returns whether the user specified by the given ID is frozen.
|
||||
func (s *AccountFreezeService) IsUserViolationFrozen(ctx context.Context, userID uuid.UUID) (_ bool, err error) {
|
||||
return s.IsUserFrozen(ctx, userID, ViolationFreeze)
|
||||
}
|
||||
|
||||
// IsUserFrozen returns whether the user specified by the given ID has an eventType freeze.
|
||||
func (s *AccountFreezeService) IsUserFrozen(ctx context.Context, userID uuid.UUID, eventType AccountFreezeEventType) (_ bool, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
_, err = s.freezeEventsDB.Get(ctx, userID, ViolationFreeze)
|
||||
_, err = s.freezeEventsDB.Get(ctx, userID, eventType)
|
||||
switch {
|
||||
case errors.Is(err, sql.ErrNoRows):
|
||||
return false, nil
|
||||
@ -177,6 +176,9 @@ func (s *AccountFreezeService) BillingFreezeUser(ctx context.Context, userID uui
|
||||
if freezes.ViolationFreeze != nil {
|
||||
return ErrAccountFreeze.New("User is already frozen due to ToS violation")
|
||||
}
|
||||
if freezes.LegalFreeze != nil {
|
||||
return ErrAccountFreeze.New("User is already frozen for legal review")
|
||||
}
|
||||
|
||||
userLimits := UsageLimits{
|
||||
Storage: user.ProjectStorageLimit,
|
||||
@ -315,7 +317,7 @@ func (s *AccountFreezeService) BillingWarnUser(ctx context.Context, userID uuid.
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
|
||||
if freezes.ViolationFreeze != nil || freezes.BillingFreeze != nil {
|
||||
if freezes.ViolationFreeze != nil || freezes.BillingFreeze != nil || freezes.LegalFreeze != nil {
|
||||
return ErrAccountFreeze.New("User is already frozen")
|
||||
}
|
||||
|
||||
@ -374,6 +376,10 @@ func (s *AccountFreezeService) ViolationFreezeUser(ctx context.Context, userID u
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
|
||||
if freezes.LegalFreeze != nil {
|
||||
return ErrAccountFreeze.New("User is already frozen for legal review")
|
||||
}
|
||||
|
||||
var limits *AccountFreezeEventLimits
|
||||
if freezes.BillingFreeze != nil {
|
||||
limits = freezes.BillingFreeze.Limits
|
||||
@ -509,6 +515,150 @@ func (s *AccountFreezeService) ViolationUnfreezeUser(ctx context.Context, userID
|
||||
return nil
|
||||
}
|
||||
|
||||
// LegalFreezeUser freezes the user specified by the given ID for legal review.
|
||||
func (s *AccountFreezeService) LegalFreezeUser(ctx context.Context, userID uuid.UUID) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
user, err := s.usersDB.Get(ctx, userID)
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
|
||||
freezes, err := s.freezeEventsDB.GetAll(ctx, userID)
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
if freezes.ViolationFreeze != nil {
|
||||
return ErrAccountFreeze.New("User is already frozen due to ToS violation")
|
||||
}
|
||||
|
||||
userLimits := UsageLimits{
|
||||
Storage: user.ProjectStorageLimit,
|
||||
Bandwidth: user.ProjectBandwidthLimit,
|
||||
Segment: user.ProjectSegmentLimit,
|
||||
}
|
||||
|
||||
legalFreeze := freezes.LegalFreeze
|
||||
if legalFreeze == nil {
|
||||
legalFreeze = &AccountFreezeEvent{
|
||||
UserID: userID,
|
||||
Type: LegalFreeze,
|
||||
Limits: &AccountFreezeEventLimits{
|
||||
User: userLimits,
|
||||
Projects: make(map[uuid.UUID]UsageLimits),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// If user limits have been zeroed already, we should not override what is in the freeze table.
|
||||
if userLimits != (UsageLimits{}) {
|
||||
legalFreeze.Limits.User = userLimits
|
||||
}
|
||||
|
||||
projects, err := s.projectsDB.GetOwn(ctx, userID)
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
for _, p := range projects {
|
||||
projLimits := UsageLimits{}
|
||||
if p.StorageLimit != nil {
|
||||
projLimits.Storage = p.StorageLimit.Int64()
|
||||
}
|
||||
if p.BandwidthLimit != nil {
|
||||
projLimits.Bandwidth = p.BandwidthLimit.Int64()
|
||||
}
|
||||
if p.SegmentLimit != nil {
|
||||
projLimits.Segment = *p.SegmentLimit
|
||||
}
|
||||
// If project limits have been zeroed already, we should not override what is in the freeze table.
|
||||
if projLimits != (UsageLimits{}) {
|
||||
legalFreeze.Limits.Projects[p.ID] = projLimits
|
||||
}
|
||||
}
|
||||
|
||||
_, err = s.freezeEventsDB.Upsert(ctx, legalFreeze)
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
|
||||
err = s.usersDB.UpdateUserProjectLimits(ctx, userID, UsageLimits{})
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
|
||||
for _, proj := range projects {
|
||||
err := s.projectsDB.UpdateUsageLimits(ctx, proj.ID, UsageLimits{})
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
if freezes.BillingWarning != nil {
|
||||
err = s.freezeEventsDB.DeleteByUserIDAndEvent(ctx, userID, BillingWarning)
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
status := LegalHold
|
||||
err = s.usersDB.Update(ctx, userID, UpdateUserRequest{
|
||||
Status: &status,
|
||||
})
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(errs.Combine(ErrFreezeUserStatusUpdate, err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LegalUnfreezeUser reverses the legal freeze placed on the user specified by the given ID.
|
||||
func (s *AccountFreezeService) LegalUnfreezeUser(ctx context.Context, userID uuid.UUID) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
user, err := s.usersDB.Get(ctx, userID)
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
|
||||
event, err := s.freezeEventsDB.Get(ctx, userID, LegalFreeze)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrAccountFreeze.New("user is not legal-frozen")
|
||||
}
|
||||
|
||||
if event.Limits == nil {
|
||||
return ErrAccountFreeze.New("freeze event limits are nil")
|
||||
}
|
||||
|
||||
for id, limits := range event.Limits.Projects {
|
||||
err = s.projectsDB.UpdateUsageLimits(ctx, id, limits)
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = s.usersDB.UpdateUserProjectLimits(ctx, userID, event.Limits.User)
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
|
||||
err = ErrAccountFreeze.Wrap(s.freezeEventsDB.DeleteByUserIDAndEvent(ctx, userID, LegalFreeze))
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(err)
|
||||
}
|
||||
|
||||
if user.Status == LegalHold {
|
||||
status := Active
|
||||
err = s.usersDB.Update(ctx, userID, UpdateUserRequest{
|
||||
Status: &status,
|
||||
})
|
||||
if err != nil {
|
||||
return ErrAccountFreeze.Wrap(errs.Combine(ErrFreezeUserStatusUpdate, err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAll returns all events for a user.
|
||||
func (s *AccountFreezeService) GetAll(ctx context.Context, userID uuid.UUID) (freezes *UserFreezeEvents, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
@ -71,6 +71,11 @@ func TestAccountBillingFreeze(t *testing.T) {
|
||||
require.Error(t, service.BillingFreezeUser(ctx, user.ID))
|
||||
require.NoError(t, service.ViolationUnfreezeUser(ctx, user.ID))
|
||||
|
||||
require.NoError(t, service.LegalFreezeUser(ctx, user.ID))
|
||||
// cannot billing freeze a legal-frozen user.
|
||||
require.Error(t, service.BillingFreezeUser(ctx, user.ID))
|
||||
require.NoError(t, service.LegalUnfreezeUser(ctx, user.ID))
|
||||
|
||||
require.NoError(t, service.BillingFreezeUser(ctx, user.ID))
|
||||
|
||||
user, err = usersDB.Get(ctx, user.ID)
|
||||
@ -238,6 +243,88 @@ func TestAccountViolationFreeze(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccountLegalFreeze(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
sat := planet.Satellites[0]
|
||||
usersDB := sat.DB.Console().Users()
|
||||
projectsDB := sat.DB.Console().Projects()
|
||||
service := console.NewAccountFreezeService(sat.DB.Console().AccountFreezeEvents(), usersDB, projectsDB, sat.API.Analytics.Service, sat.Config.Console.AccountFreeze)
|
||||
|
||||
userLimits := randUsageLimits()
|
||||
user, err := sat.AddUser(ctx, console.CreateUser{
|
||||
FullName: "Test User",
|
||||
Email: "user@mail.test",
|
||||
}, 2)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, usersDB.UpdateUserProjectLimits(ctx, user.ID, userLimits))
|
||||
|
||||
projLimits := randUsageLimits()
|
||||
proj, err := sat.AddProject(ctx, user.ID, "")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, projectsDB.UpdateUsageLimits(ctx, proj.ID, projLimits))
|
||||
|
||||
checkLimits := func(testT *testing.T) {
|
||||
user, err = usersDB.Get(ctx, user.ID)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, getUserLimits(user))
|
||||
|
||||
proj, err = projectsDB.Get(ctx, proj.ID)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, getProjectLimits(proj))
|
||||
}
|
||||
|
||||
frozen, err := service.IsUserFrozen(ctx, user.ID, console.LegalFreeze)
|
||||
require.NoError(t, err)
|
||||
require.False(t, frozen)
|
||||
|
||||
require.NoError(t, service.LegalFreezeUser(ctx, user.ID))
|
||||
frozen, err = service.IsUserFrozen(ctx, user.ID, console.LegalFreeze)
|
||||
require.NoError(t, err)
|
||||
require.True(t, frozen)
|
||||
|
||||
user, err = usersDB.Get(ctx, user.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, console.LegalHold, user.Status)
|
||||
|
||||
checkLimits(t)
|
||||
|
||||
require.NoError(t, service.LegalUnfreezeUser(ctx, user.ID))
|
||||
frozen, err = service.IsUserFrozen(ctx, user.ID, console.LegalFreeze)
|
||||
require.NoError(t, err)
|
||||
require.False(t, frozen)
|
||||
|
||||
require.NoError(t, service.BillingWarnUser(ctx, user.ID))
|
||||
frozen, err = service.IsUserFrozen(ctx, user.ID, console.LegalFreeze)
|
||||
require.NoError(t, err)
|
||||
require.False(t, frozen)
|
||||
// legal freezing a warned user should be possible.
|
||||
require.NoError(t, service.LegalFreezeUser(ctx, user.ID))
|
||||
frozen, err = service.IsUserFrozen(ctx, user.ID, console.LegalFreeze)
|
||||
require.NoError(t, err)
|
||||
require.True(t, frozen)
|
||||
require.NoError(t, service.LegalUnfreezeUser(ctx, user.ID))
|
||||
|
||||
require.NoError(t, service.BillingFreezeUser(ctx, user.ID))
|
||||
frozen, err = service.IsUserFrozen(ctx, user.ID, console.LegalFreeze)
|
||||
require.NoError(t, err)
|
||||
require.False(t, frozen)
|
||||
// legal freezing a billing frozen user should be possible.
|
||||
require.NoError(t, service.LegalFreezeUser(ctx, user.ID))
|
||||
frozen, err = service.IsUserFrozen(ctx, user.ID, console.LegalFreeze)
|
||||
require.NoError(t, err)
|
||||
require.True(t, frozen)
|
||||
|
||||
freezes, err := service.GetAll(ctx, user.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, freezes.LegalFreeze)
|
||||
require.Nil(t, freezes.LegalFreeze.DaysTillEscalation)
|
||||
|
||||
checkLimits(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveAccountBillingWarning(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
@ -286,6 +373,11 @@ func TestRemoveAccountBillingWarning(t *testing.T) {
|
||||
require.Error(t, service.BillingWarnUser(ctx, user.ID))
|
||||
require.NoError(t, service.BillingUnfreezeUser(ctx, user.ID))
|
||||
|
||||
require.NoError(t, service.LegalFreezeUser(ctx, user.ID))
|
||||
// cannot warn a legal-frozen user.
|
||||
require.Error(t, service.BillingWarnUser(ctx, user.ID))
|
||||
require.NoError(t, service.LegalUnfreezeUser(ctx, user.ID))
|
||||
|
||||
require.NoError(t, service.BillingWarnUser(ctx, user.ID))
|
||||
require.NoError(t, service.ViolationFreezeUser(ctx, user.ID))
|
||||
// cannot warn a violation-frozen user.
|
||||
@ -391,12 +483,6 @@ func TestAccountFreezeAlreadyFrozen(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, userLimits, getUserLimits(user))
|
||||
})
|
||||
|
||||
// Billing freezing a violation frozen user should not be possible.
|
||||
t.Run("ViolationFrozen user", func(t *testing.T) {
|
||||
require.NoError(t, service.ViolationFreezeUser(ctx, user.ID))
|
||||
require.Error(t, service.BillingFreezeUser(ctx, user.ID))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -463,32 +549,42 @@ func TestFreezeEffects(t *testing.T) {
|
||||
t.Run("BillingFreeze effect on project owner", func(t *testing.T) {
|
||||
shouldUploadAndDownload(t)
|
||||
|
||||
err = freezeService.BillingWarnUser(ctx, user1.ID)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, freezeService.BillingWarnUser(ctx, user1.ID))
|
||||
|
||||
// Should be able to download because account is not frozen.
|
||||
shouldUploadAndDownload(t)
|
||||
|
||||
err = freezeService.BillingFreezeUser(ctx, user1.ID)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, freezeService.BillingFreezeUser(ctx, user1.ID))
|
||||
|
||||
shouldNotUploadAndDownload(t)
|
||||
|
||||
shouldListAndDelete(t)
|
||||
|
||||
err = freezeService.BillingUnfreezeUser(ctx, user1.ID)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, freezeService.BillingUnfreezeUser(ctx, user1.ID))
|
||||
})
|
||||
|
||||
t.Run("ViolationFreeze effect on project owner", func(t *testing.T) {
|
||||
shouldUploadAndDownload(t)
|
||||
|
||||
err = freezeService.ViolationFreezeUser(ctx, user1.ID)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, freezeService.ViolationFreezeUser(ctx, user1.ID))
|
||||
|
||||
shouldNotUploadAndDownload(t)
|
||||
|
||||
shouldListAndDelete(t)
|
||||
|
||||
require.NoError(t, freezeService.ViolationUnfreezeUser(ctx, user1.ID))
|
||||
})
|
||||
|
||||
t.Run("LegalFreeze effect on project owner", func(t *testing.T) {
|
||||
shouldUploadAndDownload(t)
|
||||
|
||||
require.NoError(t, freezeService.LegalFreezeUser(ctx, user1.ID))
|
||||
|
||||
shouldNotUploadAndDownload(t)
|
||||
|
||||
shouldListAndDelete(t)
|
||||
|
||||
require.NoError(t, freezeService.LegalUnfreezeUser(ctx, user1.ID))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -192,6 +192,12 @@ func (chore *Chore) attemptBillingFreezeWarn(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
if freezes.LegalFreeze != nil {
|
||||
infoLog("Ignoring invoice; account already frozen for legal review")
|
||||
chore.analytics.TrackLegalHoldUnpaidInvoice(invoice.ID, userID, user.Email)
|
||||
continue
|
||||
}
|
||||
|
||||
shouldEscalate := func(event *console.AccountFreezeEvent) bool {
|
||||
if event == nil || event.DaysTillEscalation == nil {
|
||||
return false
|
||||
@ -352,6 +358,11 @@ func (chore *Chore) attemptBillingUnfreezeUnwarn(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
if event.Type == console.LegalFreeze {
|
||||
infoLog("Skipping legal freeze event")
|
||||
continue
|
||||
}
|
||||
|
||||
user, err := chore.usersDB.Get(ctx, event.UserID)
|
||||
if err != nil {
|
||||
errorLog("Could not get user", err)
|
||||
|
@ -58,6 +58,91 @@ func TestAutoFreezeChore(t *testing.T) {
|
||||
amount := int64(100)
|
||||
curr := string(stripe.CurrencyUSD)
|
||||
|
||||
t.Run("No billing event for legal frozen user", func(t *testing.T) {
|
||||
// AnalyticsMock tests that events are sent once.
|
||||
service.TestChangeFreezeTracker(newFreezeTrackerMock(t))
|
||||
|
||||
violatingUser, err := sat.AddUser(ctx, console.CreateUser{
|
||||
FullName: "Violating User",
|
||||
Email: "legalhold@mail.test",
|
||||
}, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
cus2, err := customerDB.GetCustomerID(ctx, violatingUser.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
inv, err := stripeClient.Invoices().New(&stripe.InvoiceParams{
|
||||
Params: stripe.Params{Context: ctx},
|
||||
Customer: &cus2,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = stripeClient.InvoiceItems().New(&stripe.InvoiceItemParams{
|
||||
Params: stripe.Params{Context: ctx},
|
||||
Amount: &amount,
|
||||
Currency: &curr,
|
||||
Customer: &cus2,
|
||||
Invoice: &inv.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
paymentMethod := stripe1.MockInvoicesPayFailure
|
||||
inv, err = stripeClient.Invoices().Pay(inv.ID, &stripe.InvoicePayParams{
|
||||
Params: stripe.Params{Context: ctx},
|
||||
PaymentMethod: &paymentMethod,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Equal(t, stripe.InvoiceStatusOpen, inv.Status)
|
||||
|
||||
failed, err := invoicesDB.ListFailed(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(failed))
|
||||
|
||||
require.NoError(t, service.LegalFreezeUser(ctx, violatingUser.ID))
|
||||
|
||||
chore.Loop.TriggerWait()
|
||||
|
||||
// user should not be billing warned or frozen.
|
||||
freezes, err := service.GetAll(ctx, violatingUser.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, freezes)
|
||||
require.Nil(t, freezes.BillingWarning)
|
||||
require.Nil(t, freezes.BillingFreeze)
|
||||
require.NotNil(t, freezes.LegalFreeze)
|
||||
|
||||
// forward date to after the grace period
|
||||
chore.TestSetNow(func() time.Time {
|
||||
return time.Now().Add(sat.Config.Console.AccountFreeze.BillingWarnGracePeriod).Add(24 * time.Hour)
|
||||
})
|
||||
chore.Loop.TriggerWait()
|
||||
|
||||
// user should still not be billing warned or frozen.
|
||||
freezes, err = service.GetAll(ctx, violatingUser.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, freezes)
|
||||
require.Nil(t, freezes.BillingFreeze)
|
||||
require.Nil(t, freezes.BillingWarning)
|
||||
require.NotNil(t, freezes.LegalFreeze)
|
||||
|
||||
paymentMethod = stripe1.MockInvoicesPaySuccess
|
||||
_, err = stripeClient.Invoices().Pay(inv.ID, &stripe.InvoicePayParams{
|
||||
Params: stripe.Params{Context: ctx},
|
||||
PaymentMethod: &paymentMethod,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, stripe.InvoiceStatusPaid, inv.Status)
|
||||
|
||||
chore.Loop.TriggerWait()
|
||||
|
||||
// paying for the invoice does not remove the legal freeze
|
||||
freezes, err = service.GetAll(ctx, violatingUser.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, freezes)
|
||||
require.Nil(t, freezes.BillingFreeze)
|
||||
require.Nil(t, freezes.BillingWarning)
|
||||
require.NotNil(t, freezes.LegalFreeze)
|
||||
})
|
||||
|
||||
t.Run("No billing event for violation frozen user", func(t *testing.T) {
|
||||
// AnalyticsMock tests that events are sent once.
|
||||
service.TestChangeFreezeTracker(newFreezeTrackerMock(t))
|
||||
|
@ -126,8 +126,8 @@ func (events *accountFreezeEvents) GetAllEvents(ctx context.Context, cursor cons
|
||||
func (events *accountFreezeEvents) GetAll(ctx context.Context, userID uuid.UUID) (freezes *console.UserFreezeEvents, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
// dbxEvents will have a max length of 3.
|
||||
// because there's at most 1 instance each of 3 types of events for a user.
|
||||
// dbxEvents will have a max length of 4.
|
||||
// because there's at most 1 instance each of 4 types of events for a user.
|
||||
dbxEvents, err := events.db.All_AccountFreezeEvent_By_UserId(ctx,
|
||||
dbx.AccountFreezeEvent_UserId(userID.Bytes()),
|
||||
)
|
||||
@ -151,6 +151,13 @@ func (events *accountFreezeEvents) GetAll(ctx context.Context, userID uuid.UUID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if console.AccountFreezeEventType(event.Event) == console.LegalFreeze {
|
||||
freezes.LegalFreeze, err = fromDBXAccountFreezeEvent(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
freezes.BillingWarning, err = fromDBXAccountFreezeEvent(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -206,7 +206,7 @@ model account_freeze_event (
|
||||
|
||||
// user_id refers to user.id column.
|
||||
field user_id blob
|
||||
// event indicates the console.AccountFreezeEventType. BillingFreeze=0, BillingWarning=1, ViolationFreeze=2.
|
||||
// event indicates the console.AccountFreezeEventType. BillingFreeze=0, BillingWarning=1, ViolationFreeze=2, LegalFreeze=3.
|
||||
field event int
|
||||
// limits are the limits before the freeze begun.
|
||||
field limits json ( nullable, updatable )
|
||||
|
Loading…
Reference in New Issue
Block a user