package wheel import ( "strings" "testing" activityv1 "hyapp.local/api/proto/activity/v1" ) func TestConfigToProtoSupportsPoolTierCountAndForcesPropRTPZero(t *testing.T) { tiers := validClassicTiers() tiers[0] = tierDTO{DisplayName: "金币", RewardType: " coin ", RewardCount: 1, RewardCoins: 1000, ProbabilityPercent: defaultProbabilityPercent(0, 9), Enabled: true} tiers[1] = tierDTO{DisplayName: "礼物", RewardType: "gift", RewardID: "gift_1", RewardCount: 1, RTPValueCoins: 5000, ProbabilityPercent: defaultProbabilityPercent(1, 9), Enabled: true} tiers[2] = tierDTO{DisplayName: "装扮", RewardType: "dress_up", RewardID: "dress_1", RewardCount: 1, RTPValueCoins: 999999, ProbabilityPercent: defaultProbabilityPercent(2, 9), Enabled: true} req := configRequest{ WheelID: " classic ", Enabled: true, DrawPriceCoins: 10000, TargetRTPPercent: 90, PoolRatePercent: 92, SettlementWindowDraws: 1000, Tiers: tiers, } if err := validateConfigRequest(req); err != nil { t.Fatalf("validate config failed: %v", err) } config := configToProto(req) if config.GetWheelId() != "classic" { t.Fatalf("wheel id = %q, want classic", config.GetWheelId()) } if config.GetTargetRtpPpm() != 900000 || config.GetPoolRatePpm() != 920000 { t.Fatalf("ppm mismatch: target=%d pool=%d", config.GetTargetRtpPpm(), config.GetPoolRatePpm()) } protoTiers := config.GetTiers() if len(protoTiers) != 9 { t.Fatalf("tier count = %d, want 9", len(protoTiers)) } if protoTiers[0].GetTierId() != "coin-01" || protoTiers[0].GetRtpValueCoins() != 1000 { t.Fatalf("coin tier mismatch: %+v", protoTiers[0]) } if protoTiers[2].GetRewardType() != "dress" || protoTiers[2].GetRtpValueCoins() != 0 { t.Fatalf("dress tier must force rtp to zero: %+v", protoTiers[2]) } } func TestValidateConfigRequiresPoolTierCountAndProbabilitySum(t *testing.T) { err := validateConfigRequest(configRequest{ WheelID: "classic", Tiers: []tierDTO{ {RewardType: "coin", RewardCoins: 100, ProbabilityPercent: 50}, {RewardType: "gift", RewardID: "gift_1", ProbabilityPercent: 30}, {RewardType: "gift", RewardID: "gift_2", ProbabilityPercent: 20}, }, }) if err == nil || !strings.Contains(err.Error(), "9 个奖品档位") { t.Fatalf("classic tier count should be rejected, got %v", err) } tiers := validClassicTiers() tiers[8].ProbabilityPercent = 1 err = validateConfigRequest(configRequest{ WheelID: "classic", Tiers: tiers, }) if err == nil || !strings.Contains(err.Error(), "概率合计") { t.Fatalf("probability sum should be rejected, got %v", err) } } func TestDefaultConfigDoesNotCreateMockPrizeTiers(t *testing.T) { config := defaultConfigDTO("classic") if len(config.Tiers) != 0 { t.Fatalf("default config must not create mock prize tiers: %+v", config.Tiers) } if config.WheelID != "classic" || config.DrawPriceCoins <= 0 { t.Fatalf("default base config mismatch: %+v", config) } } func TestConfigFromProtoConvertsPPMToPercent(t *testing.T) { config := configFromProto(&activityv1.WheelRuleConfig{ AppCode: "lalu", WheelId: "default", RuleVersion: 3, Enabled: true, DrawPriceCoins: 10000, TargetRtpPpm: 900000, PoolRatePpm: 920000, SettlementWindowDraws: 1000, Tiers: []*activityv1.WheelPrizeTier{ {TierId: "coin", DisplayName: "金币", RewardType: "coin", RewardCoins: 1000, RtpValueCoins: 1000, WeightPpm: 600000, Enabled: true}, }, }) if config.TargetRTPPercent != 90 || config.PoolRatePercent != 92 { t.Fatalf("percent mismatch: target=%v pool=%v", config.TargetRTPPercent, config.PoolRatePercent) } if len(config.Tiers) != 1 || config.Tiers[0].ProbabilityPercent != 60 { t.Fatalf("tier probability mismatch: %+v", config.Tiers) } } func validClassicTiers() []tierDTO { tiers := make([]tierDTO, 0, 9) for index := 0; index < 9; index++ { tiers = append(tiers, tierDTO{ DisplayName: "金币", RewardType: "coin", RewardCount: 1, RewardCoins: 100, ProbabilityPercent: defaultProbabilityPercent(index, 9), Enabled: true, }) } return tiers }