package pricing import ( "math" "testing" ) func TestScreenshotExample(t *testing.T) { config := DefaultConfig() result := config.Calculate(Input{ CoinM: 226, Insurance: "3*3", States: map[string]bool{}, DailyLossM: 10, }) if result.ReferenceRatio != 46 { t.Fatalf("reference ratio = %v, want 46", result.ReferenceRatio) } if result.FinalRatio != 46 { t.Fatalf("final ratio = %v, want 46", result.FinalRatio) } if result.CoinBasePrice != 491.3 { t.Fatalf("coin base price = %v, want 491.3", result.CoinBasePrice) } if result.ItemPrice != 0 { t.Fatalf("item price = %v, want 0", result.ItemPrice) } if result.TotalPrice != 491.3 { t.Fatalf("total price = %v, want 491.3", result.TotalPrice) } } func TestAllStatesMatched(t *testing.T) { config := DefaultConfig() result := config.Calculate(Input{ CoinM: 100, Insurance: "3*3", States: map[string]bool{ "melee": true, "operatorRed": true, "staminaMax": true, "loadMax": true, "weapon": true, }, DailyLossM: 10, }) if result.ReferenceRatio != 40 { t.Fatalf("reference ratio = %v, want 40", result.ReferenceRatio) } if result.CoinBasePrice != 250 { t.Fatalf("coin base price = %v, want 250", result.CoinBasePrice) } } func TestMissingPenalty(t *testing.T) { config := DefaultConfig() result := config.Calculate(Input{ CoinM: 100, Insurance: "3*3", States: map[string]bool{ "melee": true, "operatorRed": true, "staminaMax": true, "loadMax": true, }, DailyLossM: 10, }) if result.ConfigPenalty != 1 { t.Fatalf("config penalty = %v, want 1", result.ConfigPenalty) } if result.ReferenceRatio != 41 { t.Fatalf("reference ratio = %v, want 41", result.ReferenceRatio) } if result.CoinBasePrice != 243.9 { t.Fatalf("coin base price = %v, want 243.9", result.CoinBasePrice) } } func TestCoinCorrectionUsesHighestCrossedThreshold(t *testing.T) { config := DefaultConfig() cases := []struct { coinM float64 want float64 }{ {130, 0}, {130.1, 1}, {230, 1}, {230.1, 2.5}, {380.1, 4}, {600, 5}, } for _, item := range cases { result := config.Calculate(Input{CoinM: item.coinM, Insurance: "3*3", DailyLossM: 10}) if result.CoinCorrection != item.want { t.Fatalf("coinM=%v correction=%v, want %v", item.coinM, result.CoinCorrection, item.want) } } } func TestDailyLossAdjustment(t *testing.T) { config := DefaultConfig() cases := []struct { loss float64 want float64 }{ {0, 0}, {10, 0}, {20, 1}, {30, 2}, {40, 3}, {50, 4}, {90, 4}, } for _, item := range cases { result := config.Calculate(Input{CoinM: 100, Insurance: "3*3", DailyLossM: item.loss}) if result.DailyLossAdjustment != item.want { t.Fatalf("dailyLoss=%v adjustment=%v, want %v", item.loss, result.DailyLossAdjustment, item.want) } } } func TestAcceleratedRatioClamp(t *testing.T) { config := DefaultConfig() base := Input{CoinM: 226, Insurance: "3*3", DailyLossM: 10} result := config.Calculate(withSaleRatio(base, 50)) if result.FinalRatio != 50 { t.Fatalf("final ratio = %v, want 50", result.FinalRatio) } if result.MaxRatio != 56 { t.Fatalf("max ratio = %v, want 56", result.MaxRatio) } result = config.Calculate(withSaleRatio(base, 100)) if result.FinalRatio != 56 { t.Fatalf("clamped ratio = %v, want 56", result.FinalRatio) } result = config.Calculate(withSaleRatio(base, 30)) if result.FinalRatio != 46 { t.Fatalf("ratio below reference = %v, want 46", result.FinalRatio) } } func TestConsumablePriceParsing(t *testing.T) { cases := []struct { price string want float64 }{ {"0.6元/发", 0.6}, {"6元/60发", 0.1}, {"2.5元/个", 2.5}, {"3元/张", 3}, {"2元", 2}, {"", 0}, } for _, item := range cases { if got := ParseUnitPrice(item.price); math.Abs(got-item.want) > 1e-9 { t.Fatalf("ParseUnitPrice(%q)=%v, want %v", item.price, got, item.want) } } } func TestConsumablesAndFreeItems(t *testing.T) { config := DefaultConfig() result := config.Calculate(Input{ CoinM: 100, Insurance: "3*3", DailyLossM: 10, States: map[string]bool{ "melee": true, "operatorRed": true, "staminaMax": true, "loadMax": true, "weapon": true, }, Quantities: map[string]float64{ "awmAmmo": 10, "helmet6": 2, }, FreeItems: map[string]bool{ "helmet6": true, }, }) if result.ItemPrice != 6 { t.Fatalf("item price = %v, want 6", result.ItemPrice) } if result.TotalPrice != 256 { t.Fatalf("total price = %v, want 256", result.TotalPrice) } } func TestGridCardDisabledAtThreeByThree(t *testing.T) { config := DefaultConfig() result := config.Calculate(Input{ CoinM: 100, Insurance: "3*3", DailyLossM: 10, Quantities: map[string]float64{"gridCard9": 3}, }) if result.ItemPrice != 0 { t.Fatalf("item price = %v, want 0", result.ItemPrice) } for _, item := range result.Items { if item.Key == "gridCard9" && !item.Disabled { t.Fatal("gridCard9 should be disabled at 3*3") } } result = config.Calculate(Input{ CoinM: 100, Insurance: "2*3", DailyLossM: 10, Quantities: map[string]float64{"gridCard9": 3}, }) if result.ItemPrice != 9 { t.Fatalf("item price at 2*3 = %v, want 9", result.ItemPrice) } } func TestNormalizeRestoresEmptySections(t *testing.T) { normalized := Normalize(Config{}) if len(normalized.InsuranceBaseRatios) == 0 { t.Fatal("insurance ratios should fall back to defaults") } if len(normalized.RatioConfigItems) == 0 { t.Fatal("ratio config items should fall back to defaults") } if len(normalized.QuantityItems) == 0 { t.Fatal("quantity items should fall back to defaults") } if normalized.AcceleratedMaxRatio != 10 { t.Fatalf("accelerated max = %v, want 10", normalized.AcceleratedMaxRatio) } } func withSaleRatio(input Input, ratio float64) Input { input.SaleRatio = ratio return input }