satellite/{console,accountfreeze}: test freeze effects

This change adds more tests to the autofreeze chore and the freeze
service according to the testplan linked in the issue below.

Issue: https://github.com/storj/storj/issues/5738

Change-Id: Ib2afaa283961b2e7ef6fb6e5613ee083ac7d79eb
This commit is contained in:
Wilfred Asomani 2023-04-19 00:02:47 +00:00 committed by Storj Robot
parent 816c3d31ac
commit 260b71e70c
5 changed files with 162 additions and 8 deletions

View File

@ -98,6 +98,24 @@ type Config struct {
HubSpot HubSpotConfig HubSpot HubSpotConfig
} }
// FreezeTracker is an interface for account freeze event tracking methods.
type FreezeTracker interface {
// TrackAccountFrozen sends an account frozen event to Segment.
TrackAccountFrozen(userID uuid.UUID, email string)
// TrackAccountUnfrozen sends an account unfrozen event to Segment.
TrackAccountUnfrozen(userID uuid.UUID, email string)
// TrackAccountUnwarned sends an account unwarned event to Segment.
TrackAccountUnwarned(userID uuid.UUID, email string)
// TrackAccountFreezeWarning sends an account freeze warning event to Segment.
TrackAccountFreezeWarning(userID uuid.UUID, email string)
// TrackLargeUnpaidInvoice sends an event to Segment indicating that a user has not paid a large invoice.
TrackLargeUnpaidInvoice(invID string, userID uuid.UUID, email string)
}
// Service for sending analytics. // Service for sending analytics.
// //
// architecture: Service // architecture: Service

View File

@ -63,16 +63,16 @@ type AccountFreezeService struct {
freezeEventsDB AccountFreezeEvents freezeEventsDB AccountFreezeEvents
usersDB Users usersDB Users
projectsDB Projects projectsDB Projects
analytics *analytics.Service tracker analytics.FreezeTracker
} }
// NewAccountFreezeService creates a new account freeze service. // NewAccountFreezeService creates a new account freeze service.
func NewAccountFreezeService(freezeEventsDB AccountFreezeEvents, usersDB Users, projectsDB Projects, analytics *analytics.Service) *AccountFreezeService { func NewAccountFreezeService(freezeEventsDB AccountFreezeEvents, usersDB Users, projectsDB Projects, tracker analytics.FreezeTracker) *AccountFreezeService {
return &AccountFreezeService{ return &AccountFreezeService{
freezeEventsDB: freezeEventsDB, freezeEventsDB: freezeEventsDB,
usersDB: usersDB, usersDB: usersDB,
projectsDB: projectsDB, projectsDB: projectsDB,
analytics: analytics, tracker: tracker,
} }
} }
@ -173,7 +173,7 @@ func (s *AccountFreezeService) FreezeUser(ctx context.Context, userID uuid.UUID)
} }
} }
s.analytics.TrackAccountFrozen(userID, user.Email) s.tracker.TrackAccountFrozen(userID, user.Email)
return nil return nil
} }
@ -212,7 +212,7 @@ func (s *AccountFreezeService) UnfreezeUser(ctx context.Context, userID uuid.UUI
return err return err
} }
s.analytics.TrackAccountUnfrozen(userID, user.Email) s.tracker.TrackAccountUnfrozen(userID, user.Email)
return nil return nil
} }
@ -233,7 +233,7 @@ func (s *AccountFreezeService) WarnUser(ctx context.Context, userID uuid.UUID) (
return ErrAccountFreeze.Wrap(err) return ErrAccountFreeze.Wrap(err)
} }
s.analytics.TrackAccountFreezeWarning(userID, user.Email) s.tracker.TrackAccountFreezeWarning(userID, user.Email)
return nil return nil
} }
@ -256,7 +256,7 @@ func (s *AccountFreezeService) UnWarnUser(ctx context.Context, userID uuid.UUID)
return err return err
} }
s.analytics.TrackAccountUnwarned(userID, user.Email) s.tracker.TrackAccountUnwarned(userID, user.Email)
return nil return nil
} }
@ -271,3 +271,8 @@ func (s *AccountFreezeService) GetAll(ctx context.Context, userID uuid.UUID) (fr
return freeze, warning, nil return freeze, warning, nil
} }
// TestChangeFreezeTracker changes the freeze tracker service for tests.
func (s *AccountFreezeService) TestChangeFreezeTracker(t analytics.FreezeTracker) {
s.tracker = t
}

View File

@ -8,9 +8,13 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap"
"storj.io/common/memory"
"storj.io/common/testcontext" "storj.io/common/testcontext"
"storj.io/common/testrand"
"storj.io/storj/private/testplanet" "storj.io/storj/private/testplanet"
"storj.io/storj/satellite"
"storj.io/storj/satellite/console" "storj.io/storj/satellite/console"
) )
@ -242,3 +246,76 @@ func TestAccountFreezeAlreadyFrozen(t *testing.T) {
}) })
}) })
} }
func TestFreezeEffects(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 2, UplinkCount: 2,
Reconfigure: testplanet.Reconfigure{
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
config.AccountFreeze.Enabled = true
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
usersDB := sat.DB.Console().Users()
projectsDB := sat.DB.Console().Projects()
consoleService := sat.API.Console.Service
freezeService := console.NewAccountFreezeService(sat.DB.Console().AccountFreezeEvents(), usersDB, projectsDB, sat.API.Analytics.Service)
uplink1 := planet.Uplinks[0]
user1, _, err := consoleService.GetUserByEmailWithUnverified(ctx, uplink1.User[sat.ID()].Email)
require.NoError(t, err)
bucketName := "testbucket"
path := "test/path"
expectedData := testrand.Bytes(50 * memory.KiB)
shouldUploadAndDownload := func(testT *testing.T) {
// Should be able to upload because account is not warned nor frozen.
err = uplink1.Upload(ctx, sat, bucketName, path, expectedData)
require.NoError(testT, err)
// Should be able to download because account is not frozen.
data, err := uplink1.Download(ctx, sat, bucketName, path)
require.NoError(testT, err)
require.Equal(testT, expectedData, data)
}
t.Run("Freeze effect on project owner", func(t *testing.T) {
shouldUploadAndDownload(t)
err = freezeService.WarnUser(ctx, user1.ID)
require.NoError(t, err)
// Should be able to download because account is not frozen.
data, err := uplink1.Download(ctx, sat, bucketName, path)
require.NoError(t, err)
require.Equal(t, expectedData, data)
err = freezeService.FreezeUser(ctx, user1.ID)
require.NoError(t, err)
// Should not be able to upload because account is frozen.
err = uplink1.Upload(ctx, sat, bucketName, path, expectedData)
require.Error(t, err)
// Should not be able to download because account is frozen.
_, err = uplink1.Download(ctx, sat, bucketName, path)
require.Error(t, err)
// Should not be able to create bucket because account is frozen.
err = uplink1.CreateBucket(ctx, sat, "anotherBucket")
require.Error(t, err)
// Should be able to list even if frozen.
objects, err := uplink1.ListObjects(ctx, sat, bucketName)
require.NoError(t, err)
require.Len(t, objects, 1)
// Should be able to delete even if frozen.
err = uplink1.DeleteObject(ctx, sat, bucketName, path)
require.NoError(t, err)
})
})
}

View File

@ -188,6 +188,11 @@ func (chore *Chore) TestSetNow(f func() time.Time) {
chore.nowFn = f chore.nowFn = f
} }
// TestSetFreezeService changes the freeze service for tests.
func (chore *Chore) TestSetFreezeService(service *console.AccountFreezeService) {
chore.freezeService = service
}
// Close closes the chore. // Close closes the chore.
func (chore *Chore) Close() error { func (chore *Chore) Close() error {
chore.Loop.Close() chore.Loop.Close()

View File

@ -12,6 +12,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"storj.io/common/testcontext" "storj.io/common/testcontext"
"storj.io/common/uuid"
"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"
@ -33,8 +34,9 @@ func TestAutoFreezeChore(t *testing.T) {
customerDB := sat.Core.DB.StripeCoinPayments().Customers() customerDB := sat.Core.DB.StripeCoinPayments().Customers()
usersDB := sat.DB.Console().Users() usersDB := sat.DB.Console().Users()
projectsDB := sat.DB.Console().Projects() projectsDB := sat.DB.Console().Projects()
service := console.NewAccountFreezeService(sat.DB.Console().AccountFreezeEvents(), usersDB, projectsDB, sat.API.Analytics.Service) service := console.NewAccountFreezeService(sat.DB.Console().AccountFreezeEvents(), usersDB, projectsDB, newFreezeTrackerMock(t))
chore := sat.Core.Payments.AccountFreeze chore := sat.Core.Payments.AccountFreeze
chore.TestSetFreezeService(service)
user, err := sat.AddUser(ctx, console.CreateUser{ user, err := sat.AddUser(ctx, console.CreateUser{
FullName: "Test User", FullName: "Test User",
@ -49,6 +51,8 @@ func TestAutoFreezeChore(t *testing.T) {
curr := string(stripe.CurrencyUSD) curr := string(stripe.CurrencyUSD)
t.Run("No freeze event for paid invoice", func(t *testing.T) { t.Run("No freeze event for paid invoice", func(t *testing.T) {
// AnalyticsMock tests that events are sent once.
service.TestChangeFreezeTracker(newFreezeTrackerMock(t))
item, err := stripeClient.InvoiceItems().New(&stripe.InvoiceItemParams{ item, err := stripeClient.InvoiceItems().New(&stripe.InvoiceItemParams{
Params: stripe.Params{Context: ctx}, Params: stripe.Params{Context: ctx},
Amount: &amount, Amount: &amount,
@ -102,6 +106,8 @@ func TestAutoFreezeChore(t *testing.T) {
}) })
t.Run("Freeze event for failed invoice", func(t *testing.T) { t.Run("Freeze event for failed invoice", func(t *testing.T) {
// AnalyticsMock tests that events are sent once.
service.TestChangeFreezeTracker(newFreezeTrackerMock(t))
// reset chore clock // reset chore clock
chore.TestSetNow(time.Now) chore.TestSetNow(time.Now)
@ -160,3 +166,46 @@ func TestAutoFreezeChore(t *testing.T) {
}) })
}) })
} }
type freezeTrackerMock struct {
t *testing.T
freezeCounts map[string]int
warnCounts map[string]int
}
func newFreezeTrackerMock(t *testing.T) *freezeTrackerMock {
return &freezeTrackerMock{
t: t,
freezeCounts: map[string]int{},
warnCounts: map[string]int{},
}
}
// The following functions are implemented from analytics.FreezeTracker.
// They mock/test to make sure freeze events are sent just once.
func (mock *freezeTrackerMock) TrackAccountFrozen(_ uuid.UUID, email string) {
mock.freezeCounts[email]++
// make sure this tracker has not been called already for this email.
require.Equal(mock.t, 1, mock.freezeCounts[email])
}
func (mock *freezeTrackerMock) TrackAccountUnfrozen(_ uuid.UUID, email string) {
mock.freezeCounts[email]--
// make sure this tracker has not been called already for this email.
require.Equal(mock.t, 0, mock.freezeCounts[email])
}
func (mock *freezeTrackerMock) TrackAccountUnwarned(_ uuid.UUID, email string) {
mock.warnCounts[email]--
// make sure this tracker has not been called already for this email.
require.Equal(mock.t, 0, mock.warnCounts[email])
}
func (mock *freezeTrackerMock) TrackAccountFreezeWarning(_ uuid.UUID, email string) {
mock.warnCounts[email]++
// make sure this tracker has not been called already for this email.
require.Equal(mock.t, 1, mock.warnCounts[email])
}
func (mock *freezeTrackerMock) TrackLargeUnpaidInvoice(_ string, _ uuid.UUID, _ string) {}