init
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
package economy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"hakemsho/internal/store"
|
||||
)
|
||||
|
||||
func setup(t *testing.T) (*Service, int64) {
|
||||
t.Helper()
|
||||
dbPath := filepath.Join(t.TempDir(), "test.db")
|
||||
st, err := store.Open(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { st.Close() })
|
||||
|
||||
res, err := st.DB.Exec(`INSERT INTO users (mobile) VALUES ('09120000000')`)
|
||||
if err != nil {
|
||||
t.Fatalf("insert user: %v", err)
|
||||
}
|
||||
id, _ := res.LastInsertId()
|
||||
svc := New(st.DB, DevAdVerifier{}, DevIAPVerifier{})
|
||||
return svc, id
|
||||
}
|
||||
|
||||
func setCoins(t *testing.T, s *Service, userID, coins int64) {
|
||||
t.Helper()
|
||||
if _, err := s.db.Exec(`UPDATE users SET coins = ? WHERE id = ?`, coins, userID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func coinsOf(t *testing.T, s *Service, userID int64) int64 {
|
||||
t.Helper()
|
||||
p, err := s.GetProfile(context.Background(), userID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return p.Coins
|
||||
}
|
||||
|
||||
func TestLevelInfo(t *testing.T) {
|
||||
cases := []struct {
|
||||
xp int64
|
||||
level int
|
||||
into int64
|
||||
need int64
|
||||
}{
|
||||
{0, 1, 0, 50},
|
||||
{49, 1, 49, 50},
|
||||
{50, 2, 0, 100},
|
||||
{150, 3, 0, 150},
|
||||
}
|
||||
for _, c := range cases {
|
||||
lvl, into, need := LevelInfo(c.xp)
|
||||
if lvl != c.level || into != c.into || need != c.need {
|
||||
t.Errorf("LevelInfo(%d) = (%d,%d,%d), want (%d,%d,%d)",
|
||||
c.xp, lvl, into, need, c.level, c.into, c.need)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalletInsufficient(t *testing.T) {
|
||||
svc, id := setup(t)
|
||||
ctx := context.Background()
|
||||
setCoins(t, svc, id, 100)
|
||||
if err := svc.Adjust(ctx, id, CurrencyCoin, -200, "test", ""); err != ErrInsufficient {
|
||||
t.Fatalf("want ErrInsufficient, got %v", err)
|
||||
}
|
||||
if c := coinsOf(t, svc, id); c != 100 {
|
||||
t.Fatalf("balance changed on failed debit: %d", c)
|
||||
}
|
||||
if err := svc.Adjust(ctx, id, CurrencyCoin, -60, "test", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := coinsOf(t, svc, id); c != 40 {
|
||||
t.Fatalf("want 40, got %d", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDailyCooldown(t *testing.T) {
|
||||
svc, id := setup(t)
|
||||
ctx := context.Background()
|
||||
if amt, err := svc.ClaimDaily(ctx, id); err != nil || amt != DailyReward {
|
||||
t.Fatalf("first claim: amt=%d err=%v", amt, err)
|
||||
}
|
||||
if _, err := svc.ClaimDaily(ctx, id); err != ErrTooSoon {
|
||||
t.Fatalf("want ErrTooSoon, got %v", err)
|
||||
}
|
||||
if c := coinsOf(t, svc, id); c != DailyReward {
|
||||
t.Fatalf("want %d coins, got %d", DailyReward, c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdRewardDedupeAndCredit(t *testing.T) {
|
||||
svc, id := setup(t)
|
||||
ctx := context.Background()
|
||||
if amt, err := svc.ClaimAdReward(ctx, id, "tok-1"); err != nil || amt != AdReward {
|
||||
t.Fatalf("claim: amt=%d err=%v", amt, err)
|
||||
}
|
||||
// همان توکن دوباره ⇒ تکراری
|
||||
if _, err := svc.ClaimAdReward(ctx, id, "tok-1"); err != ErrAdDuplicate {
|
||||
t.Fatalf("want ErrAdDuplicate, got %v", err)
|
||||
}
|
||||
// توکن خالی ⇒ تأیید نشده
|
||||
if _, err := svc.ClaimAdReward(ctx, id, ""); err != ErrAdNotVerified {
|
||||
t.Fatalf("want ErrAdNotVerified, got %v", err)
|
||||
}
|
||||
if c := coinsOf(t, svc, id); c != AdReward {
|
||||
t.Fatalf("want %d, got %d", AdReward, c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuyAndSelectCard(t *testing.T) {
|
||||
svc, id := setup(t)
|
||||
ctx := context.Background()
|
||||
setCoins(t, svc, id, 10000)
|
||||
|
||||
// انتخاب کارتی که مالکش نیست ⇒ خطا
|
||||
if err := svc.SelectCard(ctx, id, "wizard"); err != ErrNotOwned {
|
||||
t.Fatalf("want ErrNotOwned, got %v", err)
|
||||
}
|
||||
// خرید کارت جادوگر (۱۰۰۰۰)
|
||||
if err := svc.BuyCard(ctx, id, "wizard"); err != nil {
|
||||
t.Fatalf("buy: %v", err)
|
||||
}
|
||||
if c := coinsOf(t, svc, id); c != 0 {
|
||||
t.Fatalf("want 0 after buy, got %d", c)
|
||||
}
|
||||
// خرید دوباره ⇒ قبلاً مالک است
|
||||
if err := svc.BuyCard(ctx, id, "wizard"); err != ErrAlreadyOwned {
|
||||
t.Fatalf("want ErrAlreadyOwned, got %v", err)
|
||||
}
|
||||
// حالا قابل انتخاب است
|
||||
if err := svc.SelectCard(ctx, id, "wizard"); err != nil {
|
||||
t.Fatalf("select: %v", err)
|
||||
}
|
||||
p, _ := svc.GetProfile(ctx, id)
|
||||
if p.SelectedCard != "wizard" {
|
||||
t.Fatalf("selected = %s", p.SelectedCard)
|
||||
}
|
||||
// خرید بدون سکه کافی ⇒ insufficient
|
||||
if err := svc.BuyCard(ctx, id, "epic"); err != ErrInsufficient {
|
||||
t.Fatalf("want ErrInsufficient, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPurchaseCreditsAndIdempotent(t *testing.T) {
|
||||
svc, id := setup(t)
|
||||
ctx := context.Background()
|
||||
req := PurchaseRequest{Store: "bazaar", Kind: "coin", ProductID: "small", Token: "pt-1"}
|
||||
|
||||
if err := svc.VerifyPurchase(ctx, id, req); err != nil {
|
||||
t.Fatalf("purchase: %v", err)
|
||||
}
|
||||
pkg := findCoinPackage("small")
|
||||
if c := coinsOf(t, svc, id); c != pkg.Coins {
|
||||
t.Fatalf("want %d coins, got %d", pkg.Coins, c)
|
||||
}
|
||||
// همان توکن دوباره ⇒ قبلاً پردازش شده، بدون شارژ مجدد
|
||||
if err := svc.VerifyPurchase(ctx, id, req); err != ErrAlreadyOwned {
|
||||
t.Fatalf("want ErrAlreadyOwned, got %v", err)
|
||||
}
|
||||
if c := coinsOf(t, svc, id); c != pkg.Coins {
|
||||
t.Fatalf("double credit! got %d", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGameSettlement(t *testing.T) {
|
||||
svc, id := setup(t)
|
||||
ctx := context.Background()
|
||||
setCoins(t, svc, id, 1000)
|
||||
|
||||
// کسر ورودی
|
||||
if err := svc.ChargeEntry(ctx, id, 100, "r1"); err != nil {
|
||||
t.Fatalf("charge: %v", err)
|
||||
}
|
||||
if c := coinsOf(t, svc, id); c != 900 {
|
||||
t.Fatalf("after entry want 900, got %d", c)
|
||||
}
|
||||
// جایزه برنده
|
||||
if err := svc.AwardWinner(ctx, id, 250, 15, 50, "r1"); err != nil {
|
||||
t.Fatalf("award: %v", err)
|
||||
}
|
||||
p, _ := svc.GetProfile(ctx, id)
|
||||
if p.Coins != 1150 {
|
||||
t.Fatalf("after prize want 1150, got %d", p.Coins)
|
||||
}
|
||||
if p.XP != 15 {
|
||||
t.Fatalf("want 15 xp, got %d", p.XP)
|
||||
}
|
||||
if p.Trophies != 50 {
|
||||
t.Fatalf("want 50 trophies, got %d", p.Trophies)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user