mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-26 17:43:21 +01:00
refactor(gacha): extract gacha logic into GachaService
Move payment processing, reward selection, stepup state management, and box gacha tracking from handlers into a dedicated service layer. Handlers now delegate to GachaService methods and only handle protocol serialization.
This commit is contained in:
316
server/channelserver/svc_gacha_test.go
Normal file
316
server/channelserver/svc_gacha_test.go
Normal file
@@ -0,0 +1,316 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func newTestGachaService(gr GachaRepo, ur UserRepo, cr CharacterRepo) *GachaService {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
return NewGachaService(gr, ur, cr, logger, 100000)
|
||||
}
|
||||
|
||||
func TestGachaService_PlayNormalGacha(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
txErr error
|
||||
poolErr error
|
||||
txRolls int
|
||||
pool []GachaEntry
|
||||
items map[uint32][]GachaItem
|
||||
wantErr bool
|
||||
wantCount int
|
||||
}{
|
||||
{
|
||||
name: "transact error",
|
||||
txErr: errors.New("tx fail"),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "reward pool error",
|
||||
txRolls: 1,
|
||||
poolErr: errors.New("pool fail"),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "success single roll",
|
||||
txRolls: 1,
|
||||
pool: []GachaEntry{{ID: 10, Weight: 100, Rarity: 3}},
|
||||
items: map[uint32][]GachaItem{
|
||||
10: {{ItemType: 1, ItemID: 500, Quantity: 1}},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gr := &mockGachaRepo{
|
||||
txRolls: tt.txRolls,
|
||||
txErr: tt.txErr,
|
||||
rewardPool: tt.pool,
|
||||
rewardPoolErr: tt.poolErr,
|
||||
entryItems: tt.items,
|
||||
}
|
||||
cr := newMockCharacterRepo()
|
||||
svc := newTestGachaService(gr, &mockUserRepoGacha{}, cr)
|
||||
|
||||
result, err := svc.PlayNormalGacha(1, 1, 1, 0)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if len(result.Rewards) != tt.wantCount {
|
||||
t.Errorf("Rewards count = %d, want %d", len(result.Rewards), tt.wantCount)
|
||||
}
|
||||
// Verify items were saved
|
||||
if tt.wantCount > 0 && cr.columns["gacha_items"] == nil {
|
||||
t.Error("Expected gacha items to be saved")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGachaService_PlayStepupGacha(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
txErr error
|
||||
poolErr error
|
||||
txRolls int
|
||||
pool []GachaEntry
|
||||
items map[uint32][]GachaItem
|
||||
guaranteed []GachaItem
|
||||
wantErr bool
|
||||
wantRandomCount int
|
||||
wantGuaranteeCount int
|
||||
}{
|
||||
{
|
||||
name: "transact error",
|
||||
txErr: errors.New("tx fail"),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "reward pool error",
|
||||
txRolls: 1,
|
||||
poolErr: errors.New("pool fail"),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "success with guaranteed",
|
||||
txRolls: 1,
|
||||
pool: []GachaEntry{{ID: 10, Weight: 100, Rarity: 2}},
|
||||
items: map[uint32][]GachaItem{
|
||||
10: {{ItemType: 1, ItemID: 600, Quantity: 2}},
|
||||
},
|
||||
guaranteed: []GachaItem{{ItemType: 1, ItemID: 700, Quantity: 1}},
|
||||
wantRandomCount: 1,
|
||||
wantGuaranteeCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gr := &mockGachaRepo{
|
||||
txRolls: tt.txRolls,
|
||||
txErr: tt.txErr,
|
||||
rewardPool: tt.pool,
|
||||
rewardPoolErr: tt.poolErr,
|
||||
entryItems: tt.items,
|
||||
guaranteedItems: tt.guaranteed,
|
||||
}
|
||||
cr := newMockCharacterRepo()
|
||||
svc := newTestGachaService(gr, &mockUserRepoGacha{}, cr)
|
||||
|
||||
result, err := svc.PlayStepupGacha(1, 1, 1, 0)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if len(result.RandomRewards) != tt.wantRandomCount {
|
||||
t.Errorf("RandomRewards count = %d, want %d", len(result.RandomRewards), tt.wantRandomCount)
|
||||
}
|
||||
if len(result.GuaranteedRewards) != tt.wantGuaranteeCount {
|
||||
t.Errorf("GuaranteedRewards count = %d, want %d", len(result.GuaranteedRewards), tt.wantGuaranteeCount)
|
||||
}
|
||||
if !gr.deletedStepup {
|
||||
t.Error("Expected stepup to be deleted")
|
||||
}
|
||||
if gr.insertedStep != 1 {
|
||||
t.Errorf("Expected insertedStep=1, got %d", gr.insertedStep)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGachaService_PlayBoxGacha(t *testing.T) {
|
||||
gr := &mockGachaRepo{
|
||||
txRolls: 1,
|
||||
rewardPool: []GachaEntry{
|
||||
{ID: 10, Weight: 100, Rarity: 1},
|
||||
},
|
||||
entryItems: map[uint32][]GachaItem{
|
||||
10: {{ItemType: 1, ItemID: 800, Quantity: 1}},
|
||||
},
|
||||
}
|
||||
cr := newMockCharacterRepo()
|
||||
svc := newTestGachaService(gr, &mockUserRepoGacha{}, cr)
|
||||
|
||||
result, err := svc.PlayBoxGacha(1, 1, 1, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if len(result.Rewards) != 1 {
|
||||
t.Errorf("Rewards count = %d, want 1", len(result.Rewards))
|
||||
}
|
||||
if len(gr.insertedBoxIDs) == 0 {
|
||||
t.Error("Expected box entry to be inserted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGachaService_GetStepupStatus(t *testing.T) {
|
||||
now := time.Date(2025, 6, 15, 15, 0, 0, 0, time.UTC) // 3 PM
|
||||
midday := time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
step uint8
|
||||
createdAt time.Time
|
||||
stepupErr error
|
||||
hasEntry bool
|
||||
wantStep uint8
|
||||
wantDeleted bool
|
||||
}{
|
||||
{
|
||||
name: "no rows",
|
||||
stepupErr: sql.ErrNoRows,
|
||||
wantStep: 0,
|
||||
},
|
||||
{
|
||||
name: "fresh with entry",
|
||||
step: 2,
|
||||
createdAt: now, // after midday
|
||||
hasEntry: true,
|
||||
wantStep: 2,
|
||||
wantDeleted: false,
|
||||
},
|
||||
{
|
||||
name: "stale (before midday)",
|
||||
step: 3,
|
||||
createdAt: midday.Add(-1 * time.Hour), // before midday boundary
|
||||
wantStep: 0,
|
||||
wantDeleted: true,
|
||||
},
|
||||
{
|
||||
name: "fresh but no entry type",
|
||||
step: 2,
|
||||
createdAt: now,
|
||||
hasEntry: false,
|
||||
wantStep: 0,
|
||||
wantDeleted: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gr := &mockGachaRepo{
|
||||
stepupStep: tt.step,
|
||||
stepupTime: tt.createdAt,
|
||||
stepupErr: tt.stepupErr,
|
||||
hasEntryType: tt.hasEntry,
|
||||
}
|
||||
svc := newTestGachaService(gr, &mockUserRepoGacha{}, newMockCharacterRepo())
|
||||
|
||||
status, err := svc.GetStepupStatus(1, 1, now)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if status.Step != tt.wantStep {
|
||||
t.Errorf("Step = %d, want %d", status.Step, tt.wantStep)
|
||||
}
|
||||
if gr.deletedStepup != tt.wantDeleted {
|
||||
t.Errorf("deletedStepup = %v, want %v", gr.deletedStepup, tt.wantDeleted)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGachaService_GetBoxInfo(t *testing.T) {
|
||||
gr := &mockGachaRepo{
|
||||
boxEntryIDs: []uint32{10, 20, 30},
|
||||
}
|
||||
svc := newTestGachaService(gr, &mockUserRepoGacha{}, newMockCharacterRepo())
|
||||
|
||||
ids, err := svc.GetBoxInfo(1, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if len(ids) != 3 {
|
||||
t.Errorf("Got %d entry IDs, want 3", len(ids))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGachaService_ResetBox(t *testing.T) {
|
||||
gr := &mockGachaRepo{}
|
||||
svc := newTestGachaService(gr, &mockUserRepoGacha{}, newMockCharacterRepo())
|
||||
|
||||
err := svc.ResetBox(1, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if !gr.deletedBox {
|
||||
t.Error("Expected box entries to be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGachaService_Transact_NetcafeCoins(t *testing.T) {
|
||||
cr := newMockCharacterRepo()
|
||||
cr.ints["netcafe_points"] = 5000
|
||||
gr := &mockGachaRepo{
|
||||
txItemType: 17,
|
||||
txItemNumber: 100,
|
||||
txRolls: 1,
|
||||
}
|
||||
svc := newTestGachaService(gr, &mockUserRepoGacha{}, cr)
|
||||
|
||||
rolls, err := svc.transact(1, 1, 1, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if rolls != 1 {
|
||||
t.Errorf("Rolls = %d, want 1", rolls)
|
||||
}
|
||||
// Netcafe points should have been reduced
|
||||
if cr.ints["netcafe_points"] != 4900 {
|
||||
t.Errorf("Netcafe points = %d, want 4900", cr.ints["netcafe_points"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGachaService_SpendGachaCoin_TrialFirst(t *testing.T) {
|
||||
ur := &mockUserRepoGacha{trialCoins: 100}
|
||||
svc := newTestGachaService(&mockGachaRepo{}, ur, newMockCharacterRepo())
|
||||
|
||||
svc.spendGachaCoin(1, 50)
|
||||
// Should have used trial coins, not premium
|
||||
}
|
||||
|
||||
func TestGachaService_SpendGachaCoin_PremiumFallback(t *testing.T) {
|
||||
ur := &mockUserRepoGacha{trialCoins: 10}
|
||||
svc := newTestGachaService(&mockGachaRepo{}, ur, newMockCharacterRepo())
|
||||
|
||||
svc.spendGachaCoin(1, 50)
|
||||
// Should have used premium coins since trial < quantity
|
||||
}
|
||||
Reference in New Issue
Block a user