Files
back-hokm/internal/economy/economy_test.go
T
2026-06-15 20:54:05 +03:30

202 lines
5.5 KiB
Go

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{})
if err := svc.LoadCatalog(context.Background()); err != nil {
t.Fatalf("load catalog: %v", err)
}
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 := svc.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)
}
}