package main import ( "context" "fmt" "time" "github.com/google/uuid" "github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/model/order" "github.com/perfect-panel/server/internal/model/subscribe" "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/pkg/orm" "github.com/perfect-panel/server/pkg/tool" orderLogic "github.com/perfect-panel/server/queue/logic/order" "github.com/redis/go-redis/v9" ) func main() { // 1. Setup Configuration c := config.Config{ MySQL: orm.Config{ Addr: "127.0.0.1:3306", Dbname: "dev_ppanel", // Using dev_ppanel as default, change if needed Username: "root", Password: "rootpassword", Config: "charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai", MaxIdleConns: 10, MaxOpenConns: 10, }, Redis: config.RedisConfig{ Host: "127.0.0.1:6379", DB: 0, }, Invite: config.InviteConfig{ GiftDays: 3, // Default gift days }, } // 2. Connect to Database & Redis db, err := orm.ConnectMysql(orm.Mysql{Config: c.MySQL}) if err != nil { panic(fmt.Sprintf("DB Connection failed: %v", err)) } rds := redis.NewClient(&redis.Options{ Addr: c.Redis.Host, DB: c.Redis.DB, }) // 3. Initialize ServiceContext serviceCtx := svc.NewServiceContext(c) serviceCtx.DB = db serviceCtx.Redis = rds // We don't need queue/scheduler for this unit test ctx := context.Background() // 4. Run Scenarios fmt.Println("=== Starting Invite Reward Test ===") // Scenario 1: Commission 0 (Expect Gift Days) runScenario(ctx, serviceCtx, "Scenario_0_Commission", 0) // Scenario 2: Commission 10 (Expect Money) runScenario(ctx, serviceCtx, "Scenario_10_Commission", 10) } func runScenario(ctx context.Context, s *svc.ServiceContext, name string, referralPercentage int64) { fmt.Printf("\n--- Running %s (ReferralPercentage: %d%%) ---\n", name, referralPercentage) // Update Config s.Config.Invite.ReferralPercentage = referralPercentage // Cleanup old data (Partial cleanup since we don't have email to query) // We'll rely on unique ReferCode / UUIDs to avoid collisions but DB might grow. // Actually we should try to clean up. // Since we removed Email from struct, we can't use it to query easily unless we check `auth_methods`. // For this test, let's just create new users. // Create Referrer referrer := &user.User{ Password: tool.EncodePassWord("123456"), ReferCode: fmt.Sprintf("REF%d", time.Now().UnixNano())[:20], ReferralPercentage: 0, // Use global settings Commission: 0, } // Use DB directly to ensure ID is updated in struct if err := s.DB.Create(referrer).Error; err != nil { fmt.Printf("Create Referrer Failed: %v\n", err) return } // Force active subscription for referrer so they can receive gift time createActiveSubscription(ctx, s, referrer.Id) fmt.Printf("Created Referrer: ID=%d, Commission=%d\n", referrer.Id, referrer.Commission) // Create User (Invitee) invitee := &user.User{ Password: tool.EncodePassWord("123456"), RefererId: referrer.Id, } if err := s.DB.Create(invitee).Error; err != nil { fmt.Printf("Create Invitee Failed: %v\n", err) return } // Force active subscription for invitee to receive gift time _ = createActiveSubscription(ctx, s, invitee.Id) fmt.Printf("Created Invitee: ID=%d, RefererID=%d\n", invitee.Id, invitee.RefererId) // Create Order orderInfo := &order.Order{ OrderNo: tool.GenerateTradeNo(), UserId: invitee.Id, Amount: 10000, // 100.00 Price: 10000, FeeAmount: 0, Status: 2, // Paid Type: 1, // Subscribe IsNew: true, SubscribeId: 1, // Assume plan 1 exists Quantity: 1, } // We need a dummy subscribe plan in DB or use existing ensureSubscribePlan(ctx, s, 1) // Execute Logic logic := orderLogic.NewActivateOrderLogic(s) // We only simulate the commission part logic or NewPurchase // logic.NewPurchase does a lot of things. // Let's call NewPurchase to be realistic, but we need to ensure dependencies exist. // Instead of full NewPurchase which might fail on other things, // let's verify if we can just call handleCommission? No it's private. // So we call NewPurchase. err := logic.NewPurchase(ctx, orderInfo) if err != nil { fmt.Printf("NewPurchase failed (expected for mocked env): %v\n", err) // If it failed because of things we don't care (like sending email), check data anyway } else { fmt.Println("NewPurchase executed successfully.") } // Wait for async goroutines time.Sleep(2 * time.Second) // Check Results // 1. Check Referrer Commission refRes, _ := s.UserModel.FindOne(ctx, referrer.Id) fmt.Printf("Result Referrer Commission: %d (Expected: %d)\n", refRes.Commission, int64(float64(orderInfo.Amount)*float64(referralPercentage)/100)) // 2. Check Gift Days (Check expiration time changes) // We compare with the initial subscription time // But since we just created it, it's simpler to check if 'ExpiryTime' is far in the future or extended. // For 0 commission, we expect gift days. refSub, _ := s.UserModel.FindActiveSubscribe(ctx, referrer.Id) invSub, _ := s.UserModel.FindActiveSubscribe(ctx, invitee.Id) // Avoid panic if sub not found if refSub != nil { fmt.Printf("Result Referrer Sub Expire: %v\n", refSub.ExpireTime) } else { fmt.Println("Result Referrer Sub Expire: nil") } if invSub != nil { // NewPurchase renews/creates sub, so it should be valid + duration fmt.Printf("Result Invitee Sub Expire: %v\n", invSub.ExpireTime) } else { fmt.Println("Result Invitee Sub Expire: nil") } } func createActiveSubscription(ctx context.Context, s *svc.ServiceContext, userId int64) *user.Subscribe { sub := &user.Subscribe{ UserId: userId, Status: 1, ExpireTime: time.Now().Add(30 * 24 * time.Hour), // 30 days initial Token: uuid.New().String(), UUID: uuid.New().String(), } s.UserModel.InsertSubscribe(ctx, sub) return sub } func ensureSubscribePlan(ctx context.Context, s *svc.ServiceContext, id int64) { _, err := s.SubscribeModel.FindOne(ctx, id) if err != nil { s.SubscribeModel.Insert(ctx, &subscribe.Subscribe{ Id: id, Name: "Test Plan", UnitTime: "Day", // Days UnitPrice: 100, Sell: &[]bool{true}[0], }) } }