210 lines
7.6 KiB
Go

package luckygift
import (
"testing"
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
)
func TestConfigToProtoConvertsBusinessUnitsToPPM(t *testing.T) {
config := configToProto(configRequest{
PoolID: " lucky_100 ",
Enabled: true,
TargetRTPPercent: 95,
PoolRatePercent: 96,
SettlementWindowWager: 1_000_000,
ControlBandPercent: 1,
GiftPriceReference: 100,
NoviceMaxEquivalentDraws: 2_000,
NormalMaxEquivalentDraws: 20_000,
Stages: []stageDTO{
{
Stage: " novice ",
Tiers: []tierDTO{
{
TierID: " novice_0_3x ",
Multiplier: 0.3,
ProbabilityPercent: 22,
Enabled: true,
},
},
},
},
})
if config.GetPoolId() != "lucky_100" {
t.Fatalf("pool id = %q, want lucky_100", config.GetPoolId())
}
if config.GetTargetRtpPpm() != 950_000 {
t.Fatalf("target rtp ppm = %d, want 950000", config.GetTargetRtpPpm())
}
if config.GetPoolRatePpm() != 960_000 {
t.Fatalf("pool rate ppm = %d, want 960000", config.GetPoolRatePpm())
}
if config.GetControlBandPpm() != 10_000 {
t.Fatalf("control band ppm = %d, want 10000", config.GetControlBandPpm())
}
if config.GetNoviceMaxEquivalentDraws() != 2_000 || config.GetNormalMaxEquivalentDraws() != 20_000 {
t.Fatalf("equivalent draw thresholds mismatch: novice=%d normal=%d", config.GetNoviceMaxEquivalentDraws(), config.GetNormalMaxEquivalentDraws())
}
tier := config.GetStages()[0].GetTiers()[0]
if tier.GetStage() != "novice" {
t.Fatalf("tier stage = %q, want novice", tier.GetStage())
}
if tier.GetTierId() != "novice_0_3x" {
t.Fatalf("tier id = %q, want novice_0_3x", tier.GetTierId())
}
if tier.GetMultiplierPpm() != 300_000 {
t.Fatalf("multiplier ppm = %d, want 300000", tier.GetMultiplierPpm())
}
if tier.GetBaseWeightPpm() != 220_000 {
t.Fatalf("base weight ppm = %d, want 220000", tier.GetBaseWeightPpm())
}
}
func TestConfigToProtoGeneratesUniqueTierIDs(t *testing.T) {
config := configToProto(configRequest{
PoolID: "lucky",
Stages: []stageDTO{
{
Stage: "advanced",
Tiers: []tierDTO{
{TierID: "manual_one", Multiplier: 0, ProbabilityPercent: 50, Enabled: true},
{TierID: "manual_two", Multiplier: 5, ProbabilityPercent: 25, Enabled: true},
{TierID: "manual_two", Multiplier: 5, ProbabilityPercent: 25, Enabled: true},
{TierID: "manual_decimal", Multiplier: 0.5, ProbabilityPercent: 0},
},
},
},
})
tiers := config.GetStages()[0].GetTiers()
got := []string{tiers[0].GetTierId(), tiers[1].GetTierId(), tiers[2].GetTierId(), tiers[3].GetTierId()}
want := []string{"advanced_none", "advanced_5x", "advanced_5x_2", "advanced_0_5x"}
for index := range want {
if got[index] != want[index] {
t.Fatalf("tier id[%d] = %q, want %q; all=%v", index, got[index], want[index], got)
}
}
}
func TestConfigFromProtoConvertsPPMToBusinessUnits(t *testing.T) {
config := configFromProto(&luckygiftv1.LuckyGiftRuleConfig{
AppCode: "hyapp",
PoolId: "lucky_100",
RuleVersion: 7,
Enabled: true,
TargetRtpPpm: 950_000,
PoolRatePpm: 960_000,
SettlementWindowWager: 1_000_000,
ControlBandPpm: 10_000,
GiftPriceReference: 100,
NoviceMaxEquivalentDraws: 2_000,
NormalMaxEquivalentDraws: 20_000,
CreatedByAdminId: 12,
CreatedAtMs: 1_700_000_000_000,
Stages: []*luckygiftv1.LuckyGiftRuleStage{
{
Stage: "novice",
Tiers: []*luckygiftv1.LuckyGiftRuleTier{
{
TierId: "novice_0_3x",
MultiplierPpm: 300_000,
BaseWeightPpm: 220_000,
Enabled: true,
},
},
},
},
})
if config.TargetRTPPercent != 95 {
t.Fatalf("target rtp percent = %v, want 95", config.TargetRTPPercent)
}
if config.PoolRatePercent != 96 {
t.Fatalf("pool rate percent = %v, want 96", config.PoolRatePercent)
}
if config.ControlBandPercent != 1 {
t.Fatalf("control band percent = %v, want 1", config.ControlBandPercent)
}
if config.NoviceMaxEquivalentDraws != 2_000 || config.NormalMaxEquivalentDraws != 20_000 {
t.Fatalf("equivalent draw thresholds were not copied from proto: novice=%d normal=%d", config.NoviceMaxEquivalentDraws, config.NormalMaxEquivalentDraws)
}
if config.Stages[0].Tiers[0].Multiplier != 0.3 {
t.Fatalf("multiplier = %v, want 0.3", config.Stages[0].Tiers[0].Multiplier)
}
if config.Stages[0].Tiers[0].ProbabilityPercent != 22 {
t.Fatalf("probability percent = %v, want 22", config.Stages[0].Tiers[0].ProbabilityPercent)
}
}
func TestDynamicConfigAdminMappingRoundTrip(t *testing.T) {
protoConfig := configToProto(configRequest{
AppCode: " LALU ",
PoolID: " lucky ",
StrategyVersion: " DYNAMIC_V3 ",
Enabled: true,
TargetRTPPercent: 98,
PoolRatePercent: 98,
ProfitRatePercent: 1,
AnchorRatePercent: 1,
SettlementWindowWager: 1_000_000,
ControlBandPercent: 3,
GiftPriceReference: 100,
InitialPoolCoins: 1_000_000,
LossStreakGuarantee: 5,
LowWatermarkCoins: 10_000_000,
LowWaterNonzeroFactorPercent: 70,
HighWatermarkCoins: 20_000_000,
HighWaterNonzeroFactorPercent: 130,
RechargeBoostWindowMS: 300_000,
RechargeBoostFactorPercent: 110,
JackpotMultipliers: []float64{200, 500, 1000},
JackpotGlobalRTPMaxPercent: 98,
JackpotUserDayRTPMaxPercent: 96,
JackpotUser72hRTPMaxPercent: 96,
JackpotSpendThresholdCoins: 5_000,
MaxJackpotHitsPerUserDay: 5,
MaxSinglePayout: 10_000,
UserHourlyPayoutCap: 20_000,
UserDailyPayoutCap: 30_000,
DeviceDailyPayoutCap: 40_000,
RoomHourlyPayoutCap: 50_000,
AnchorDailyPayoutCap: 60_000,
Stages: []stageDTO{{
Stage: " normal ",
MinRecharge7DCoins: 7_000,
MinRecharge30DCoins: 30_000,
Tiers: []tierDTO{{Multiplier: 1, ProbabilityPercent: 100, Enabled: true}},
}},
})
if protoConfig.GetStrategyVersion() != "dynamic_v3" || protoConfig.GetProfitRatePpm() != 10_000 || protoConfig.GetAnchorRatePpm() != 10_000 {
t.Fatalf("strategy/fund split proto mismatch: %+v", protoConfig)
}
if protoConfig.GetLowWaterNonzeroFactorPpm() != 700_000 || protoConfig.GetHighWaterNonzeroFactorPpm() != 1_300_000 || protoConfig.GetRechargeBoostFactorPpm() != 1_100_000 {
t.Fatalf("dynamic factors lost precision in admin mapping: %+v", protoConfig)
}
if got := protoConfig.GetJackpotMultiplierPpms(); len(got) != 3 || got[0] != 200_000_000 || got[2] != 1_000_000_000 {
t.Fatalf("jackpot multiplier ppms=%v, want 200x/500x/1000x", got)
}
stage := protoConfig.GetStages()[0]
if stage.GetMinRecharge_7DCoins() != 7_000 || stage.GetMinRecharge_30DCoins() != 30_000 {
t.Fatalf("stage recharge thresholds were not mapped: %+v", stage)
}
dto := configFromProto(protoConfig)
if dto.StrategyVersion != "dynamic_v3" || dto.ProfitRatePercent != 1 || dto.AnchorRatePercent != 1 {
t.Fatalf("strategy/fund split DTO mismatch: %+v", dto)
}
if dto.LowWaterNonzeroFactorPercent != 70 || dto.HighWaterNonzeroFactorPercent != 130 || dto.RechargeBoostFactorPercent != 110 {
t.Fatalf("dynamic factor DTO mismatch: %+v", dto)
}
if len(dto.JackpotMultipliers) != 3 || dto.JackpotMultipliers[1] != 500 || dto.JackpotUser72hRTPMaxPercent != 96 {
t.Fatalf("jackpot DTO mismatch: %+v", dto)
}
if dto.MaxSinglePayout != 10_000 || dto.AnchorDailyPayoutCap != 60_000 || dto.Stages[0].MinRecharge30DCoins != 30_000 {
t.Fatalf("risk/stage DTO mismatch: %+v", dto)
}
}