openai_alpha_search_billing_test.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. //go:build unit
  2. package service
  3. import (
  4. "context"
  5. "testing"
  6. "time"
  7. "github.com/Wei-Shaw/sub2api/internal/config"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestCalculateWebSearchCostDefaultAndOverride(t *testing.T) {
  11. t.Parallel()
  12. s := &BillingService{}
  13. // 默认价:官方 $10/1000 次 = 0.01/次
  14. cost := s.CalculateWebSearchCost(1, nil, 1.0)
  15. require.InDelta(t, 0.01, cost.TotalCost, 1e-12)
  16. require.InDelta(t, 0.01, cost.ActualCost, 1e-12)
  17. require.Equal(t, string(BillingModePerRequest), cost.BillingMode)
  18. // 分组覆盖价 + 倍率
  19. cost = s.CalculateWebSearchCost(1, float64Ptr(0.02), 2.5)
  20. require.InDelta(t, 0.02, cost.TotalCost, 1e-12)
  21. require.InDelta(t, 0.05, cost.ActualCost, 1e-12)
  22. // 0 = 免费(区别于 nil = 默认价)
  23. cost = s.CalculateWebSearchCost(1, float64Ptr(0), 3.0)
  24. require.Zero(t, cost.TotalCost)
  25. require.Zero(t, cost.ActualCost)
  26. // 负数倍率按 0 处理,避免按 1x 误扣
  27. cost = s.CalculateWebSearchCost(1, nil, -1)
  28. require.InDelta(t, 0.01, cost.TotalCost, 1e-12)
  29. require.Zero(t, cost.ActualCost)
  30. // 次数 <= 0 不产生费用
  31. cost = s.CalculateWebSearchCost(0, float64Ptr(0.02), 1.0)
  32. require.Zero(t, cost.TotalCost)
  33. require.Empty(t, cost.BillingMode)
  34. }
  35. func TestCalculateOpenAIRecordUsageCostWebSearchPerCall(t *testing.T) {
  36. t.Parallel()
  37. svc := &OpenAIGatewayService{billingService: &BillingService{}}
  38. groupID := int64(11)
  39. // 分组未配置单价:默认 0.01。按次搜索使用不含高峰因子的基础倍率(第 4 个倍率参数 2.0),
  40. // 即使 token 倍率(含高峰,3.0)更高也不采用。
  41. apiKey := &APIKey{ID: 1, GroupID: &groupID, Group: &Group{ID: groupID, Platform: PlatformOpenAI}}
  42. result := &OpenAIForwardResult{Model: "gpt-5.6-sol", UpstreamModel: "gpt-5.6-sol", WebSearchCalls: 1}
  43. cost, err := svc.calculateOpenAIRecordUsageCost(context.Background(), result, apiKey, []string{"gpt-5.6-sol"}, 3.0, 1.0, 1.0, 2.0, UsageTokens{}, "", boolPtr(false), time.Time{})
  44. require.NoError(t, err)
  45. require.Equal(t, string(BillingModePerRequest), cost.BillingMode)
  46. require.InDelta(t, 0.01, cost.TotalCost, 1e-12)
  47. require.InDelta(t, 0.02, cost.ActualCost, 1e-12)
  48. // 分组配置单价 0.005
  49. apiKey.Group.WebSearchPricePerCall = float64Ptr(0.005)
  50. cost, err = svc.calculateOpenAIRecordUsageCost(context.Background(), result, apiKey, []string{"gpt-5.6-sol"}, 1.0, 1.0, 1.0, 1.0, UsageTokens{}, "", boolPtr(false), time.Time{})
  51. require.NoError(t, err)
  52. require.InDelta(t, 0.005, cost.TotalCost, 1e-12)
  53. require.InDelta(t, 0.005, cost.ActualCost, 1e-12)
  54. // WebSearchCalls = 0 时不得走按次分支(无定价数据会返回 pricing 错误,
  55. // 证明回落到了 token 路径而不是被按次分支吞掉)。
  56. result.WebSearchCalls = 0
  57. _, err = svc.calculateOpenAIRecordUsageCost(context.Background(), result, apiKey, []string{"gpt-5.6-sol"}, 1.0, 1.0, 1.0, 1.0, UsageTokens{InputTokens: 10}, "", boolPtr(false), time.Time{})
  58. require.Error(t, err)
  59. }
  60. func TestAPIKeyService_SnapshotRoundTrip_PreservesWebSearchPricePerCall(t *testing.T) {
  61. svc := NewAPIKeyService(nil, nil, nil, nil, nil, nil, &config.Config{})
  62. groupID := int64(9)
  63. apiKey := &APIKey{
  64. ID: 1,
  65. UserID: 2,
  66. GroupID: &groupID,
  67. Key: "k-websearch",
  68. Status: StatusActive,
  69. User: &User{ID: 2, Status: StatusActive, Role: RoleUser},
  70. Group: &Group{
  71. ID: groupID,
  72. Name: "openai",
  73. Platform: PlatformOpenAI,
  74. Status: StatusActive,
  75. SubscriptionType: SubscriptionTypeStandard,
  76. RateMultiplier: 1,
  77. WebSearchPricePerCall: float64Ptr(0.008),
  78. },
  79. }
  80. snapshot := svc.snapshotFromAPIKey(context.Background(), apiKey)
  81. roundTrip := svc.snapshotToAPIKey(apiKey.Key, snapshot)
  82. require.NotNil(t, roundTrip)
  83. require.NotNil(t, roundTrip.Group)
  84. require.NotNil(t, roundTrip.Group.WebSearchPricePerCall)
  85. require.InDelta(t, 0.008, *roundTrip.Group.WebSearchPricePerCall, 1e-12)
  86. }