account_rpm_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package service
  2. import (
  3. "encoding/json"
  4. "testing"
  5. )
  6. func TestGetBaseRPM(t *testing.T) {
  7. tests := []struct {
  8. name string
  9. extra map[string]any
  10. expected int
  11. }{
  12. {"nil extra", nil, 0},
  13. {"no key", map[string]any{}, 0},
  14. {"zero", map[string]any{"base_rpm": 0}, 0},
  15. {"int value", map[string]any{"base_rpm": 15}, 15},
  16. {"float value", map[string]any{"base_rpm": 15.0}, 15},
  17. {"string value", map[string]any{"base_rpm": "15"}, 15},
  18. {"negative value", map[string]any{"base_rpm": -5}, 0},
  19. {"int64 value", map[string]any{"base_rpm": int64(20)}, 20},
  20. {"json.Number value", map[string]any{"base_rpm": json.Number("25")}, 25},
  21. }
  22. for _, tt := range tests {
  23. t.Run(tt.name, func(t *testing.T) {
  24. a := &Account{Extra: tt.extra}
  25. if got := a.GetBaseRPM(); got != tt.expected {
  26. t.Errorf("GetBaseRPM() = %d, want %d", got, tt.expected)
  27. }
  28. })
  29. }
  30. }
  31. func TestGetRPMStrategy(t *testing.T) {
  32. tests := []struct {
  33. name string
  34. extra map[string]any
  35. expected string
  36. }{
  37. {"nil extra", nil, "tiered"},
  38. {"no key", map[string]any{}, "tiered"},
  39. {"tiered", map[string]any{"rpm_strategy": "tiered"}, "tiered"},
  40. {"sticky_exempt", map[string]any{"rpm_strategy": "sticky_exempt"}, "sticky_exempt"},
  41. {"invalid", map[string]any{"rpm_strategy": "foobar"}, "tiered"},
  42. {"empty string fallback", map[string]any{"rpm_strategy": ""}, "tiered"},
  43. {"numeric value fallback", map[string]any{"rpm_strategy": 123}, "tiered"},
  44. }
  45. for _, tt := range tests {
  46. t.Run(tt.name, func(t *testing.T) {
  47. a := &Account{Extra: tt.extra}
  48. if got := a.GetRPMStrategy(); got != tt.expected {
  49. t.Errorf("GetRPMStrategy() = %q, want %q", got, tt.expected)
  50. }
  51. })
  52. }
  53. }
  54. func TestCheckRPMSchedulability(t *testing.T) {
  55. tests := []struct {
  56. name string
  57. extra map[string]any
  58. currentRPM int
  59. expected WindowCostSchedulability
  60. }{
  61. {"disabled", map[string]any{}, 100, WindowCostSchedulable},
  62. {"green zone", map[string]any{"base_rpm": 15}, 10, WindowCostSchedulable},
  63. {"yellow zone tiered", map[string]any{"base_rpm": 15}, 15, WindowCostStickyOnly},
  64. {"red zone tiered", map[string]any{"base_rpm": 15}, 18, WindowCostNotSchedulable},
  65. {"sticky_exempt at limit", map[string]any{"base_rpm": 15, "rpm_strategy": "sticky_exempt"}, 15, WindowCostStickyOnly},
  66. {"sticky_exempt over limit", map[string]any{"base_rpm": 15, "rpm_strategy": "sticky_exempt"}, 100, WindowCostStickyOnly},
  67. {"custom buffer", map[string]any{"base_rpm": 10, "rpm_sticky_buffer": 5}, 14, WindowCostStickyOnly},
  68. {"custom buffer red", map[string]any{"base_rpm": 10, "rpm_sticky_buffer": 5}, 15, WindowCostNotSchedulable},
  69. {"base_rpm=1 green", map[string]any{"base_rpm": 1}, 0, WindowCostSchedulable},
  70. {"base_rpm=1 yellow (at limit)", map[string]any{"base_rpm": 1}, 1, WindowCostStickyOnly},
  71. {"base_rpm=1 red (at limit+buffer)", map[string]any{"base_rpm": 1}, 2, WindowCostNotSchedulable},
  72. {"negative currentRPM", map[string]any{"base_rpm": 15}, -1, WindowCostSchedulable},
  73. {"base_rpm negative disabled", map[string]any{"base_rpm": -5}, 10, WindowCostSchedulable},
  74. {"very high currentRPM", map[string]any{"base_rpm": 10}, 9999, WindowCostNotSchedulable},
  75. {"sticky_exempt very high currentRPM", map[string]any{"base_rpm": 10, "rpm_strategy": "sticky_exempt"}, 9999, WindowCostStickyOnly},
  76. }
  77. for _, tt := range tests {
  78. t.Run(tt.name, func(t *testing.T) {
  79. a := &Account{Extra: tt.extra}
  80. if got := a.CheckRPMSchedulability(tt.currentRPM); got != tt.expected {
  81. t.Errorf("CheckRPMSchedulability(%d) = %d, want %d", tt.currentRPM, got, tt.expected)
  82. }
  83. })
  84. }
  85. }
  86. func TestGetRPMStickyBuffer(t *testing.T) {
  87. tests := []struct {
  88. name string
  89. concurrency int
  90. extra map[string]any
  91. expected int
  92. }{
  93. // 基础退化
  94. {"nil extra", 0, nil, 0},
  95. {"no keys", 0, map[string]any{}, 0},
  96. {"base_rpm=0", 0, map[string]any{"base_rpm": 0}, 0},
  97. // 新公式: concurrency + maxSessions, floor = base/5
  98. {"conc=3 sess=10 → 13", 3, map[string]any{"base_rpm": 15, "max_sessions": 10}, 13},
  99. {"conc=2 sess=5 → 7", 2, map[string]any{"base_rpm": 10, "max_sessions": 5}, 7},
  100. {"conc=3 sess=15 → 18", 3, map[string]any{"base_rpm": 30, "max_sessions": 15}, 18},
  101. // floor 生效 (conc+sess < base/5)
  102. {"conc=0 sess=0 base=15 → floor 3", 0, map[string]any{"base_rpm": 15}, 3},
  103. {"conc=0 sess=0 base=10 → floor 2", 0, map[string]any{"base_rpm": 10}, 2},
  104. {"conc=0 sess=0 base=1 → floor 1", 0, map[string]any{"base_rpm": 1}, 1},
  105. {"conc=0 sess=0 base=4 → floor 1", 0, map[string]any{"base_rpm": 4}, 1},
  106. {"conc=1 sess=0 base=15 → floor 3", 1, map[string]any{"base_rpm": 15}, 3},
  107. // 手动 override
  108. {"custom buffer=5", 3, map[string]any{"base_rpm": 10, "rpm_sticky_buffer": 5, "max_sessions": 10}, 5},
  109. {"custom buffer=0 fallback", 3, map[string]any{"base_rpm": 10, "rpm_sticky_buffer": 0, "max_sessions": 10}, 13},
  110. {"custom buffer negative fallback", 3, map[string]any{"base_rpm": 10, "rpm_sticky_buffer": -1, "max_sessions": 10}, 13},
  111. {"custom buffer with float", 3, map[string]any{"base_rpm": 10, "rpm_sticky_buffer": float64(7)}, 7},
  112. // 负值 clamp
  113. {"negative concurrency clamped", -5, map[string]any{"base_rpm": 15, "max_sessions": 10}, 10},
  114. {"negative maxSessions clamped", 3, map[string]any{"base_rpm": 15, "max_sessions": -5}, 3},
  115. // 高并发低会话
  116. {"conc=10 sess=5 → 15", 10, map[string]any{"base_rpm": 10, "max_sessions": 5}, 15},
  117. // json.Number
  118. {"json.Number base_rpm", 3, map[string]any{"base_rpm": json.Number("10"), "max_sessions": json.Number("5")}, 8},
  119. }
  120. for _, tt := range tests {
  121. t.Run(tt.name, func(t *testing.T) {
  122. a := &Account{Concurrency: tt.concurrency, Extra: tt.extra}
  123. if got := a.GetRPMStickyBuffer(); got != tt.expected {
  124. t.Errorf("GetRPMStickyBuffer() = %d, want %d", got, tt.expected)
  125. }
  126. })
  127. }
  128. }