You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

162 lines
6.6 KiB

  1. package ratio_setting
  2. import (
  3. "testing"
  4. "github.com/QuantumNous/new-api/types"
  5. "github.com/stretchr/testify/require"
  6. )
  7. func validPricingConfig() *types.PricingConfig {
  8. return &types.PricingConfig{
  9. SchemaVersion: types.PricingSchemaVersion,
  10. Scope: types.PricingScopeModel,
  11. Dimensions: []types.PricingDimension{
  12. {Key: "resolution", Source: "request.resolution", Type: "string", Default: nil},
  13. {Key: "duration", Source: "request.duration", Type: "number", Default: nil},
  14. },
  15. Table: []types.PricingRow{
  16. {"resolution": "720P", "duration": float64(5), "price": float64(0.10), "source": types.PricingRowSourceManual},
  17. {"resolution": "720P", "duration": float64(10), "price": float64(0.20), "source": types.PricingRowSourceGenerated},
  18. },
  19. Fallback: types.PricingFallback{Strategy: types.PricingFallbackReject},
  20. }
  21. }
  22. func TestValidatePricingConfigValidDefaults(t *testing.T) {
  23. cfg := validPricingConfig()
  24. require.NoError(t, ValidatePricingConfig("hailuo-test", cfg))
  25. require.Equal(t, types.BillingUnitPerCall, cfg.BillingUnit)
  26. require.Equal(t, types.PreconsumeStrategyExact, cfg.PreconsumeStrategy)
  27. }
  28. func TestValidatePricingConfigSchemaRules(t *testing.T) {
  29. tests := []struct {
  30. name string
  31. mutate func(*types.PricingConfig)
  32. wantErr string
  33. }{
  34. {"schema_version", func(c *types.PricingConfig) { c.SchemaVersion = 2 }, "schema_version"},
  35. {"scope", func(c *types.PricingConfig) { c.Scope = "channel" }, "scope"},
  36. {"dimensions", func(c *types.PricingConfig) { c.Dimensions = nil }, "dimensions"},
  37. {"table", func(c *types.PricingConfig) { c.Table = nil }, "table"},
  38. {"fallback", func(c *types.PricingConfig) { c.Fallback.Strategy = "nearest" }, "fallback.strategy"},
  39. {"default_price", func(c *types.PricingConfig) {
  40. c.Fallback = types.PricingFallback{Strategy: types.PricingFallbackDefault, DefaultPrice: -1}
  41. }, "default_price"},
  42. {"per_call_strategy", func(c *types.PricingConfig) {
  43. c.BillingUnit = types.BillingUnitPerCall
  44. c.PreconsumeStrategy = types.PreconsumeStrategyMinimum
  45. }, "per_call"},
  46. {"usage_strategy", func(c *types.PricingConfig) {
  47. c.BillingUnit = types.BillingUnitPer1MTokens
  48. c.PreconsumeStrategy = types.PreconsumeStrategyExact
  49. }, "per_1m_tokens"},
  50. {"usage_allowlist", func(c *types.PricingConfig) {
  51. c.BillingUnit = types.BillingUnitPer1MTokens
  52. c.PreconsumeStrategy = types.PreconsumeStrategyMinimum
  53. }, "does not support"},
  54. }
  55. for _, tt := range tests {
  56. t.Run(tt.name, func(t *testing.T) {
  57. cfg := validPricingConfig()
  58. tt.mutate(cfg)
  59. err := ValidatePricingConfig("hailuo-test", cfg)
  60. require.Error(t, err)
  61. require.Contains(t, err.Error(), tt.wantErr)
  62. })
  63. }
  64. }
  65. func TestValidatePricingConfigUsageAllowlist(t *testing.T) {
  66. cfg := validPricingConfig()
  67. cfg.BillingUnit = types.BillingUnitPer1MTokens
  68. cfg.PreconsumeStrategy = types.PreconsumeStrategyMinimum
  69. require.NoError(t, ValidatePricingConfig("seedance-2", cfg))
  70. }
  71. func TestValidatePricingConfigDimensionRules(t *testing.T) {
  72. tests := []struct {
  73. name string
  74. dim types.PricingDimension
  75. wantErr string
  76. }{
  77. {"bad_key", types.PricingDimension{Key: "Bad", Source: "request.x", Type: "string", Default: nil}, "invalid"},
  78. {"reserved", types.PricingDimension{Key: "price", Source: "request.x", Type: "string", Default: nil}, "reserved"},
  79. {"bad_type", types.PricingDimension{Key: "quality", Source: "request.x", Type: "object", Default: nil}, "type"},
  80. {"bad_source", types.PricingDimension{Key: "quality", Source: "response.usage.total_tokens", Type: "number", Default: nil}, "source"},
  81. {"optional_default_missing", types.PricingDimension{Key: "quality", Source: "request.quality", Type: "string", Optional: true, Default: nil}, "default required"},
  82. {"optional_default_type", types.PricingDimension{Key: "quality", Source: "request.quality", Type: "number", Optional: true, Default: "fast"}, "default type"},
  83. {"required_default", types.PricingDimension{Key: "quality", Source: "request.quality", Type: "string", Default: "fast"}, "default must be null"},
  84. }
  85. for _, tt := range tests {
  86. t.Run(tt.name, func(t *testing.T) {
  87. cfg := validPricingConfig()
  88. cfg.Dimensions = []types.PricingDimension{tt.dim}
  89. cfg.Table = []types.PricingRow{{tt.dim.Key: "x", "price": float64(1), "source": types.PricingRowSourceManual}}
  90. if tt.dim.Type == "number" {
  91. cfg.Table[0][tt.dim.Key] = float64(1)
  92. }
  93. err := ValidatePricingConfig("hailuo-test", cfg)
  94. require.Error(t, err)
  95. require.Contains(t, err.Error(), tt.wantErr)
  96. })
  97. }
  98. }
  99. func TestValidatePricingConfigRowRules(t *testing.T) {
  100. tests := []struct {
  101. name string
  102. mutate func(*types.PricingConfig)
  103. wantErr string
  104. }{
  105. {"missing_dim", func(c *types.PricingConfig) { delete(c.Table[0], "duration") }, "missing dimension"},
  106. {"extra_field", func(c *types.PricingConfig) { c.Table[0]["price_formula"] = "x" }, "unexpected field"},
  107. {"bad_dim_type", func(c *types.PricingConfig) { c.Table[0]["duration"] = "five" }, "value type"},
  108. {"bad_price", func(c *types.PricingConfig) { c.Table[0]["price"] = -1.0 }, "price"},
  109. {"bad_source", func(c *types.PricingConfig) { c.Table[0]["source"] = "api" }, "source"},
  110. {"duplicate", func(c *types.PricingConfig) { c.Table = append(c.Table, types.PricingRow(types.CloneMapAny(c.Table[0]))) }, "duplicate"},
  111. {"ambiguous", func(c *types.PricingConfig) {
  112. c.Dimensions = []types.PricingDimension{
  113. {Key: "res", Source: "request.res", Type: "string", Default: nil},
  114. {Key: "dur", Source: "request.dur", Type: "number", Default: nil},
  115. }
  116. c.Table = []types.PricingRow{
  117. {"res": "*", "dur": float64(5), "price": float64(0.10), "source": types.PricingRowSourceManual},
  118. {"res": "720P", "dur": "*", "price": float64(0.12), "source": types.PricingRowSourceManual},
  119. }
  120. }, "ambiguous"},
  121. }
  122. for _, tt := range tests {
  123. t.Run(tt.name, func(t *testing.T) {
  124. cfg := validPricingConfig()
  125. tt.mutate(cfg)
  126. err := ValidatePricingConfig("hailuo-test", cfg)
  127. require.Error(t, err)
  128. require.Contains(t, err.Error(), tt.wantErr)
  129. })
  130. }
  131. }
  132. func TestModelPricingRulesRoundTripAndClone(t *testing.T) {
  133. require.NoError(t, UpdateModelPricingRulesByJSONString(`{}`))
  134. cfg := validPricingConfig()
  135. require.NoError(t, SetPricingConfig("hailuo-test", cfg))
  136. cfg.Table[0]["price"] = float64(999)
  137. got := GetPricingConfig("hailuo-test")
  138. require.NotNil(t, got)
  139. require.Equal(t, float64(0.10), got.Table[0]["price"])
  140. jsonStr := ModelPricingRules2JSONString()
  141. require.Contains(t, jsonStr, "hailuo-test")
  142. require.NoError(t, UpdateModelPricingRulesByJSONString(jsonStr))
  143. require.NotNil(t, GetPricingConfig("hailuo-test"))
  144. require.NoError(t, DeletePricingConfig("hailuo-test"))
  145. require.Nil(t, GetPricingConfig("hailuo-test"))
  146. }