feat: add tournoment
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
package economy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// makeUser یک کاربرِ آزمایشی با شمارهی دلخواه میسازد.
|
||||
func makeUser(t *testing.T, s *Service, mobile string) int64 {
|
||||
t.Helper()
|
||||
res, err := s.db.Exec(`INSERT INTO users (mobile) VALUES (?)`, mobile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id, _ := res.LastInsertId()
|
||||
return id
|
||||
}
|
||||
|
||||
func TestTournamentJoinPointsSettle(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
svc, u1 := setup(t)
|
||||
u2 := makeUser(t, svc, "09120000002")
|
||||
setCoins(t, svc, u1, 1000)
|
||||
setCoins(t, svc, u2, 1000)
|
||||
|
||||
// تورنومنتی که همین حالا فعال است.
|
||||
id, err := svc.CreateTournament(ctx, Tournament{
|
||||
Title: "شبانه",
|
||||
EntryFee: 200,
|
||||
Prizes: []int64{500, 200}, // رتبه۱=۵۰۰، رتبه۲=۲۰۰
|
||||
StartsAt: nowUTC().Add(-time.Minute),
|
||||
EndsAt: nowUTC().Add(time.Hour),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
|
||||
// هر دو ثبتنام میکنند ⇒ ۲۰۰ سکه کسر میشود.
|
||||
if err := svc.JoinTournament(ctx, u1, id); err != nil {
|
||||
t.Fatalf("join u1: %v", err)
|
||||
}
|
||||
if err := svc.JoinTournament(ctx, u2, id); err != nil {
|
||||
t.Fatalf("join u2: %v", err)
|
||||
}
|
||||
if c := coinsOf(t, svc, u1); c != 800 {
|
||||
t.Errorf("entry fee not charged: coins=%d", c)
|
||||
}
|
||||
// ثبتنامِ دوباره باید خطا دهد.
|
||||
if err := svc.JoinTournament(ctx, u1, id); err != ErrAlreadyJoined {
|
||||
t.Errorf("expected ErrAlreadyJoined, got %v", err)
|
||||
}
|
||||
|
||||
// u1 دو برد، u2 یک برد ⇒ u1 رتبهی اول.
|
||||
svc.AddTournamentPoints(ctx, u1, true)
|
||||
svc.AddTournamentPoints(ctx, u1, true)
|
||||
svc.AddTournamentPoints(ctx, u2, true)
|
||||
svc.AddTournamentPoints(ctx, u2, false)
|
||||
|
||||
st, err := svc.TournamentStandings(ctx, id, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("standings: %v", err)
|
||||
}
|
||||
if len(st) != 2 || st[0].UserID != u1 || st[0].Points != 200 {
|
||||
t.Fatalf("bad standings: %+v", st)
|
||||
}
|
||||
if st[1].UserID != u2 || st[1].Points != 125 {
|
||||
t.Errorf("u2 points wrong: %+v", st[1])
|
||||
}
|
||||
|
||||
// پایانِ تورنومنت را به گذشته میبریم؛ تسویه جوایز را پرداخت میکند.
|
||||
if _, err := svc.db.Exec(`UPDATE tournaments SET ends_at = ? WHERE id = ?`,
|
||||
nowUTC().Add(-time.Minute).Format(tsLayout), id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
svc.SettleTournaments(ctx)
|
||||
|
||||
if c := coinsOf(t, svc, u1); c != 800+500 {
|
||||
t.Errorf("u1 prize not paid: coins=%d (want %d)", c, 800+500)
|
||||
}
|
||||
if c := coinsOf(t, svc, u2); c != 800+200 {
|
||||
t.Errorf("u2 prize not paid: coins=%d (want %d)", c, 800+200)
|
||||
}
|
||||
|
||||
// تسویهی دوباره نباید دوباره پرداخت کند.
|
||||
svc.SettleTournaments(ctx)
|
||||
if c := coinsOf(t, svc, u1); c != 1300 {
|
||||
t.Errorf("double settle paid again: coins=%d", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTournamentsWithRows(t *testing.T) {
|
||||
// رگرسیون: با MaxOpenConns(1)، ListTournaments نباید حین باز بودنِ rows
|
||||
// زیرکوئری بزند (self-deadlock). این تست با تورنومنتِ واقعی آن مسیر را میپیماید.
|
||||
ctx := context.Background()
|
||||
svc, u1 := setup(t)
|
||||
setCoins(t, svc, u1, 1000)
|
||||
id, err := svc.CreateTournament(ctx, Tournament{
|
||||
Title: "لیست", EntryFee: 100, Prizes: []int64{500},
|
||||
StartsAt: nowUTC().Add(-time.Minute), EndsAt: nowUTC().Add(time.Hour),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := svc.JoinTournament(ctx, u1, id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
svc.AddTournamentPoints(ctx, u1, true)
|
||||
|
||||
done := make(chan []TournamentView, 1)
|
||||
go func() {
|
||||
v, e := svc.ListTournaments(ctx, u1)
|
||||
if e != nil {
|
||||
t.Errorf("list: %v", e)
|
||||
}
|
||||
done <- v
|
||||
}()
|
||||
select {
|
||||
case v := <-done:
|
||||
if len(v) != 1 || !v[0].Joined || v[0].MyPoints != 100 ||
|
||||
v[0].Players != 1 || v[0].MyRank != 1 {
|
||||
t.Errorf("bad view: %+v", v)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("ListTournaments deadlocked (nested query on single conn)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTournamentJoinInsufficient(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
svc, u1 := setup(t)
|
||||
setCoins(t, svc, u1, 50)
|
||||
id, _ := svc.CreateTournament(ctx, Tournament{
|
||||
Title: "گران", EntryFee: 500, Prizes: []int64{100},
|
||||
StartsAt: nowUTC().Add(-time.Minute), EndsAt: nowUTC().Add(time.Hour),
|
||||
})
|
||||
if err := svc.JoinTournament(ctx, u1, id); err != ErrInsufficient {
|
||||
t.Errorf("expected ErrInsufficient, got %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user