package game_test import ( "bindbox-game/internal/repository/mysql" "bindbox-game/internal/service/game" "context" "fmt" "testing" "time" "bindbox-game/internal/pkg/logger" "github.com/alicebob/miniredis/v2" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" ) // Mock logger type MockLogger struct { logger.CustomLogger } func (l *MockLogger) Info(msg string, fields ...zap.Field) {} func (l *MockLogger) Error(msg string, fields ...zap.Field) {} func (l *MockLogger) Warn(msg string, fields ...zap.Field) {} func (l *MockLogger) Debug(msg string, fields ...zap.Field) {} func TestGenerateToken_FreeMode(t *testing.T) { // 1. Setup Miniredis mr, err := miniredis.Run() assert.NoError(t, err) defer mr.Close() rdb := redis.NewClient(&redis.Options{ Addr: mr.Addr(), }) // 2. Setup GORM (SQLite in-memory) // We use an empty DB to ensure NO ticket exists db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{}) assert.NoError(t, err) // AutoMigrate to make sure table exists (even if empty) to avoid SQL errors // err = db.AutoMigrate(&model.UserGameTickets{}) // assert.NoError(t, err) repo := mysql.NewTestRepo(db) // 3. Create Service svc := game.NewGameTokenService(&MockLogger{}, repo, rdb) // 4. Test Case: minesweeper_free ctx := context.Background() userID := int64(12345) username := "testuser" gameCode := "minesweeper_free" // Should succeed even though DB is empty (bypasses ticket check) token, ticket, expiresAt, err := svc.GenerateToken(ctx, userID, username, "", gameCode) assert.NoError(t, err) assert.NotEmpty(t, token) assert.NotEmpty(t, ticket) assert.True(t, expiresAt.After(time.Now())) // 5. Verify Redis Key Format // Expected: "userID:gameCode" ticketKey := fmt.Sprintf("game:token:ticket:%s", ticket) val, err := rdb.Get(ctx, ticketKey).Result() assert.NoError(t, err) assert.Equal(t, fmt.Sprintf("%d:%s", userID, gameCode), val) // 6. Test Validation claims, err := svc.ValidateToken(ctx, token) assert.NoError(t, err) assert.Equal(t, userID, claims.UserID) assert.Equal(t, gameCode, claims.GameType) } func TestGenerateToken_PaidMode_NoTicket(t *testing.T) { // 1. Setup mr, err := miniredis.Run() assert.NoError(t, err) defer mr.Close() rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) db, _ := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{}) // db.AutoMigrate(&model.UserGameTickets{}) // Empty table repo := mysql.NewTestRepo(db) svc := game.NewGameTokenService(&MockLogger{}, repo, rdb) // 2. Test Case: normal game // Should FAIL because no ticket in DB _, _, _, err = svc.GenerateToken(context.Background(), 12345, "user", "", "minesweeper_paid") assert.Error(t, err) assert.Contains(t, err.Error(), "no available game tickets") } func TestValidateToken_LegacyFormat_ShouldFail(t *testing.T) { // Test that strict mode rejects legacy keys mr, _ := miniredis.Run() defer mr.Close() rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) svc := game.NewGameTokenService(&MockLogger{}, mysql.NewTestRepo(nil), rdb) userID := int64(999) // Generate valid token first token, ticket, _, _ := svc.GenerateToken(context.Background(), userID, "user", "", "minesweeper_free") // Overwrite Redis with legacy format (just userID) rdb.Set(context.Background(), "game:token:ticket:"+ticket, fmt.Sprintf("%d", userID), time.Hour) // Now Validate - SHOULD FAIL _, err := svc.ValidateToken(context.Background(), token) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid ticket format") }