package lottery import ( "context" "encoding/json" "errors" "strings" "testing" "github.com/DATA-DOG/go-sqlmock" "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/pkg/constant" "github.com/perfect-panel/server/pkg/xerr" "gorm.io/driver/mysql" "gorm.io/gorm" ) func newAdminLotteryDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func()) { t.Helper() sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherFunc(func(expected, actual string) error { if strings.Contains(actual, expected) { return nil } return errors.New("actual sql does not contain expected: " + expected) }))) if err != nil { t.Fatalf("sqlmock: %v", err) } db, err := gorm.Open(mysql.New(mysql.Config{Conn: sqlDB, SkipInitializeWithVersion: true}), &gorm.Config{SkipDefaultTransaction: true}) if err != nil { _ = sqlDB.Close() t.Fatalf("gorm: %v", err) } return db, mock, func() { _ = sqlDB.Close() } } func adminCtx() context.Context { return context.WithValue(context.Background(), constant.CtxKeyUser, &user.User{Id: 7}) } func TestUpdateLotteryRules_RejectsOversizeEligibility(t *testing.T) { db, _, cleanup := newAdminLotteryDB(t) defer cleanup() // build oversized JSON blob := strings.Repeat("a", 9000) req := &types.UpdateAdminLotteryRulesRequest{ Id: 1, Eligibility: json.RawMessage(`{"op":"AND","payload":"` + blob + `"}`), } logic := NewUpdateLotteryRulesLogic(adminCtx(), &svc.ServiceContext{DB: db}) err := logic.UpdateLotteryRules(req) var ce *xerr.CodeError if !errors.As(err, &ce) { t.Fatalf("expected CodeError, got %v", err) } if ce.GetErrCode() != xerr.LotteryRuleTooLarge { t.Fatalf("expected LotteryRuleTooLarge, got %d", ce.GetErrCode()) } } func TestUpdateLotteryRules_RejectsDeepTree(t *testing.T) { db, _, cleanup := newAdminLotteryDB(t) defer cleanup() // build depth 9 tree tree := map[string]any{"op": "OR", "children": []any{}} cur := tree for i := 1; i < 9; i++ { next := map[string]any{"op": "OR", "children": []any{}} cur["children"] = []any{next} cur = next } raw, _ := json.Marshal(tree) req := &types.UpdateAdminLotteryRulesRequest{Id: 1, Eligibility: raw} logic := NewUpdateLotteryRulesLogic(adminCtx(), &svc.ServiceContext{DB: db}) err := logic.UpdateLotteryRules(req) var ce *xerr.CodeError if !errors.As(err, &ce) || ce.GetErrCode() != xerr.LotteryRuleTooDeep { t.Fatalf("expected LotteryRuleTooDeep, got %v", err) } } func TestUpdateLotteryRules_RejectsAnonymousCaller(t *testing.T) { db, _, cleanup := newAdminLotteryDB(t) defer cleanup() logic := NewUpdateLotteryRulesLogic(context.Background(), &svc.ServiceContext{DB: db}) err := logic.UpdateLotteryRules(&types.UpdateAdminLotteryRulesRequest{Id: 1, Eligibility: json.RawMessage("{}")}) var ce *xerr.CodeError if !errors.As(err, &ce) || ce.GetErrCode() != xerr.ErrorTokenInvalid { t.Fatalf("expected token invalid, got %v", err) } } func TestUpdateLotteryRules_ValidTreePersists(t *testing.T) { db, mock, cleanup := newAdminLotteryDB(t) defer cleanup() mock.ExpectBegin() mock.ExpectExec("UPDATE `lottery_activity`"). WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec("INSERT INTO `admin_action_log`"). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() req := &types.UpdateAdminLotteryRulesRequest{ Id: 1, Eligibility: json.RawMessage(`{"type":"has_subscription"}`), ChanceSources: json.RawMessage(`[{"source":"daily_signin","amount":1}]`), } logic := NewUpdateLotteryRulesLogic(adminCtx(), &svc.ServiceContext{DB: db}) if err := logic.UpdateLotteryRules(req); err != nil { t.Fatalf("UpdateLotteryRules: %v", err) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("expectations: %v", err) } } func TestUpdateLotteryRules_MissingBothFieldsRejects(t *testing.T) { db, _, cleanup := newAdminLotteryDB(t) defer cleanup() req := &types.UpdateAdminLotteryRulesRequest{Id: 1} logic := NewUpdateLotteryRulesLogic(adminCtx(), &svc.ServiceContext{DB: db}) err := logic.UpdateLotteryRules(req) var ce *xerr.CodeError if !errors.As(err, &ce) || ce.GetErrCode() != xerr.InvalidParams { t.Fatalf("expected InvalidParams, got %v", err) } }