package shared import ( "net/http" "net/http/httptest" "strings" "testing" "github.com/mostlygeek/llama-swap/internal/config" ) func TestShared_ParsePriority(t *testing.T) { tests := []struct { name string value string want int }{ {"absent", "", PriorityNormal}, {"zero", "0", 0}, {"positive", "100", 100}, {"negative", "-100", -100}, {"explicit plus", "+42", 42}, {"surrounding space", " 100 ", 100}, {"large value is not clamped", "100000", 100000}, {"alias interactive", "interactive", PriorityInteractive}, {"alias normal", "normal", PriorityNormal}, {"alias batch", "batch", PriorityBatch}, {"alias is case insensitive", "Batch", PriorityBatch}, {"tier offset on a band", "-98", -98}, {"unknown alias falls back to normal", "urgent", PriorityNormal}, {"garbage falls back to normal", "!!", PriorityNormal}, {"float falls back to normal", "1.5", PriorityNormal}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { if got := ParsePriority(tc.value); got != tc.want { t.Errorf("ParsePriority(%q)=%d want %d", tc.value, got, tc.want) } }) } } func TestShared_RequestPriority(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/v1/models", nil) if got := RequestPriority(r); got != PriorityNormal { t.Errorf("no header: got %d want %d", got, PriorityNormal) } r.Header.Set(PriorityHeader, "batch") if got := RequestPriority(r); got != PriorityBatch { t.Errorf("batch header: got %d want %d", got, PriorityBatch) } } // TestShared_PriorityBand pins the bucketing the metrics labels rely on. The // important cases are the tier offsets: a max member's batch job at -98 must // still report as batch, not normal. func TestShared_PriorityBand(t *testing.T) { tests := []struct { priority int want string }{ {PriorityInteractive, BandInteractive}, {PriorityInteractive + 2, BandInteractive}, // max tier, interactive {1000, BandInteractive}, // saturates {50, BandInteractive}, // lower edge {49, BandNormal}, {PriorityNormal, BandNormal}, {2, BandNormal}, // max tier, normal {-49, BandNormal}, {-50, BandBatch}, // upper edge {PriorityBatch + 2, BandBatch}, {PriorityBatch, BandBatch}, {-1000, BandBatch}, // saturates } for _, tc := range tests { if got := PriorityBand(tc.priority); got != tc.want { t.Errorf("PriorityBand(%d)=%q want %q", tc.priority, got, tc.want) } } } // TestShared_FetchContext_Priority verifies the header reaches the request // context, which is what carries it to the scheduler. Both entry points into // FetchContext are covered: the normal body-parsed path and /upstream/. func TestShared_FetchContext_Priority(t *testing.T) { cfg := config.Config{Models: map[string]config.ModelConfig{"m1": {}}} cases := []struct { name string path string header string want int }{ {"body path, no header", "/v1/chat/completions", "", PriorityNormal}, {"body path, alias", "/v1/chat/completions", "interactive", PriorityInteractive}, {"body path, number", "/v1/chat/completions", "-98", -98}, {"upstream path", "/upstream/m1/v1/chat/completions", "batch", PriorityBatch}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { r := httptest.NewRequest(http.MethodPost, c.path, strings.NewReader(`{"model":"m1"}`)) r.Header.Set("Content-Type", "application/json") if c.header != "" { r.Header.Set(PriorityHeader, c.header) } data, err := FetchContext(r, cfg) if err != nil { t.Fatalf("FetchContext: %v", err) } if data.Priority != c.want { t.Errorf("Priority=%d want %d", data.Priority, c.want) } }) } }