profit_preview_test.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package service
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/stretchr/testify/require"
  6. )
  7. func TestPreviewProfitAdmissionUsesAccountRatesAndPreinitializesModels(t *testing.T) {
  8. now := time.Now()
  9. group := profitControlTestGroup(50, 0.2, 0)
  10. group.Name = "VIP-preview"
  11. cheap := profitControlTestAccountWithRate(
  12. upstreamCostTestAccount(1, UpstreamBillingProbeStatusOK, 1.0, now.Add(-3*time.Hour), 30*time.Minute),
  13. 0.5,
  14. )
  15. cheap.Name = "cheap"
  16. cheap.Extra[UpstreamBillingRateSyncEnabledExtraKey] = true
  17. cheap.Credentials = map[string]any{"model_mapping": map[string]any{"gpt-sol": "gpt-sol"}}
  18. boundary := profitControlTestAccountWithRate(
  19. upstreamCostTestAccount(2, UpstreamBillingProbeStatusOK, 0.8, now.Add(-time.Minute), 30*time.Minute),
  20. 0.8,
  21. )
  22. boundary.Name = "boundary"
  23. boundary.Credentials = map[string]any{"model_mapping": map[string]any{
  24. "gpt-sol": "gpt-sol",
  25. "gpt-luna": "gpt-luna",
  26. }}
  27. expensive := profitControlTestAccountWithRate(
  28. upstreamCostTestAccount(3, UpstreamBillingProbeStatusOK, 0.2, now.Add(-time.Minute), 30*time.Minute),
  29. 1.0,
  30. )
  31. expensive.Name = "expensive"
  32. invalid := upstreamCostTestOAuthAccount(4)
  33. invalid.Name = "invalid"
  34. reports := PreviewProfitAdmission([]ProfitPreviewGroupInput{{
  35. Group: group,
  36. Accounts: []*Account{cheap, boundary, expensive, invalid},
  37. UserOverrides: map[int64]float64{40: 0.5},
  38. Models: []string{"gpt-sol", "gpt-luna", "gpt-no-account"},
  39. }}, now)
  40. require.Len(t, reports, 1)
  41. report := reports[0]
  42. require.True(t, report.EffectiveGate)
  43. require.InDelta(t, 1.0, report.DefaultD, 1e-12)
  44. require.InDelta(t, 0.8, report.ThresholdDefault, 1e-12)
  45. require.InDelta(t, 0.5, report.MinEffectiveD, 1e-12)
  46. require.InDelta(t, 0.4, report.ThresholdMinD, 1e-12)
  47. byID := map[int64]ProfitPreviewAccountVerdict{}
  48. for _, verdict := range report.Verdicts {
  49. byID[verdict.AccountID] = verdict
  50. }
  51. require.Equal(t, ProfitPreviewClassAdmitted, byID[cheap.ID].Class)
  52. require.Equal(t, ProfitPreviewRateSourceUpstreamProbe, byID[cheap.ID].RateSource)
  53. require.Contains(t, byID[cheap.ID].Warnings, ProfitPreviewWarningProbeStale)
  54. require.True(t, byID[cheap.ID].RejectedUnderMinD)
  55. require.Equal(t, ProfitPreviewClassAdmitted, byID[boundary.ID].Class, "U == 阈值按 epsilon 语义准入")
  56. require.Equal(t, ProfitPreviewClassRejectedThreshold, byID[expensive.ID].Class)
  57. require.Contains(t, byID[expensive.ID].Warnings, ProfitPreviewWarningManualRateOne)
  58. require.Equal(t, ProfitPreviewClassRejectedInvalidRate, byID[invalid.ID].Class)
  59. require.Equal(t, 2, report.RemainingByModel["gpt-sol"])
  60. require.Equal(t, 1, report.RemainingByModel["gpt-luna"])
  61. require.Equal(t, 0, report.RemainingByModel["gpt-no-account"])
  62. _, present := report.RemainingByModel["gpt-no-account"]
  63. require.True(t, present, "全部账号均不支持时也必须显式保留 0,供 CLI 发出警告")
  64. }
  65. func TestPreviewProfitAdmissionAssumeEnabled(t *testing.T) {
  66. now := time.Now()
  67. group := profitControlTestGroup(51, 0, 0)
  68. group.Platform = PlatformOpenAI
  69. group.RateMultiplier = 0.5
  70. group.ProfitControlEnabled = false
  71. cheapAccount := profitControlTestAccountWithRate(
  72. upstreamCostTestAccount(1, UpstreamBillingProbeStatusOK, 0.9, now.Add(-time.Minute), 30*time.Minute),
  73. 0.2,
  74. )
  75. expensiveAccount := profitControlTestAccountWithRate(
  76. upstreamCostTestAccount(2, UpstreamBillingProbeStatusOK, 0.2, now.Add(-time.Minute), 30*time.Minute),
  77. 0.9,
  78. )
  79. withoutAssume := PreviewProfitAdmission([]ProfitPreviewGroupInput{{
  80. Group: group,
  81. Accounts: []*Account{cheapAccount, expensiveAccount},
  82. Models: []string{"gpt-test"},
  83. }}, now)[0]
  84. require.False(t, withoutAssume.EffectiveGate)
  85. require.Equal(t, ProfitPreviewClassAdmitted, withoutAssume.Verdicts[0].Class)
  86. require.Equal(t, ProfitPreviewClassAdmitted, withoutAssume.Verdicts[1].Class)
  87. withAssume := PreviewProfitAdmission([]ProfitPreviewGroupInput{{
  88. Group: group,
  89. Accounts: []*Account{cheapAccount, expensiveAccount},
  90. Models: []string{"gpt-test"},
  91. AssumeEnabled: true,
  92. }}, now)[0]
  93. require.True(t, withAssume.EffectiveGate)
  94. require.True(t, withAssume.AssumedEnabled)
  95. byID := map[int64]ProfitPreviewAccountVerdict{}
  96. for _, verdict := range withAssume.Verdicts {
  97. byID[verdict.AccountID] = verdict
  98. }
  99. require.Equal(t, ProfitPreviewClassAdmitted, byID[cheapAccount.ID].Class,
  100. "账号倍率 0.2 <= 阈值 0.5,探测快照的高倍率不参与准入判断")
  101. require.Equal(t, ProfitPreviewClassRejectedThreshold, byID[expensiveAccount.ID].Class,
  102. "账号倍率 0.9 > 阈值 0.5,探测快照的低倍率不能替代账号倍率")
  103. }
  104. func TestPreviewProfitAdmissionSupportsFivePlatforms(t *testing.T) {
  105. for i, platform := range []string{
  106. PlatformOpenAI,
  107. PlatformAnthropic,
  108. PlatformGemini,
  109. PlatformGrok,
  110. PlatformAntigravity,
  111. } {
  112. group := profitControlTestGroup(int64(100+i), 0, 0)
  113. group.Platform = platform
  114. rate := 0.2
  115. account := &Account{
  116. ID: int64(200 + i),
  117. Platform: platform,
  118. RateMultiplier: &rate,
  119. }
  120. report := PreviewProfitAdmission([]ProfitPreviewGroupInput{{
  121. Group: group,
  122. Accounts: []*Account{account},
  123. Models: []string{"model"},
  124. }}, time.Now())[0]
  125. require.True(t, report.EffectiveGate, platform)
  126. require.Equal(t, ProfitPreviewClassAdmitted, report.Verdicts[0].Class, platform)
  127. }
  128. }