mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 15:43:49 +01:00
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.
317 lines
7.7 KiB
Go
317 lines
7.7 KiB
Go
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
|
|
}
|