fix(cag): resolve data races, add queue observability, fix test coverage
Cycle 2 fixes from reviewer-sre + reviewer-quality: - scheduler.go: remove delete() under RLock (data race); expired entries skipped on read, cleaned lazily by next MarkFailed write - proxy.go: pre-populate transports for scheduler backends at startup to prevent concurrent map writes from ServeHTTP goroutines - proxy.go: log + IncrQueueFull() on queue-full 503 path (was silent) - proxy.go: remove stub total_tokens/hit_rate from cache-hit log event - serviceadapter/router.go: Warnf when Auth.Required but jwtValidator nil - observability: add queue_full_requests_total counter + Prometheus export - scheduler_test.go: TestBackendAcquireCancelledContext (ctx cancel branch) - proxy_test.go: implement TestWarmupHeaderHandling (remove t.Skip) - router_test.go: TestAuthenticate_MethodCapabilityOverrideDirect (auth coverage)
This commit is contained in:
@@ -416,13 +416,14 @@ func TestProxyPreservesBody(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestWarmupHeaderHandling verifies that X-Poimen-Warmup: true is stripped from
|
||||
// the upstream request and replaced with X-Priority: low, while no X-Poimen-Warmup
|
||||
// on a normal request produces neither header downstream.
|
||||
// the upstream request and replaced with X-Priority: low, while a request without
|
||||
// X-Poimen-Warmup reaches upstream without either header.
|
||||
//
|
||||
// TODO(#45): implement warmup header stripping in proxy.Handler Director or middleware.
|
||||
// Once implemented, remove t.Skip and wire up the real Handler.
|
||||
// The warmup path runs only for model-based routes (cfg.Models + scheduler),
|
||||
// so we POST to /v1/chat/completions with a JSON body that names the test model.
|
||||
// WARMUP_DELAY_MS=0 eliminates the 500 ms sleep.
|
||||
func TestWarmupHeaderHandling(t *testing.T) {
|
||||
t.Skip("requires full handler setup: warmup header transform (#45) not yet implemented")
|
||||
t.Setenv("WARMUP_DELAY_MS", "0")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -433,8 +434,8 @@ func TestWarmupHeaderHandling(t *testing.T) {
|
||||
{
|
||||
name: "warmup=true",
|
||||
sendWarmup: true,
|
||||
wantWarmupGone: true, // X-Poimen-Warmup must NOT reach upstream
|
||||
wantPriorityLow: true, // X-Priority: low must be set
|
||||
wantWarmupGone: true, // X-Poimen-Warmup must NOT reach upstream
|
||||
wantPriorityLow: true, // X-Priority: low must be set
|
||||
},
|
||||
{
|
||||
name: "warmup=false",
|
||||
@@ -455,18 +456,14 @@ func TestWarmupHeaderHandling(t *testing.T) {
|
||||
defer upstream.Close()
|
||||
|
||||
upstreamAddr := strings.TrimPrefix(upstream.URL, "http://")
|
||||
// Warmup code only runs for model-based routes (scheduler path).
|
||||
// Use cfg.Models so New() builds a Scheduler for "warmup-model".
|
||||
cfg := &config.Config{
|
||||
Routes: map[string]*config.Route{
|
||||
"test": {
|
||||
Name: "test",
|
||||
Upstream: config.Upstream{
|
||||
Address: upstreamAddr,
|
||||
ConnectTimeout: 5 * time.Second,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
MaxBodySize: 1024 * 1024,
|
||||
AuthRequired: false,
|
||||
},
|
||||
Routes: make(map[string]*config.Route),
|
||||
Models: map[string]*config.ModelUpstream{
|
||||
"warmup-model": {
|
||||
Name: "warmup-model",
|
||||
Address: upstreamAddr,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -476,7 +473,10 @@ func TestWarmupHeaderHandling(t *testing.T) {
|
||||
srv := httptest.NewServer(h)
|
||||
defer srv.Close()
|
||||
|
||||
req, _ := http.NewRequest("GET", srv.URL+"/test", nil)
|
||||
// POST to /v1/chat/completions — triggers model-based dispatch and warmup path.
|
||||
body := strings.NewReader(`{"model":"warmup-model","messages":[]}`)
|
||||
req, _ := http.NewRequest("POST", srv.URL+"/v1/chat/completions", body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if tc.sendWarmup {
|
||||
req.Header.Set("X-Poimen-Warmup", "true")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user