2019-06-18 16:55:47 +01:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
2019-08-26 11:19:02 +01:00
|
|
|
package console_test
|
2019-06-18 16:55:47 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2019-07-01 20:16:49 +01:00
|
|
|
"storj.io/storj/internal/currency"
|
2019-06-18 16:55:47 +01:00
|
|
|
"storj.io/storj/internal/testcontext"
|
2019-06-26 11:38:51 +01:00
|
|
|
"storj.io/storj/internal/testrand"
|
2019-06-18 16:55:47 +01:00
|
|
|
"storj.io/storj/satellite"
|
2019-06-24 21:51:54 +01:00
|
|
|
"storj.io/storj/satellite/console"
|
|
|
|
"storj.io/storj/satellite/rewards"
|
2019-06-18 16:55:47 +01:00
|
|
|
"storj.io/storj/satellite/satellitedb/satellitedbtest"
|
|
|
|
)
|
|
|
|
|
2019-08-26 11:19:02 +01:00
|
|
|
func TestUserCredits(t *testing.T) {
|
2019-06-18 16:55:47 +01:00
|
|
|
satellitedbtest.Run(t, func(t *testing.T, db satellite.DB) {
|
|
|
|
ctx := testcontext.New(t)
|
|
|
|
defer ctx.Cleanup()
|
|
|
|
|
|
|
|
consoleDB := db.Console()
|
|
|
|
|
2019-07-16 00:30:00 +01:00
|
|
|
user, referrer, activeOffer, defaultOffer := setupData(ctx, t, db)
|
2019-06-26 11:38:51 +01:00
|
|
|
randomID := testrand.UUID()
|
2019-08-14 20:53:48 +01:00
|
|
|
invalidOffer := rewards.Offer{
|
|
|
|
ID: 10,
|
|
|
|
}
|
2019-06-18 16:55:47 +01:00
|
|
|
|
|
|
|
// test foreign key constraint for inserting a new user credit entry with randomID
|
2019-08-14 20:53:48 +01:00
|
|
|
var invalidUserCredits []console.CreateCredit
|
|
|
|
invalid1, err := console.NewCredit(activeOffer, console.Invitee, randomID, &referrer.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
invalid2, err := console.NewCredit(&invalidOffer, console.Invitee, user.ID, &referrer.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
invalid3, err := console.NewCredit(activeOffer, console.Invitee, randomID, &randomID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
invalidUserCredits = append(invalidUserCredits, *invalid1, *invalid2, *invalid3)
|
2019-06-18 16:55:47 +01:00
|
|
|
|
|
|
|
for _, ivc := range invalidUserCredits {
|
2019-07-16 00:30:00 +01:00
|
|
|
err := consoleDB.UserCredits().Create(ctx, ivc)
|
2019-06-18 16:55:47 +01:00
|
|
|
require.Error(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
type result struct {
|
2019-06-19 21:49:04 +01:00
|
|
|
remainingCharge int
|
|
|
|
usage console.UserCreditUsage
|
2019-07-16 00:30:00 +01:00
|
|
|
referred int64
|
|
|
|
hasUpdateErr bool
|
|
|
|
hasCreateErr bool
|
2019-06-18 16:55:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var validUserCredits = []struct {
|
2019-08-14 20:53:48 +01:00
|
|
|
userCredit console.CreateCredit
|
2019-06-18 16:55:47 +01:00
|
|
|
chargedCredits int
|
|
|
|
expected result
|
|
|
|
}{
|
|
|
|
{
|
2019-08-14 20:53:48 +01:00
|
|
|
userCredit: console.CreateCredit{
|
|
|
|
OfferInfo: rewards.RedeemOffer{
|
|
|
|
RedeemableCap: activeOffer.RedeemableCap,
|
|
|
|
Status: activeOffer.Status,
|
|
|
|
Type: activeOffer.Type,
|
|
|
|
},
|
2019-07-01 20:16:49 +01:00
|
|
|
UserID: user.ID,
|
2019-07-16 00:30:00 +01:00
|
|
|
OfferID: activeOffer.ID,
|
|
|
|
ReferredBy: &referrer.ID,
|
2019-08-01 18:46:33 +01:00
|
|
|
Type: console.Invitee,
|
2019-07-01 20:16:49 +01:00
|
|
|
CreditsEarned: currency.Cents(100),
|
|
|
|
ExpiresAt: time.Now().UTC().AddDate(0, 1, 0),
|
2019-06-18 16:55:47 +01:00
|
|
|
},
|
|
|
|
chargedCredits: 120,
|
|
|
|
expected: result{
|
2019-06-19 21:49:04 +01:00
|
|
|
remainingCharge: 20,
|
|
|
|
usage: console.UserCreditUsage{
|
2019-07-01 20:16:49 +01:00
|
|
|
AvailableCredits: currency.Cents(0),
|
|
|
|
UsedCredits: currency.Cents(100),
|
2019-06-19 21:49:04 +01:00
|
|
|
Referred: 0,
|
|
|
|
},
|
2019-08-01 18:46:33 +01:00
|
|
|
referred: 0,
|
2019-06-18 16:55:47 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// simulate a credit that's already expired
|
2019-08-14 20:53:48 +01:00
|
|
|
userCredit: console.CreateCredit{
|
|
|
|
OfferInfo: rewards.RedeemOffer{
|
|
|
|
RedeemableCap: activeOffer.RedeemableCap,
|
|
|
|
Status: activeOffer.Status,
|
|
|
|
Type: activeOffer.Type,
|
|
|
|
},
|
2019-07-01 20:16:49 +01:00
|
|
|
UserID: user.ID,
|
2019-07-16 00:30:00 +01:00
|
|
|
OfferID: activeOffer.ID,
|
|
|
|
ReferredBy: &referrer.ID,
|
2019-08-01 18:46:33 +01:00
|
|
|
Type: console.Invitee,
|
2019-07-01 20:16:49 +01:00
|
|
|
CreditsEarned: currency.Cents(100),
|
|
|
|
ExpiresAt: time.Now().UTC().AddDate(0, 0, -5),
|
2019-06-18 16:55:47 +01:00
|
|
|
},
|
|
|
|
chargedCredits: 60,
|
|
|
|
expected: result{
|
2019-06-19 21:49:04 +01:00
|
|
|
remainingCharge: 60,
|
|
|
|
usage: console.UserCreditUsage{
|
2019-07-01 20:16:49 +01:00
|
|
|
AvailableCredits: currency.Cents(0),
|
|
|
|
UsedCredits: currency.Cents(100),
|
2019-06-19 21:49:04 +01:00
|
|
|
Referred: 0,
|
|
|
|
},
|
2019-08-01 18:46:33 +01:00
|
|
|
referred: 0,
|
2019-07-16 00:30:00 +01:00
|
|
|
hasCreateErr: true,
|
|
|
|
hasUpdateErr: true,
|
2019-06-18 16:55:47 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// simulate a credit that's not expired
|
2019-08-14 20:53:48 +01:00
|
|
|
userCredit: console.CreateCredit{
|
|
|
|
OfferInfo: rewards.RedeemOffer{
|
|
|
|
RedeemableCap: activeOffer.RedeemableCap,
|
|
|
|
Status: activeOffer.Status,
|
|
|
|
Type: activeOffer.Type,
|
|
|
|
},
|
2019-07-01 20:16:49 +01:00
|
|
|
UserID: user.ID,
|
2019-07-16 00:30:00 +01:00
|
|
|
OfferID: activeOffer.ID,
|
|
|
|
ReferredBy: &referrer.ID,
|
2019-08-01 18:46:33 +01:00
|
|
|
Type: console.Invitee,
|
2019-07-01 20:16:49 +01:00
|
|
|
CreditsEarned: currency.Cents(100),
|
|
|
|
ExpiresAt: time.Now().UTC().AddDate(0, 0, 5),
|
2019-06-18 16:55:47 +01:00
|
|
|
},
|
|
|
|
chargedCredits: 80,
|
|
|
|
expected: result{
|
2019-06-19 21:49:04 +01:00
|
|
|
remainingCharge: 0,
|
|
|
|
usage: console.UserCreditUsage{
|
2019-07-01 20:16:49 +01:00
|
|
|
AvailableCredits: currency.Cents(20),
|
|
|
|
UsedCredits: currency.Cents(180),
|
2019-06-19 21:49:04 +01:00
|
|
|
Referred: 0,
|
|
|
|
},
|
2019-08-01 18:46:33 +01:00
|
|
|
referred: 0,
|
2019-07-16 00:30:00 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// simulate redeemable capacity has been reached for active offers
|
2019-08-14 20:53:48 +01:00
|
|
|
userCredit: console.CreateCredit{
|
|
|
|
OfferInfo: rewards.RedeemOffer{
|
|
|
|
RedeemableCap: 1,
|
|
|
|
Status: activeOffer.Status,
|
|
|
|
Type: activeOffer.Type,
|
|
|
|
},
|
2019-07-16 00:30:00 +01:00
|
|
|
UserID: user.ID,
|
|
|
|
OfferID: activeOffer.ID,
|
|
|
|
ReferredBy: &randomID,
|
2019-08-01 18:46:33 +01:00
|
|
|
Type: console.Invitee,
|
2019-07-16 00:30:00 +01:00
|
|
|
CreditsEarned: currency.Cents(100),
|
|
|
|
ExpiresAt: time.Now().UTC().AddDate(0, 1, 0),
|
|
|
|
},
|
|
|
|
expected: result{
|
|
|
|
usage: console.UserCreditUsage{
|
|
|
|
Referred: 0,
|
|
|
|
AvailableCredits: currency.Cents(20),
|
|
|
|
UsedCredits: currency.Cents(180),
|
|
|
|
},
|
2019-08-01 18:46:33 +01:00
|
|
|
referred: 0,
|
2019-07-16 00:30:00 +01:00
|
|
|
hasCreateErr: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// simulate redeemable capacity has been reached for default offers
|
2019-08-14 20:53:48 +01:00
|
|
|
userCredit: console.CreateCredit{
|
|
|
|
OfferInfo: rewards.RedeemOffer{
|
|
|
|
RedeemableCap: defaultOffer.RedeemableCap,
|
|
|
|
Status: defaultOffer.Status,
|
|
|
|
Type: defaultOffer.Type,
|
|
|
|
},
|
2019-07-16 00:30:00 +01:00
|
|
|
UserID: user.ID,
|
|
|
|
OfferID: defaultOffer.ID,
|
|
|
|
ReferredBy: nil,
|
2019-08-01 18:46:33 +01:00
|
|
|
Type: console.Invitee,
|
2019-07-16 00:30:00 +01:00
|
|
|
CreditsEarned: currency.Cents(100),
|
|
|
|
ExpiresAt: time.Now().UTC().AddDate(0, 1, 0),
|
|
|
|
},
|
|
|
|
expected: result{
|
|
|
|
usage: console.UserCreditUsage{
|
|
|
|
Referred: 0,
|
|
|
|
AvailableCredits: currency.Cents(120),
|
|
|
|
UsedCredits: currency.Cents(180),
|
|
|
|
},
|
2019-08-01 18:46:33 +01:00
|
|
|
referred: 0,
|
2019-07-16 00:30:00 +01:00
|
|
|
hasCreateErr: false,
|
2019-06-18 16:55:47 +01:00
|
|
|
},
|
|
|
|
},
|
2019-07-30 14:21:00 +01:00
|
|
|
{
|
|
|
|
// simulate credit on account creation
|
2019-08-14 20:53:48 +01:00
|
|
|
userCredit: console.CreateCredit{
|
|
|
|
OfferInfo: rewards.RedeemOffer{
|
|
|
|
RedeemableCap: defaultOffer.RedeemableCap,
|
|
|
|
Status: defaultOffer.Status,
|
|
|
|
Type: defaultOffer.Type,
|
|
|
|
},
|
2019-07-30 14:21:00 +01:00
|
|
|
UserID: user.ID,
|
|
|
|
OfferID: defaultOffer.ID,
|
|
|
|
ReferredBy: &referrer.ID,
|
2019-08-01 18:46:33 +01:00
|
|
|
Type: console.Invitee,
|
2019-07-30 14:21:00 +01:00
|
|
|
CreditsEarned: currency.Cents(0),
|
|
|
|
ExpiresAt: time.Now().UTC().AddDate(0, 1, 0),
|
|
|
|
},
|
|
|
|
expected: result{
|
|
|
|
usage: console.UserCreditUsage{
|
|
|
|
Referred: 0,
|
|
|
|
AvailableCredits: currency.Cents(220),
|
|
|
|
UsedCredits: currency.Cents(180),
|
|
|
|
},
|
2019-08-01 18:46:33 +01:00
|
|
|
referred: 0,
|
|
|
|
hasCreateErr: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// simulate credit redemption for referrer
|
2019-08-14 20:53:48 +01:00
|
|
|
userCredit: console.CreateCredit{
|
|
|
|
OfferInfo: rewards.RedeemOffer{
|
|
|
|
RedeemableCap: activeOffer.RedeemableCap,
|
|
|
|
Status: activeOffer.Status,
|
|
|
|
Type: activeOffer.Type,
|
|
|
|
},
|
2019-08-01 18:46:33 +01:00
|
|
|
UserID: referrer.ID,
|
|
|
|
OfferID: activeOffer.ID,
|
|
|
|
ReferredBy: nil,
|
|
|
|
Type: console.Referrer,
|
|
|
|
CreditsEarned: activeOffer.AwardCredit,
|
|
|
|
ExpiresAt: time.Now().UTC().AddDate(0, 0, activeOffer.AwardCreditDurationDays),
|
|
|
|
},
|
|
|
|
expected: result{
|
|
|
|
usage: console.UserCreditUsage{
|
|
|
|
Referred: 1,
|
|
|
|
AvailableCredits: activeOffer.AwardCredit,
|
|
|
|
UsedCredits: currency.Cents(0),
|
|
|
|
},
|
|
|
|
referred: 1,
|
2019-07-30 14:21:00 +01:00
|
|
|
hasCreateErr: false,
|
|
|
|
},
|
|
|
|
},
|
2019-06-18 16:55:47 +01:00
|
|
|
}
|
|
|
|
|
2019-07-16 00:30:00 +01:00
|
|
|
for _, vc := range validUserCredits {
|
|
|
|
err := consoleDB.UserCredits().Create(ctx, vc.userCredit)
|
|
|
|
if vc.expected.hasCreateErr {
|
|
|
|
require.Error(t, err)
|
|
|
|
} else {
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
2019-06-18 16:55:47 +01:00
|
|
|
|
2019-07-30 14:21:00 +01:00
|
|
|
if vc.userCredit.CreditsEarned.Cents() == 0 {
|
|
|
|
err = consoleDB.UserCredits().UpdateEarnedCredits(ctx, vc.userCredit.UserID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
2019-06-18 16:55:47 +01:00
|
|
|
{
|
|
|
|
remainingCharge, err := consoleDB.UserCredits().UpdateAvailableCredits(ctx, vc.chargedCredits, vc.userCredit.UserID, time.Now().UTC())
|
2019-07-16 00:30:00 +01:00
|
|
|
if vc.expected.hasUpdateErr {
|
2019-06-18 16:55:47 +01:00
|
|
|
require.Error(t, err)
|
|
|
|
} else {
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
require.Equal(t, vc.expected.remainingCharge, remainingCharge)
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2019-06-19 21:49:04 +01:00
|
|
|
usage, err := consoleDB.UserCredits().GetCreditUsage(ctx, vc.userCredit.UserID, time.Now().UTC())
|
2019-06-18 16:55:47 +01:00
|
|
|
require.NoError(t, err)
|
2019-06-19 21:49:04 +01:00
|
|
|
require.Equal(t, vc.expected.usage, *usage)
|
|
|
|
}
|
2019-06-18 16:55:47 +01:00
|
|
|
|
2019-06-19 21:49:04 +01:00
|
|
|
{
|
|
|
|
referred, err := consoleDB.UserCredits().GetCreditUsage(ctx, referrer.ID, time.Now().UTC())
|
2019-06-18 16:55:47 +01:00
|
|
|
require.NoError(t, err)
|
2019-07-16 00:30:00 +01:00
|
|
|
require.Equal(t, vc.expected.referred, referred.Referred)
|
2019-06-18 16:55:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-07-16 00:30:00 +01:00
|
|
|
func setupData(ctx context.Context, t *testing.T, db satellite.DB) (user *console.User, referrer *console.User, activeOffer *rewards.Offer, defaultOffer *rewards.Offer) {
|
2019-06-18 16:55:47 +01:00
|
|
|
consoleDB := db.Console()
|
2019-06-24 21:51:54 +01:00
|
|
|
offersDB := db.Rewards()
|
2019-06-26 11:38:51 +01:00
|
|
|
|
2019-06-18 16:55:47 +01:00
|
|
|
// create user
|
2019-06-26 11:38:51 +01:00
|
|
|
userPassHash := testrand.Bytes(8)
|
|
|
|
referrerPassHash := testrand.Bytes(8)
|
2019-06-18 16:55:47 +01:00
|
|
|
|
2019-06-26 11:38:51 +01:00
|
|
|
var err error
|
2019-06-18 16:55:47 +01:00
|
|
|
|
|
|
|
// create an user
|
|
|
|
user, err = consoleDB.Users().Insert(ctx, &console.User{
|
|
|
|
FullName: "John Doe",
|
|
|
|
Email: "john@mail.test",
|
2019-06-26 11:38:51 +01:00
|
|
|
PasswordHash: userPassHash,
|
2019-06-18 16:55:47 +01:00
|
|
|
Status: console.Active,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
//create an user as referrer
|
|
|
|
referrer, err = consoleDB.Users().Insert(ctx, &console.User{
|
|
|
|
FullName: "referrer",
|
|
|
|
Email: "referrer@mail.test",
|
2019-06-26 11:38:51 +01:00
|
|
|
PasswordHash: referrerPassHash,
|
2019-06-18 16:55:47 +01:00
|
|
|
Status: console.Active,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2019-07-16 00:30:00 +01:00
|
|
|
// create an active offer
|
|
|
|
activeOffer, err = offersDB.Create(ctx, &rewards.NewOffer{
|
|
|
|
Name: "active",
|
|
|
|
Description: "active offer",
|
2019-07-01 20:16:49 +01:00
|
|
|
AwardCredit: currency.Cents(100),
|
|
|
|
InviteeCredit: currency.Cents(50),
|
2019-06-18 16:55:47 +01:00
|
|
|
AwardCreditDurationDays: 60,
|
|
|
|
InviteeCreditDurationDays: 30,
|
|
|
|
RedeemableCap: 50,
|
|
|
|
ExpiresAt: time.Now().UTC().Add(time.Hour * 1),
|
2019-06-24 21:51:54 +01:00
|
|
|
Status: rewards.Active,
|
|
|
|
Type: rewards.Referral,
|
2019-06-18 16:55:47 +01:00
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2019-07-16 00:30:00 +01:00
|
|
|
// create a default offer
|
|
|
|
defaultOffer, err = offersDB.Create(ctx, &rewards.NewOffer{
|
|
|
|
Name: "default",
|
|
|
|
Description: "default offer",
|
2019-07-30 14:21:00 +01:00
|
|
|
AwardCredit: currency.Cents(0),
|
|
|
|
InviteeCredit: currency.Cents(100),
|
|
|
|
AwardCreditDurationDays: 0,
|
|
|
|
InviteeCreditDurationDays: 14,
|
2019-07-16 00:30:00 +01:00
|
|
|
RedeemableCap: 0,
|
|
|
|
ExpiresAt: time.Now().UTC().Add(time.Hour * 1),
|
|
|
|
Status: rewards.Default,
|
|
|
|
Type: rewards.FreeCredit,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return user, referrer, activeOffer, defaultOffer
|
2019-06-18 16:55:47 +01:00
|
|
|
}
|