package reward import ( "context" "database/sql" "testing" "time" "github.com/wangjia/pangolin/server/internal/codes" ) func newSvc(t *testing.T, db *sql.DB) *Service { g := codes.NewService(codes.NewStore(db), nil, 5, time.Hour) return NewService(db, NewStore(db), g, Config{RegDays: 3, PaidDays: 7, TgDays: 3, RegMonthlyCap: 10}, time.Now) } func proDays(t *testing.T, db *sql.DB, userID int64) int { // 该用户 pro 剩余天数(粗算) var exp sql.NullTime db.QueryRow(`SELECT expires_at FROM subscriptions s JOIN plans p ON p.id=s.plan_id WHERE s.user_id=? AND p.code='pro' ORDER BY expires_at DESC LIMIT 1`, userID).Scan(&exp) if !exp.Valid { return 0 } return int(time.Until(exp.Time).Hours()/24) + 1 } func TestOnRegister_BothGetThreeDays(t *testing.T) { db := openDB(t) seedU(t, db, 1, "inviter") seedU(t, db, 2, "invitee") s := newSvc(t, db) code, _ := s.EnsureCode(context.Background(), 1) s.OnRegister(context.Background(), 2, code, "dev-2") var status string db.QueryRow(`SELECT status FROM referrals WHERE invitee_id=2`).Scan(&status) if status != "reg_rewarded" { t.Fatalf("status=%q", status) } if d := proDays(t, db, 1); d < 3 { t.Fatalf("inviter pro days=%d, want>=3", d) } if d := proDays(t, db, 2); d < 3 { t.Fatalf("invitee pro days=%d, want>=3", d) } } func TestOnRegister_SelfInviteRejected(t *testing.T) { db := openDB(t) seedU(t, db, 1, "u1") s := newSvc(t, db) code, _ := s.EnsureCode(context.Background(), 1) s.OnRegister(context.Background(), 1, code, "dev-1") // 自邀请 var n int db.QueryRow(`SELECT COUNT(*) FROM referrals`).Scan(&n) if n != 0 { t.Fatalf("self-invite created %d referrals, want 0", n) } } func TestOnRegister_DeviceReusedRejectsReward(t *testing.T) { db := openDB(t) seedU(t, db, 1, "inv") seedU(t, db, 2, "old") seedU(t, db, 3, "new") // old(2) 的设备 dev-X 已在 devices db.Exec(`INSERT INTO devices (uuid,user_id,name,platform,last_seen,created_at) VALUES ('dev-X',2,'n','android',?,?)`, time.Now().UTC(), time.Now().UTC()) s := newSvc(t, db) code, _ := s.EnsureCode(context.Background(), 1) s.OnRegister(context.Background(), 3, code, "dev-X") // 新用户复用老设备 var status string db.QueryRow(`SELECT status FROM referrals WHERE invitee_id=3`).Scan(&status) if status != "rejected" { t.Fatalf("status=%q, want rejected", status) } if d := proDays(t, db, 1); d >= 3 { t.Fatalf("inviter got reward despite device reuse: %d", d) } }