// Copyright (C) 2019 Storj Labs, Inc. // See LICENSE for copying information. package stripecoinpayments_test import ( "testing" "time" "github.com/stretchr/testify/require" "storj.io/common/memory" "storj.io/common/testcontext" "storj.io/common/testrand" "storj.io/common/uuid" "storj.io/storj/satellite" "storj.io/storj/satellite/console" "storj.io/storj/satellite/payments" "storj.io/storj/satellite/payments/stripecoinpayments" "storj.io/storj/satellite/satellitedb/satellitedbtest" ) func TestCouponRepository(t *testing.T) { satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) { duration := 2 couponsRepo := db.StripeCoinPayments().Coupons() coupon := payments.Coupon{ Duration: &duration, Amount: 10, Status: payments.CouponActive, Description: "description", UserID: testrand.UUID(), } now := time.Now().UTC() t.Run("insert", func(t *testing.T) { _, err := couponsRepo.Insert(ctx, coupon) require.NoError(t, err) coupons, err := couponsRepo.List(ctx, payments.CouponActive) require.NoError(t, err) require.Equal(t, 1, len(coupons)) coupon = coupons[0] }) t.Run("update", func(t *testing.T) { _, err := couponsRepo.Update(ctx, coupon.ID, payments.CouponUsed) require.NoError(t, err) coupons, err := couponsRepo.List(ctx, payments.CouponUsed) require.NoError(t, err) require.Equal(t, payments.CouponUsed, coupons[0].Status) coupon = coupons[0] }) t.Run("get latest on empty table return stripecoinpayments.ErrNoCouponUsages", func(t *testing.T) { _, err := couponsRepo.GetLatest(ctx, coupon.ID) require.Error(t, err) require.Equal(t, true, stripecoinpayments.ErrNoCouponUsages.Has(err)) }) t.Run("total on empty table returns 0", func(t *testing.T) { total, err := couponsRepo.TotalUsage(ctx, coupon.ID) require.NoError(t, err) require.Equal(t, int64(0), total) }) t.Run("add usage", func(t *testing.T) { err := couponsRepo.AddUsage(ctx, stripecoinpayments.CouponUsage{ CouponID: coupon.ID, Amount: 1, Period: now, }) require.NoError(t, err) date, err := couponsRepo.GetLatest(ctx, coupon.ID) require.NoError(t, err) // go and postgres has different precision. go - nanoseconds, postgres micro require.Equal(t, date.UTC(), now.Truncate(time.Microsecond)) }) t.Run("total usage", func(t *testing.T) { amount, err := couponsRepo.TotalUsage(ctx, coupon.ID) require.NoError(t, err) require.Equal(t, amount, int64(1)) }) }) } // TestPopulatePromotionalCoupons is a test for PopulatePromotionalCoupons function // that creates coupons with predefined values for each of user (from arguments) that have a project // and that don't have a valid promotional coupon yet. Also it updates limits of selected projects to 1TB. // Because the coupon should be added to a project, we select the first project of the user. // In this test we have the following test cases: // 1. Activated user, 2 projects, without coupon. For this case we should add new coupon to his first project. // 2. Activated user, 1 project, without coupon. Coupon should be added. // 3. Activated user without project. Coupon should not be added. // 4. User with inactive account. Coupon should not be added. // 5. Activated user with project and coupon. Coupon should not be added. // 6. Activated user with project and expired coupon. Coupon should be added. // 7. Activated user with project and fully consumed, non-expired coupon. Coupon should be added. // 8. Next step - is populating coupons for all 7 users. 4 coupons should be added. // 9. Creating new user with project. // 10. Populating coupons again. For 8 users above. Only 1 new coupon should be added. // Five new coupons total should be added by 2 runs of PopulatePromotionalCoupons method. func TestPopulatePromotionalCoupons(t *testing.T) { satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) { usersRepo := db.Console().Users() projectsRepo := db.Console().Projects() couponsRepo := db.StripeCoinPayments().Coupons() usageRepo := db.ProjectAccounting() // creating test users with different status. // activated user with 2 project. New coupon should be added to the first project. user1, err := usersRepo.Insert(ctx, &console.User{ ID: testrand.UUID(), FullName: "user1", ShortName: "", Email: "test1@example.com", PasswordHash: []byte("123qwe"), }) require.NoError(t, err) user1.Status = console.Active err = usersRepo.Update(ctx, user1) require.NoError(t, err) // activated user with proj. New coupon should be added. user2, err := usersRepo.Insert(ctx, &console.User{ ID: testrand.UUID(), FullName: "user2", ShortName: "", Email: "test2@example.com", PasswordHash: []byte("123qwe"), }) require.NoError(t, err) user2.Status = console.Active err = usersRepo.Update(ctx, user2) require.NoError(t, err) // activated user without proj. New coupon should not be added. user3, err := usersRepo.Insert(ctx, &console.User{ ID: testrand.UUID(), FullName: "user3", ShortName: "", Email: "test3@example.com", PasswordHash: []byte("123qwe"), }) require.NoError(t, err) user3.Status = console.Active err = usersRepo.Update(ctx, user3) require.NoError(t, err) // inactive user. New coupon should not be added. user4, err := usersRepo.Insert(ctx, &console.User{ ID: testrand.UUID(), FullName: "user4", ShortName: "", Email: "test4@example.com", PasswordHash: []byte("123qwe"), }) require.NoError(t, err) // activated user with proj and coupon. New coupon should not be added. user5, err := usersRepo.Insert(ctx, &console.User{ ID: testrand.UUID(), FullName: "user5", ShortName: "", Email: "test5@example.com", PasswordHash: []byte("123qwe"), }) require.NoError(t, err) user5.Status = console.Active err = usersRepo.Update(ctx, user5) require.NoError(t, err) // activated user with proj and expired coupon. New coupon should be added. user6, err := usersRepo.Insert(ctx, &console.User{ ID: testrand.UUID(), FullName: "user6", ShortName: "", Email: "test6@example.com", PasswordHash: []byte("123qwe"), }) require.NoError(t, err) user6.Status = console.Active err = usersRepo.Update(ctx, user6) require.NoError(t, err) // activated user with proj and non-expired but consumed coupon. New coupon should be added. user7, err := usersRepo.Insert(ctx, &console.User{ ID: testrand.UUID(), FullName: "user7", ShortName: "", Email: "test7@example.com", PasswordHash: []byte("123qwe"), }) require.NoError(t, err) user7.Status = console.Active err = usersRepo.Update(ctx, user7) require.NoError(t, err) // creating projects for users above. proj1, err := projectsRepo.Insert(ctx, &console.Project{ ID: testrand.UUID(), Name: "proj 1 of user 1", Description: "descr 1", OwnerID: user1.ID, }) require.NoError(t, err) // should not be processed as we takes only first project of the user. proj2, err := projectsRepo.Insert(ctx, &console.Project{ ID: testrand.UUID(), Name: "proj 2 of user 1", Description: "descr 2", OwnerID: user1.ID, }) require.NoError(t, err) proj3, err := projectsRepo.Insert(ctx, &console.Project{ ID: testrand.UUID(), Name: "proj 1 of user 2", Description: "descr 3", OwnerID: user2.ID, }) require.NoError(t, err) _, err = projectsRepo.Insert(ctx, &console.Project{ ID: testrand.UUID(), Name: "proj 1 of user 5", Description: "descr 4", OwnerID: user5.ID, }) require.NoError(t, err) _, err = projectsRepo.Insert(ctx, &console.Project{ ID: testrand.UUID(), Name: "proj 1 of user 6", Description: "descr 5", OwnerID: user6.ID, }) require.NoError(t, err) _, err = projectsRepo.Insert(ctx, &console.Project{ ID: testrand.UUID(), Name: "proj 1 of user 7", Description: "descr 5", OwnerID: user7.ID, }) require.NoError(t, err) // add coupons to users who should have them before PopulatePromotionalCoupons duration := 2 couponID := testrand.UUID() _, err = couponsRepo.Insert(ctx, payments.Coupon{ ID: couponID, UserID: user5.ID, Amount: 5500, Duration: &duration, Description: "qw", Type: payments.CouponTypePromotional, Status: payments.CouponActive, }) require.NoError(t, err) couponID = testrand.UUID() _, err = couponsRepo.Insert(ctx, payments.Coupon{ ID: couponID, UserID: user6.ID, Amount: 1000, Duration: &duration, Description: "qw", Type: payments.CouponTypePromotional, Status: payments.CouponExpired, }) require.NoError(t, err) couponID = testrand.UUID() _, err = couponsRepo.Insert(ctx, payments.Coupon{ ID: couponID, UserID: user7.ID, Amount: 1000, Duration: nil, Description: "qw", Type: payments.CouponTypePromotional, Status: payments.CouponUsed, }) require.NoError(t, err) // creating new users and projects to test that multiple execution of populate method wont generate extra coupons. user8, err := usersRepo.Insert(ctx, &console.User{ ID: testrand.UUID(), FullName: "user8", ShortName: "", Email: "test8@example.com", PasswordHash: []byte("123qwe"), }) require.NoError(t, err) user8.Status = console.Active err = usersRepo.Update(ctx, user8) require.NoError(t, err) proj4, err := projectsRepo.Insert(ctx, &console.Project{ ID: testrand.UUID(), Name: "proj 1 of user 8", Description: "descr 7", OwnerID: user8.ID, }) if err != nil { require.NoError(t, err) } t.Run("first population", func(t *testing.T) { var usersIds = []uuid.UUID{ user1.ID, user2.ID, user3.ID, user4.ID, user5.ID, user6.ID, user7.ID, } duration := 2 err := couponsRepo.PopulatePromotionalCoupons(ctx, usersIds, &duration, 5500, memory.TB) require.NoError(t, err) user1Coupons, err := couponsRepo.ListByUserID(ctx, user1.ID) require.NoError(t, err) require.Equal(t, 1, len(user1Coupons)) proj1Usage, err := usageRepo.GetProjectStorageLimit(ctx, proj1.ID) require.NoError(t, err) require.Equal(t, memory.TB.Int64(), *proj1Usage) proj2Usage, err := usageRepo.GetProjectStorageLimit(ctx, proj2.ID) require.NoError(t, err) require.Nil(t, proj2Usage) user2Coupons, err := couponsRepo.ListByUserID(ctx, user2.ID) require.NoError(t, err) require.Equal(t, 1, len(user2Coupons)) proj3Usage, err := usageRepo.GetProjectStorageLimit(ctx, proj3.ID) require.NoError(t, err) require.Equal(t, memory.TB.Int64(), *proj3Usage) user3Coupons, err := couponsRepo.ListByUserID(ctx, user3.ID) require.NoError(t, err) require.Equal(t, 0, len(user3Coupons)) user4Coupons, err := couponsRepo.ListByUserID(ctx, user4.ID) require.NoError(t, err) require.Equal(t, 0, len(user4Coupons)) user5Coupons, err := couponsRepo.ListByUserID(ctx, user5.ID) require.NoError(t, err) require.Equal(t, 1, len(user5Coupons)) user6Coupons, err := couponsRepo.ListByUserID(ctx, user6.ID) require.NoError(t, err) require.Equal(t, 2, len(user6Coupons)) user7Coupons, err := couponsRepo.ListByUserID(ctx, user7.ID) require.NoError(t, err) require.Equal(t, 2, len(user7Coupons)) user8Coupons, err := couponsRepo.ListByUserID(ctx, user8.ID) require.NoError(t, err) require.Equal(t, 0, len(user8Coupons)) }) t.Run("second population", func(t *testing.T) { var usersIds = []uuid.UUID{ user1.ID, user2.ID, user3.ID, user4.ID, user5.ID, user6.ID, user7.ID, user8.ID, } duration := 2 err := couponsRepo.PopulatePromotionalCoupons(ctx, usersIds, &duration, 5500, memory.TB) require.NoError(t, err) user1Coupons, err := couponsRepo.ListByUserID(ctx, user1.ID) require.NoError(t, err) require.Equal(t, 1, len(user1Coupons)) proj1Usage, err := usageRepo.GetProjectStorageLimit(ctx, proj1.ID) require.NoError(t, err) require.Equal(t, memory.TB.Int64(), *proj1Usage) proj2Usage, err := usageRepo.GetProjectStorageLimit(ctx, proj2.ID) require.NoError(t, err) require.Nil(t, proj2Usage) user2Coupons, err := couponsRepo.ListByUserID(ctx, user2.ID) require.NoError(t, err) require.Equal(t, 1, len(user2Coupons)) proj3Usage, err := usageRepo.GetProjectStorageLimit(ctx, proj3.ID) require.NoError(t, err) require.Equal(t, memory.TB.Int64(), *proj3Usage) user3Coupons, err := couponsRepo.ListByUserID(ctx, user3.ID) require.NoError(t, err) require.Equal(t, 0, len(user3Coupons)) user4Coupons, err := couponsRepo.ListByUserID(ctx, user4.ID) require.NoError(t, err) require.Equal(t, 0, len(user4Coupons)) user5Coupons, err := couponsRepo.ListByUserID(ctx, user5.ID) require.NoError(t, err) require.Equal(t, 1, len(user5Coupons)) require.Equal(t, "qw", user5Coupons[0].Description) user6Coupons, err := couponsRepo.ListByUserID(ctx, user6.ID) require.NoError(t, err) require.Equal(t, 2, len(user6Coupons)) user7Coupons, err := couponsRepo.ListByUserID(ctx, user7.ID) require.NoError(t, err) require.Equal(t, 2, len(user7Coupons)) user8Coupons, err := couponsRepo.ListByUserID(ctx, user8.ID) require.NoError(t, err) require.Equal(t, 1, len(user8Coupons)) proj4Usage, err := usageRepo.GetProjectStorageLimit(ctx, proj4.ID) require.NoError(t, err) require.Equal(t, memory.TB.Int64(), *proj4Usage) }) }) }