package rechargereward import ( "context" "fmt" "testing" "chatapp3-golang/internal/config" "chatapp3-golang/internal/integration" "chatapp3-golang/internal/model" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func newTestService(t *testing.T, java rechargeRewardJavaGateway) (*Service, *gorm.DB) { t.Helper() db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &model.RechargeRewardConfig{}, &model.RechargeRewardLevel{}, ); err != nil { t.Fatalf("auto migrate: %v", err) } service := NewService(config.Config{ RechargeReward: config.RechargeRewardConfig{ DefaultSysOrigin: "LIKEI", Timezone: "Asia/Riyadh", }, }, db, java) return service, db } func TestGetConfigReturnsEmptyDraftWhenMissing(t *testing.T) { service, _ := newTestService(t, nil) resp, err := service.GetConfig(context.Background(), "") if err != nil { t.Fatalf("GetConfig() error = %v", err) } if resp.Configured || resp.Enabled { t.Fatalf("GetConfig() configured/enabled = %v/%v, want false/false", resp.Configured, resp.Enabled) } if resp.SysOrigin != "LIKEI" || resp.Timezone != "Asia/Riyadh" { t.Fatalf("GetConfig() sys/timezone = %q/%q", resp.SysOrigin, resp.Timezone) } if len(resp.LevelConfigs) != 0 { t.Fatalf("default level count = %d, want 0", len(resp.LevelConfigs)) } } func TestSaveConfigRejectsDuplicateRechargeAmount(t *testing.T) { service, _ := newTestService(t, nil) _, err := service.SaveConfig(context.Background(), SaveRechargeRewardConfigRequest{ SysOrigin: "LIKEI", Enabled: true, Timezone: "Asia/Riyadh", LevelConfigs: []RechargeRewardLevelInput{ {Level: 1, RechargeAmount: mustAmount(t, "100"), RewardGold: 1000, Enabled: true}, {Level: 2, RechargeAmount: mustAmount(t, "100.00"), RewardGold: 2000, Enabled: true}, }, }) if err == nil { t.Fatalf("SaveConfig() error = nil, want duplicate amount error") } } func TestSaveConfigUpsertsBySysOrigin(t *testing.T) { service, _ := newTestService(t, nil) ctx := context.Background() first, err := service.SaveConfig(ctx, SaveRechargeRewardConfigRequest{ SysOrigin: "likei", Enabled: true, Timezone: "Asia/Riyadh", LevelConfigs: []RechargeRewardLevelInput{ {Level: 1, RechargeAmount: mustAmount(t, "100"), RewardGold: 1000, Enabled: true}, }, }) if err != nil { t.Fatalf("first SaveConfig() error = %v", err) } second, err := service.SaveConfig(ctx, SaveRechargeRewardConfigRequest{ SysOrigin: "LIKEI", Enabled: true, Timezone: "Asia/Riyadh", LevelConfigs: []RechargeRewardLevelInput{ {Level: 1, RechargeAmount: mustAmount(t, "200.50"), RewardGold: 3000, Enabled: true}, }, }) if err != nil { t.Fatalf("second SaveConfig() error = %v", err) } if first.ID != second.ID { t.Fatalf("config ID changed on upsert: first=%d second=%d", first.ID, second.ID) } if got := second.LevelConfigs[0]; got.RechargeAmount != "200.50" || got.RewardGold != 3000 { t.Fatalf("upserted level = %+v", got) } } func TestGetHomeUsesJavaRechargeAndMarksLevels(t *testing.T) { groupID := int64(9001) java := rechargeRewardTestGateway{ monthly: integration.DecimalString("150.50"), total: map[int64][]integration.UserTotalRecharge{ 42: { {UserID: integration.Int64Value(42), Type: "GOOGLE", Amount: integration.DecimalString("5000.00")}, {UserID: integration.Int64Value(42), Type: "APPLE", Amount: integration.DecimalString("998.00")}, }, }, profile: integration.UserProfile{ ID: integration.Int64Value(42), Account: "12345678", UserNickname: "nanfangjian", UserAvatar: "https://example.com/avatar.png", }, groups: map[int64]integration.RewardGroupDetail{ groupID: { ID: integration.Int64Value(groupID), Name: "Gift Box", RewardConfigList: []integration.RewardGroupItem{ {ID: integration.Int64Value(1), Type: "PROP", Name: "Frame", Quantity: integration.Int64Value(1), Cover: "https://example.com/frame.png"}, }, }, }, } service, _ := newTestService(t, java) if _, err := service.SaveConfig(context.Background(), SaveRechargeRewardConfigRequest{ SysOrigin: "LIKEI", Enabled: true, Timezone: "Asia/Riyadh", LevelConfigs: []RechargeRewardLevelInput{ {Level: 1, RechargeAmount: mustAmount(t, "100"), RewardGroupID: ptrFlexibleInt64(groupID), Enabled: true}, {Level: 2, RechargeAmount: mustAmount(t, "200"), RewardGold: 3000, Enabled: true}, }, }); err != nil { t.Fatalf("SaveConfig() error = %v", err) } resp, err := service.GetHome(context.Background(), AuthUser{UserID: 42, SysOrigin: "LIKEI"}) if err != nil { t.Fatalf("GetHome() error = %v", err) } if resp.CurrentRechargeAmount != "150.50" || resp.AccumulatedRechargeAmount != "5998.00" { t.Fatalf("amounts = current %q accumulated %q", resp.CurrentRechargeAmount, resp.AccumulatedRechargeAmount) } if resp.MatchedLevel != 1 || resp.NextLevel != 2 { t.Fatalf("levels = matched %d next %d", resp.MatchedLevel, resp.NextLevel) } if len(resp.LevelConfigs) != 2 || !resp.LevelConfigs[0].Reached || resp.LevelConfigs[1].Reached { t.Fatalf("level reach state = %+v", resp.LevelConfigs) } if len(resp.LevelConfigs[0].RewardItems) != 1 || resp.LevelConfigs[0].RewardItems[0].Name != "Frame" { t.Fatalf("reward items = %+v", resp.LevelConfigs[0].RewardItems) } } func TestParseAmountCents(t *testing.T) { tests := map[string]int64{ "100": 10000, "100.5": 10050, "100.50": 10050, "5,998.00": 599800, } for input, want := range tests { got, err := parseAmountCents(input) if err != nil { t.Fatalf("parseAmountCents(%q) error = %v", input, err) } if got != want { t.Fatalf("parseAmountCents(%q) = %d, want %d", input, got, want) } } } func mustAmount(t *testing.T, raw string) flexibleAmount { t.Helper() cents, err := parseAmountCents(raw) if err != nil { t.Fatalf("parse amount %q: %v", raw, err) } return flexibleAmount(cents) } func ptrFlexibleInt64(value int64) flexibleOptionalInt64 { return flexibleOptionalInt64{value: &value} } type rechargeRewardTestGateway struct { monthly integration.DecimalString total map[int64][]integration.UserTotalRecharge profile integration.UserProfile groups map[int64]integration.RewardGroupDetail } func (g rechargeRewardTestGateway) GetUserProfile(context.Context, int64) (integration.UserProfile, error) { return g.profile, nil } func (g rechargeRewardTestGateway) GetRewardGroupDetail(_ context.Context, groupID int64) (integration.RewardGroupDetail, error) { if detail, ok := g.groups[groupID]; ok { return detail, nil } return integration.RewardGroupDetail{}, fmt.Errorf("group not found") } func (g rechargeRewardTestGateway) MapUserTotalRecharge(context.Context, []int64) (map[int64][]integration.UserTotalRecharge, error) { return g.total, nil } func (g rechargeRewardTestGateway) GetThisMonthTotalPersonalRecharge(context.Context, int64) (integration.DecimalString, error) { return g.monthly, nil }