- Move IncrementRPM after Forward success to prevent phantom RPM consumption during account switch retries - Add base_rpm input sanitization (clamp to 0-10000) in Create/Update - Add WindowCost scheduling checks to legacy path sticky sessions (4 check sites + 4 prefetch sites), fixing pre-existing gap - Clean up rpm_strategy/rpm_sticky_buffer when disabling RPM in BulkEditModal (JSONB merge cannot delete keys, use empty values) - Add json.Number test cases to TestGetBaseRPM/TestGetRPMStickyBuffer - Document TOCTOU race as accepted soft-limit design trade-off
121 lines
4.8 KiB
Go
121 lines
4.8 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
func TestGetBaseRPM(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
extra map[string]any
|
|
expected int
|
|
}{
|
|
{"nil extra", nil, 0},
|
|
{"no key", map[string]any{}, 0},
|
|
{"zero", map[string]any{"base_rpm": 0}, 0},
|
|
{"int value", map[string]any{"base_rpm": 15}, 15},
|
|
{"float value", map[string]any{"base_rpm": 15.0}, 15},
|
|
{"string value", map[string]any{"base_rpm": "15"}, 15},
|
|
{"negative value", map[string]any{"base_rpm": -5}, 0},
|
|
{"int64 value", map[string]any{"base_rpm": int64(20)}, 20},
|
|
{"json.Number value", map[string]any{"base_rpm": json.Number("25")}, 25},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
a := &Account{Extra: tt.extra}
|
|
if got := a.GetBaseRPM(); got != tt.expected {
|
|
t.Errorf("GetBaseRPM() = %d, want %d", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetRPMStrategy(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
extra map[string]any
|
|
expected string
|
|
}{
|
|
{"nil extra", nil, "tiered"},
|
|
{"no key", map[string]any{}, "tiered"},
|
|
{"tiered", map[string]any{"rpm_strategy": "tiered"}, "tiered"},
|
|
{"sticky_exempt", map[string]any{"rpm_strategy": "sticky_exempt"}, "sticky_exempt"},
|
|
{"invalid", map[string]any{"rpm_strategy": "foobar"}, "tiered"},
|
|
{"empty string fallback", map[string]any{"rpm_strategy": ""}, "tiered"},
|
|
{"numeric value fallback", map[string]any{"rpm_strategy": 123}, "tiered"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
a := &Account{Extra: tt.extra}
|
|
if got := a.GetRPMStrategy(); got != tt.expected {
|
|
t.Errorf("GetRPMStrategy() = %q, want %q", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckRPMSchedulability(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
extra map[string]any
|
|
currentRPM int
|
|
expected WindowCostSchedulability
|
|
}{
|
|
{"disabled", map[string]any{}, 100, WindowCostSchedulable},
|
|
{"green zone", map[string]any{"base_rpm": 15}, 10, WindowCostSchedulable},
|
|
{"yellow zone tiered", map[string]any{"base_rpm": 15}, 15, WindowCostStickyOnly},
|
|
{"red zone tiered", map[string]any{"base_rpm": 15}, 18, WindowCostNotSchedulable},
|
|
{"sticky_exempt at limit", map[string]any{"base_rpm": 15, "rpm_strategy": "sticky_exempt"}, 15, WindowCostStickyOnly},
|
|
{"sticky_exempt over limit", map[string]any{"base_rpm": 15, "rpm_strategy": "sticky_exempt"}, 100, WindowCostStickyOnly},
|
|
{"custom buffer", map[string]any{"base_rpm": 10, "rpm_sticky_buffer": 5}, 14, WindowCostStickyOnly},
|
|
{"custom buffer red", map[string]any{"base_rpm": 10, "rpm_sticky_buffer": 5}, 15, WindowCostNotSchedulable},
|
|
{"base_rpm=1 green", map[string]any{"base_rpm": 1}, 0, WindowCostSchedulable},
|
|
{"base_rpm=1 yellow (at limit)", map[string]any{"base_rpm": 1}, 1, WindowCostStickyOnly},
|
|
{"base_rpm=1 red (at limit+buffer)", map[string]any{"base_rpm": 1}, 2, WindowCostNotSchedulable},
|
|
{"negative currentRPM", map[string]any{"base_rpm": 15}, -1, WindowCostSchedulable},
|
|
{"base_rpm negative disabled", map[string]any{"base_rpm": -5}, 10, WindowCostSchedulable},
|
|
{"very high currentRPM", map[string]any{"base_rpm": 10}, 9999, WindowCostNotSchedulable},
|
|
{"sticky_exempt very high currentRPM", map[string]any{"base_rpm": 10, "rpm_strategy": "sticky_exempt"}, 9999, WindowCostStickyOnly},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
a := &Account{Extra: tt.extra}
|
|
if got := a.CheckRPMSchedulability(tt.currentRPM); got != tt.expected {
|
|
t.Errorf("CheckRPMSchedulability(%d) = %d, want %d", tt.currentRPM, got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetRPMStickyBuffer(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
extra map[string]any
|
|
expected int
|
|
}{
|
|
{"nil extra", nil, 0},
|
|
{"no keys", map[string]any{}, 0},
|
|
{"base_rpm=0", map[string]any{"base_rpm": 0}, 0},
|
|
{"base_rpm=1 min buffer 1", map[string]any{"base_rpm": 1}, 1},
|
|
{"base_rpm=4 min buffer 1", map[string]any{"base_rpm": 4}, 1},
|
|
{"base_rpm=5 buffer 1", map[string]any{"base_rpm": 5}, 1},
|
|
{"base_rpm=10 buffer 2", map[string]any{"base_rpm": 10}, 2},
|
|
{"base_rpm=15 buffer 3", map[string]any{"base_rpm": 15}, 3},
|
|
{"base_rpm=100 buffer 20", map[string]any{"base_rpm": 100}, 20},
|
|
{"custom buffer=5", map[string]any{"base_rpm": 10, "rpm_sticky_buffer": 5}, 5},
|
|
{"custom buffer=0 fallback to default", map[string]any{"base_rpm": 10, "rpm_sticky_buffer": 0}, 2},
|
|
{"custom buffer negative fallback", map[string]any{"base_rpm": 10, "rpm_sticky_buffer": -1}, 2},
|
|
{"custom buffer with float", map[string]any{"base_rpm": 10, "rpm_sticky_buffer": float64(7)}, 7},
|
|
{"json.Number base_rpm", map[string]any{"base_rpm": json.Number("10")}, 2},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
a := &Account{Extra: tt.extra}
|
|
if got := a.GetRPMStickyBuffer(); got != tt.expected {
|
|
t.Errorf("GetRPMStickyBuffer() = %d, want %d", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|