3b751a35c5
Removes database tables and functionality related to our custom coupon implementation because it has been superseded by the Stripe coupon and promo code system. Requires implementations of the payments Invoices interface to return coupon usages along with invoices. Change-Id: Iac52d2ff64afca8cc4dbb2d1f20e6ad4b39ddfde
297 lines
10 KiB
Go
297 lines
10 KiB
Go
// Copyright (C) 2020 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package stripecoinpayments_test
|
|
|
|
import (
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap"
|
|
|
|
"storj.io/common/memory"
|
|
"storj.io/common/pb"
|
|
"storj.io/common/testcontext"
|
|
"storj.io/storj/private/testplanet"
|
|
"storj.io/storj/satellite"
|
|
"storj.io/storj/satellite/accounting"
|
|
"storj.io/storj/satellite/console"
|
|
"storj.io/storj/satellite/metabase"
|
|
"storj.io/storj/satellite/payments/stripecoinpayments"
|
|
)
|
|
|
|
func TestService_InvoiceElementsProcessing(t *testing.T) {
|
|
testplanet.Run(t, testplanet.Config{
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
Reconfigure: testplanet.Reconfigure{
|
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
|
config.Payments.StripeCoinPayments.ListingLimit = 4
|
|
},
|
|
},
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
satellite := planet.Satellites[0]
|
|
|
|
// pick a specific date so that it doesn't fail if it's the last day of the month
|
|
// keep month + 1 because user needs to be created before calculation
|
|
period := time.Date(time.Now().Year(), time.Now().Month()+1, 20, 0, 0, 0, 0, time.UTC)
|
|
|
|
numberOfProjects := 19
|
|
// generate test data, each user has one project and some credits
|
|
for i := 0; i < numberOfProjects; i++ {
|
|
user, err := satellite.AddUser(ctx, console.CreateUser{
|
|
FullName: "testuser" + strconv.Itoa(i),
|
|
Email: "user@test" + strconv.Itoa(i),
|
|
}, 1)
|
|
require.NoError(t, err)
|
|
|
|
project, err := satellite.AddProject(ctx, user.ID, "testproject-"+strconv.Itoa(i))
|
|
require.NoError(t, err)
|
|
|
|
err = satellite.DB.Orders().UpdateBucketBandwidthSettle(ctx, project.ID, []byte("testbucket"),
|
|
pb.PieceAction_GET, int64(i+10)*memory.GiB.Int64(), 0, period)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
satellite.API.Payments.Service.SetNow(func() time.Time {
|
|
return time.Date(period.Year(), period.Month()+1, 1, 0, 0, 0, 0, time.UTC)
|
|
})
|
|
err := satellite.API.Payments.Service.PrepareInvoiceProjectRecords(ctx, period)
|
|
require.NoError(t, err)
|
|
|
|
start := time.Date(period.Year(), period.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
end := time.Date(period.Year(), period.Month()+1, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
// check if we have project record for each project
|
|
recordsPage, err := satellite.DB.StripeCoinPayments().ProjectRecords().ListUnapplied(ctx, 0, 40, start, end)
|
|
require.NoError(t, err)
|
|
require.Equal(t, numberOfProjects, len(recordsPage.Records))
|
|
|
|
err = satellite.API.Payments.Service.InvoiceApplyProjectRecords(ctx, period)
|
|
require.NoError(t, err)
|
|
|
|
// verify that we applied all unapplied project records
|
|
recordsPage, err = satellite.DB.StripeCoinPayments().ProjectRecords().ListUnapplied(ctx, 0, 40, start, end)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(recordsPage.Records))
|
|
})
|
|
}
|
|
|
|
func TestService_InvoiceUserWithManyProjects(t *testing.T) {
|
|
testplanet.Run(t, testplanet.Config{
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
Reconfigure: testplanet.Reconfigure{
|
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
|
config.Payments.StripeCoinPayments.ListingLimit = 4
|
|
},
|
|
},
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
satellite := planet.Satellites[0]
|
|
payments := satellite.API.Payments
|
|
|
|
// pick a specific date so that it doesn't fail if it's the last day of the month
|
|
// keep month + 1 because user needs to be created before calculation
|
|
period := time.Date(time.Now().Year(), time.Now().Month()+1, 20, 0, 0, 0, 0, time.UTC)
|
|
|
|
payments.Service.SetNow(func() time.Time {
|
|
return time.Date(period.Year(), period.Month()+1, 1, 0, 0, 0, 0, time.UTC)
|
|
})
|
|
start := time.Date(period.Year(), period.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
end := time.Date(period.Year(), period.Month()+1, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
numberOfProjects := 5
|
|
storageHours := 24
|
|
|
|
user, err := satellite.AddUser(ctx, console.CreateUser{
|
|
FullName: "testuser",
|
|
Email: "user@test",
|
|
}, numberOfProjects)
|
|
require.NoError(t, err)
|
|
|
|
projects := make([]*console.Project, numberOfProjects)
|
|
projectsEgress := make([]int64, len(projects))
|
|
projectsStorage := make([]int64, len(projects))
|
|
for i := 0; i < len(projects); i++ {
|
|
projects[i], err = satellite.AddProject(ctx, user.ID, "testproject-"+strconv.Itoa(i))
|
|
require.NoError(t, err)
|
|
|
|
// generate egress
|
|
projectsEgress[i] = int64(i+10) * memory.GiB.Int64()
|
|
err = satellite.DB.Orders().UpdateBucketBandwidthSettle(ctx, projects[i].ID, []byte("testbucket"),
|
|
pb.PieceAction_GET, projectsEgress[i], 0, period)
|
|
require.NoError(t, err)
|
|
|
|
// generate storage
|
|
// we need at least two tallies across time to calculate storage
|
|
projectsStorage[i] = int64(i+1) * memory.TiB.Int64()
|
|
tally := &accounting.BucketTally{
|
|
BucketLocation: metabase.BucketLocation{
|
|
ProjectID: projects[i].ID,
|
|
BucketName: "testbucket",
|
|
},
|
|
TotalBytes: projectsStorage[i],
|
|
ObjectCount: int64(i + 1),
|
|
}
|
|
tallies := map[metabase.BucketLocation]*accounting.BucketTally{
|
|
{}: tally,
|
|
}
|
|
err = satellite.DB.ProjectAccounting().SaveTallies(ctx, period, tallies)
|
|
require.NoError(t, err)
|
|
|
|
err = satellite.DB.ProjectAccounting().SaveTallies(ctx, period.Add(time.Duration(storageHours)*time.Hour), tallies)
|
|
require.NoError(t, err)
|
|
|
|
// verify that projects don't have records yet
|
|
projectRecord, err := satellite.DB.StripeCoinPayments().ProjectRecords().Get(ctx, projects[i].ID, start, end)
|
|
require.NoError(t, err)
|
|
require.Nil(t, projectRecord)
|
|
}
|
|
|
|
err = payments.Service.PrepareInvoiceProjectRecords(ctx, period)
|
|
require.NoError(t, err)
|
|
|
|
for i := 0; i < len(projects); i++ {
|
|
projectRecord, err := satellite.DB.StripeCoinPayments().ProjectRecords().Get(ctx, projects[i].ID, start, end)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, projectRecord)
|
|
require.Equal(t, projects[i].ID, projectRecord.ProjectID)
|
|
require.Equal(t, projectsEgress[i], projectRecord.Egress)
|
|
|
|
expectedStorage := float64(projectsStorage[i] * int64(storageHours))
|
|
require.Equal(t, expectedStorage, projectRecord.Storage)
|
|
|
|
expectedObjectsCount := float64((i + 1) * storageHours)
|
|
require.Equal(t, expectedObjectsCount, projectRecord.Objects)
|
|
}
|
|
|
|
// run all parts of invoice generation to see if there are no unexpected errors
|
|
err = payments.Service.InvoiceApplyProjectRecords(ctx, period)
|
|
require.NoError(t, err)
|
|
|
|
err = payments.Service.CreateInvoices(ctx, period)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestService_ProjectsWithMembers(t *testing.T) {
|
|
testplanet.Run(t, testplanet.Config{
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
Reconfigure: testplanet.Reconfigure{
|
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
|
config.Payments.StripeCoinPayments.ListingLimit = 4
|
|
},
|
|
},
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
satellite := planet.Satellites[0]
|
|
|
|
// pick a specific date so that it doesn't fail if it's the last day of the month
|
|
// keep month + 1 because user needs to be created before calculation
|
|
period := time.Date(time.Now().Year(), time.Now().Month()+1, 20, 0, 0, 0, 0, time.UTC)
|
|
|
|
numberOfUsers := 5
|
|
users := make([]*console.User, numberOfUsers)
|
|
projects := make([]*console.Project, numberOfUsers)
|
|
for i := 0; i < numberOfUsers; i++ {
|
|
var err error
|
|
|
|
users[i], err = satellite.AddUser(ctx, console.CreateUser{
|
|
FullName: "testuser" + strconv.Itoa(i),
|
|
Email: "user@test" + strconv.Itoa(i),
|
|
}, 1)
|
|
require.NoError(t, err)
|
|
|
|
projects[i], err = satellite.AddProject(ctx, users[i].ID, "testproject-"+strconv.Itoa(i))
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// all users are members in all projects
|
|
for _, project := range projects {
|
|
for _, user := range users {
|
|
if project.OwnerID != user.ID {
|
|
_, err := satellite.DB.Console().ProjectMembers().Insert(ctx, user.ID, project.ID)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
satellite.API.Payments.Service.SetNow(func() time.Time {
|
|
return time.Date(period.Year(), period.Month()+1, 1, 0, 0, 0, 0, time.UTC)
|
|
})
|
|
err := satellite.API.Payments.Service.PrepareInvoiceProjectRecords(ctx, period)
|
|
require.NoError(t, err)
|
|
|
|
start := time.Date(period.Year(), period.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
end := time.Date(period.Year(), period.Month()+1, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
recordsPage, err := satellite.DB.StripeCoinPayments().ProjectRecords().ListUnapplied(ctx, 0, 40, start, end)
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(projects), len(recordsPage.Records))
|
|
})
|
|
}
|
|
|
|
func TestService_InvoiceItemsFromProjectRecord(t *testing.T) {
|
|
testplanet.Run(t, testplanet.Config{
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
satellite := planet.Satellites[0]
|
|
|
|
// these numbers are fraction of cents, not of dollars.
|
|
expectedStoragePrice := 0.001
|
|
expectedEgressPrice := 0.0045
|
|
expectedObjectPrice := 0.00022
|
|
|
|
type TestCase struct {
|
|
Storage float64
|
|
Egress int64
|
|
Objects float64
|
|
|
|
StorageQuantity int64
|
|
EgressQuantity int64
|
|
ObjectsQuantity int64
|
|
}
|
|
|
|
var testCases = []TestCase{
|
|
{}, // all zeros
|
|
{
|
|
Storage: 10000000000, // Byte-Hours
|
|
// storage quantity is calculated to Megabyte-Months
|
|
// (10000000000 / 1000000) Byte-Hours to Megabytes-Hours
|
|
// round(10000 / 720) Megabytes-Hours to Megabyte-Months, 720 - hours in month
|
|
StorageQuantity: 14, // Megabyte-Months
|
|
},
|
|
{
|
|
Egress: 134 * memory.GB.Int64(), // Bytes
|
|
// egress quantity is calculated to Megabytes
|
|
// (134000000000 / 1000000) Bytes to Megabytes
|
|
EgressQuantity: 134000, // Megabytes
|
|
},
|
|
{
|
|
Objects: 400000, // Object-Hours
|
|
// object quantity is calculated to Object-Months
|
|
// round(400000 / 720) Object-Hours to Object-Months, 720 - hours in month
|
|
ObjectsQuantity: 556, // Object-Months
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
record := stripecoinpayments.ProjectRecord{
|
|
Storage: tc.Storage,
|
|
Egress: tc.Egress,
|
|
Objects: tc.Objects,
|
|
}
|
|
|
|
items := satellite.API.Payments.Service.InvoiceItemsFromProjectRecord("project name", record)
|
|
|
|
require.Equal(t, tc.StorageQuantity, *items[0].Quantity)
|
|
require.Equal(t, expectedStoragePrice, *items[0].UnitAmountDecimal)
|
|
|
|
require.Equal(t, tc.EgressQuantity, *items[1].Quantity)
|
|
require.Equal(t, expectedEgressPrice, *items[1].UnitAmountDecimal)
|
|
|
|
require.Equal(t, tc.ObjectsQuantity, *items[2].Quantity)
|
|
require.Equal(t, expectedObjectPrice, *items[2].UnitAmountDecimal)
|
|
}
|
|
})
|
|
}
|