package firstrechargereward import ( "context" "encoding/json" "testing" "chatapp3-golang/internal/config" "chatapp3-golang/internal/integration" "chatapp3-golang/internal/model" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func TestSaveConfigReturnsRewardItems(t *testing.T) { service, gateway, _ := newTestService(t) gateway.groups[1001] = testRewardGroup(1001, "首冲礼包") req := mustDecodeSaveConfigRequest(t, `{ "sysOrigin": "LIKEI", "enabled": true, "levelConfigs": [ {"level": 1, "rechargeAmount": "9.99", "rewardGroupId": "1001", "rewardGroupName": "首冲礼包", "enabled": true} ] }`) resp, err := service.SaveConfig(context.Background(), req) if err != nil { t.Fatalf("SaveConfig() error = %v", err) } if !resp.Configured || !resp.Enabled { t.Fatalf("SaveConfig() configured/enabled = %v/%v", resp.Configured, resp.Enabled) } if len(resp.LevelConfigs) != 1 { t.Fatalf("level count = %d", len(resp.LevelConfigs)) } level := resp.LevelConfigs[0] if level.RechargeAmountCents != 999 || level.RewardGroupID != 1001 { t.Fatalf("level payload = %+v", level) } if len(level.RewardItems) != 1 || level.RewardItems[0].Name != "首冲礼包奖励" { t.Fatalf("reward items = %+v", level.RewardItems) } } func TestProcessRechargeEventFiltersAndGrantsOnce(t *testing.T) { service, gateway, db := newTestService(t) gateway.groups[1001] = testRewardGroup(1001, "首冲礼包") gateway.profiles[123] = integration.UserProfile{ ID: integration.Int64Value(123), Account: "100123", UserNickname: "tester", CountryCode: "SA", } _, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{ "sysOrigin": "LIKEI", "enabled": true, "levelConfigs": [ {"level": 1, "rechargeAmount": "5.00", "rewardGroupId": "1001", "rewardGroupName": "首冲礼包", "enabled": true} ] }`)) if err != nil { t.Fatalf("SaveConfig() error = %v", err) } unsupported, err := service.ProcessRechargeEvent(context.Background(), mustDecodeRechargeEvent(t, `{ "eventId": "RECHARGE_SUCCESS:APPLE:1", "sysOrigin": "LIKEI", "userId": "123", "amountCents": "999", "payPlatform": "APPLE", "paymentMethod": "APPLE", "sourceOrderId": "1" }`)) if err != nil { t.Fatalf("unsupported ProcessRechargeEvent() error = %v", err) } if unsupported.Processed || unsupported.Reason != "unsupported_payment_method" { t.Fatalf("unsupported response = %+v", unsupported) } lowAmount, err := service.ProcessRechargeEvent(context.Background(), mustDecodeRechargeEvent(t, `{ "eventId": "RECHARGE_SUCCESS:GOOGLE:LOW", "sysOrigin": "LIKEI", "userId": "123", "amountCents": "499", "payPlatform": "GOOGLE", "paymentMethod": "GOOGLE", "sourceOrderId": "LOW" }`)) if err != nil { t.Fatalf("low amount ProcessRechargeEvent() error = %v", err) } if lowAmount.Processed || lowAmount.Reason != "amount_not_reached" { t.Fatalf("low amount response = %+v", lowAmount) } granted, err := service.ProcessRechargeEvent(context.Background(), mustDecodeRechargeEvent(t, `{ "eventId": "RECHARGE_SUCCESS:GOOGLE:OK", "sysOrigin": "LIKEI", "userId": "123", "amountCents": "999", "payPlatform": "GOOGLE", "paymentMethod": "GOOGLE", "sourceOrderId": "OK", "occurredAt": "2026-05-21T12:00:00Z" }`)) if err != nil { t.Fatalf("grant ProcessRechargeEvent() error = %v", err) } if !granted.Processed || granted.Status != statusSuccess || granted.Level != 1 { t.Fatalf("grant response = %+v", granted) } if len(gateway.rewardRequests) != 1 { t.Fatalf("reward request count = %d", len(gateway.rewardRequests)) } if got := gateway.rewardRequests[0].Origin; got != firstRechargeRewardOrigin { t.Fatalf("reward origin = %s", got) } if len(gateway.notices) != 1 || gateway.notices[0].NoticeType != firstRechargeRewardNoticeType { t.Fatalf("notices = %+v", gateway.notices) } duplicate, err := service.ProcessRechargeEvent(context.Background(), mustDecodeRechargeEvent(t, `{ "eventId": "RECHARGE_SUCCESS:GOOGLE:OK", "sysOrigin": "LIKEI", "userId": "123", "amountCents": "999", "payPlatform": "GOOGLE", "paymentMethod": "GOOGLE", "sourceOrderId": "OK" }`)) if err != nil { t.Fatalf("duplicate ProcessRechargeEvent() error = %v", err) } if !duplicate.Processed || duplicate.Status != statusSuccess { t.Fatalf("duplicate response = %+v", duplicate) } if len(gateway.rewardRequests) != 1 { t.Fatalf("duplicate reward request count = %d", len(gateway.rewardRequests)) } thirdPartyUnknownPlatform, err := service.ProcessRechargeEvent(context.Background(), mustDecodeRechargeEvent(t, `{ "eventId": "RECHARGE_SUCCESS:THIRD_PARTY:CUSTOM", "sysOrigin": "LIKEI", "userId": "456", "amountCents": "999", "payPlatform": "CUSTOM_PAY", "paymentMethod": "THIRD_PARTY", "sourceOrderId": "CUSTOM" }`)) if err != nil { t.Fatalf("third party ProcessRechargeEvent() error = %v", err) } if !thirdPartyUnknownPlatform.Processed || thirdPartyUnknownPlatform.Status != statusSuccess { t.Fatalf("third party response = %+v", thirdPartyUnknownPlatform) } if len(gateway.rewardRequests) != 2 { t.Fatalf("third party reward request count = %d", len(gateway.rewardRequests)) } googleDifferentPlatform, err := service.ProcessRechargeEvent(context.Background(), mustDecodeRechargeEvent(t, `{ "eventId": "RECHARGE_SUCCESS:GOOGLE:CUSTOM", "sysOrigin": "LIKEI", "userId": "789", "amountCents": "999", "payPlatform": "CUSTOM_GOOGLE_GATEWAY", "paymentMethod": "GOOGLE", "sourceOrderId": "CUSTOM_GOOGLE" }`)) if err != nil { t.Fatalf("google ProcessRechargeEvent() error = %v", err) } if !googleDifferentPlatform.Processed || googleDifferentPlatform.Status != statusSuccess { t.Fatalf("google response = %+v", googleDifferentPlatform) } if len(gateway.rewardRequests) != 3 { t.Fatalf("google reward request count = %d", len(gateway.rewardRequests)) } var count int64 if err := db.Model(&model.FirstRechargeRewardGrantRecord{}).Count(&count).Error; err != nil { t.Fatalf("count grant records: %v", err) } if count != 3 { t.Fatalf("grant record count = %d", count) } } func newTestService(t *testing.T) (*Service, *fakeGateway, *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.FirstRechargeRewardConfig{}, &model.FirstRechargeRewardLevel{}, &model.FirstRechargeRewardGrantRecord{}, ); err != nil { t.Fatalf("migrate sqlite: %v", err) } gateway := &fakeGateway{ groups: map[int64]integration.RewardGroupDetail{}, profiles: map[int64]integration.UserProfile{}, } service := NewService(config.Config{ FirstRechargeReward: config.FirstRechargeRewardConfig{ DefaultSysOrigin: "LIKEI", }, }, db, gateway) return service, gateway, db } func mustDecodeSaveConfigRequest(t *testing.T, raw string) SaveConfigRequest { t.Helper() var req SaveConfigRequest if err := json.Unmarshal([]byte(raw), &req); err != nil { t.Fatalf("decode SaveConfigRequest: %v", err) } return req } func mustDecodeRechargeEvent(t *testing.T, raw string) RechargeEvent { t.Helper() var event RechargeEvent if err := json.Unmarshal([]byte(raw), &event); err != nil { t.Fatalf("decode RechargeEvent: %v", err) } return event } func testRewardGroup(id int64, name string) integration.RewardGroupDetail { return integration.RewardGroupDetail{ ID: integration.Int64Value(id), Name: name, RewardConfigList: []integration.RewardGroupItem{ { ID: integration.Int64Value(11), Type: "PROPS", Name: name + "奖励", Content: "10001", Quantity: integration.Int64Value(1), Cover: "https://example.com/reward.png", }, }, } } type fakeGateway struct { groups map[int64]integration.RewardGroupDetail profiles map[int64]integration.UserProfile rewardRequests []integration.SendActivityRewardRequest notices []integration.OfficialNoticeCustomizeRequest cacheRemoved []int64 } func (g *fakeGateway) GetRewardGroupDetail(_ context.Context, groupID int64) (integration.RewardGroupDetail, error) { if group, ok := g.groups[groupID]; ok { return group, nil } return testRewardGroup(groupID, "默认奖励组"), nil } func (g *fakeGateway) GetUserProfile(_ context.Context, userID int64) (integration.UserProfile, error) { if profile, ok := g.profiles[userID]; ok { return profile, nil } return integration.UserProfile{ID: integration.Int64Value(userID)}, nil } func (g *fakeGateway) SendActivityReward(_ context.Context, req integration.SendActivityRewardRequest) error { g.rewardRequests = append(g.rewardRequests, req) return nil } func (g *fakeGateway) SendOfficialNoticeCustomize(_ context.Context, req integration.OfficialNoticeCustomizeRequest) error { g.notices = append(g.notices, req) return nil } func (g *fakeGateway) RemoveUserProfileCacheAll(_ context.Context, userID int64) error { g.cacheRemoved = append(g.cacheRemoved, userID) return nil }