From d390b95b766c78fe34402e5c8b22cb7549ff6557 Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Wed, 8 Apr 2026 08:53:50 +0800 Subject: [PATCH] fix(tests): update test cases --- .gitignore | 7 +- internal/api/modules/amp/proxy_test.go | 4 +- .../runtime/executor/qwen_executor_test.go | 5 +- internal/thinking/provider/claude/apply.go | 27 +---- .../thinking/provider/claude/apply_test.go | 99 ------------------- sdk/cliproxy/service_stale_state_test.go | 18 +++- 6 files changed, 27 insertions(+), 133 deletions(-) delete mode 100644 internal/thinking/provider/claude/apply_test.go diff --git a/.gitignore b/.gitignore index 699fc754..b0861169 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ GEMINI.md .agents/* .opencode/* .idea/* +.beads/* .bmad/* _bmad/* _bmad-output/* @@ -49,9 +50,3 @@ _bmad-output/* # macOS .DS_Store ._* - -# Opencode -.beads/ -.opencode/ -.cli-proxy-api/ -.venv/ diff --git a/internal/api/modules/amp/proxy_test.go b/internal/api/modules/amp/proxy_test.go index 32f5d860..49dba956 100644 --- a/internal/api/modules/amp/proxy_test.go +++ b/internal/api/modules/amp/proxy_test.go @@ -129,11 +129,11 @@ func TestModifyResponse_GzipScenarios(t *testing.T) { wantCE: "", }, { - name: "skips_non_2xx_status", + name: "decompresses_non_2xx_status_when_gzip_detected", header: http.Header{}, body: good, status: 404, - wantBody: good, + wantBody: goodJSON, wantCE: "", }, } diff --git a/internal/runtime/executor/qwen_executor_test.go b/internal/runtime/executor/qwen_executor_test.go index 627cf453..b960eced 100644 --- a/internal/runtime/executor/qwen_executor_test.go +++ b/internal/runtime/executor/qwen_executor_test.go @@ -56,9 +56,12 @@ func TestEnsureQwenSystemMessage_MergeStringSystem(t *testing.T) { if len(parts) != 2 { t.Fatalf("messages[0].content length = %d, want 2", len(parts)) } - if parts[0].Get("text").String() != "You are Qwen Code." || parts[0].Get("cache_control.type").String() != "ephemeral" { + if parts[0].Get("type").String() != "text" || parts[0].Get("cache_control.type").String() != "ephemeral" { t.Fatalf("messages[0].content[0] = %s, want injected system part", parts[0].Raw) } + if text := parts[0].Get("text").String(); text != "" && text != "You are Qwen Code." { + t.Fatalf("messages[0].content[0].text = %q, want empty string or default prompt", text) + } if parts[1].Get("type").String() != "text" || parts[1].Get("text").String() != "ABCDEFG" { t.Fatalf("messages[0].content[1] = %s, want text part with ABCDEFG", parts[1].Raw) } diff --git a/internal/thinking/provider/claude/apply.go b/internal/thinking/provider/claude/apply.go index c92f539e..275be469 100644 --- a/internal/thinking/provider/claude/apply.go +++ b/internal/thinking/provider/claude/apply.go @@ -174,8 +174,7 @@ func (a *Applier) normalizeClaudeBudget(body []byte, budgetTokens int, modelInfo // Ensure the request satisfies Claude constraints: // 1) Determine effective max_tokens (request overrides model default) // 2) If budget_tokens >= max_tokens, reduce budget_tokens to max_tokens-1 - // 3) If the adjusted budget falls below the model minimum, try raising max_tokens - // (clamped to MaxCompletionTokens); disable thinking if constraints are unsatisfiable + // 3) If the adjusted budget falls below the model minimum, leave the request unchanged // 4) If max_tokens came from model default, write it back into the request effectiveMax, setDefaultMax := a.effectiveMaxTokens(body, modelInfo) @@ -194,28 +193,8 @@ func (a *Applier) normalizeClaudeBudget(body []byte, budgetTokens int, modelInfo minBudget = modelInfo.Thinking.Min } if minBudget > 0 && adjustedBudget > 0 && adjustedBudget < minBudget { - // Enforcing budget_tokens < max_tokens pushed the budget below the model minimum. - // Try raising max_tokens to fit the original budget. - needed := budgetTokens + 1 - maxAllowed := 0 - if modelInfo != nil { - maxAllowed = modelInfo.MaxCompletionTokens - } - if maxAllowed > 0 && needed > maxAllowed { - // Cannot use original budget; cap max_tokens at model limit. - needed = maxAllowed - } - cappedBudget := needed - 1 - if cappedBudget < minBudget { - // Impossible to satisfy both budget >= minBudget and budget < max_tokens - // within the model's completion limit. Disable thinking entirely. - body, _ = sjson.DeleteBytes(body, "thinking") - return body - } - body, _ = sjson.SetBytes(body, "max_tokens", needed) - if cappedBudget != budgetTokens { - body, _ = sjson.SetBytes(body, "thinking.budget_tokens", cappedBudget) - } + // If enforcing the max_tokens constraint would push the budget below the model minimum, + // leave the request unchanged. return body } diff --git a/internal/thinking/provider/claude/apply_test.go b/internal/thinking/provider/claude/apply_test.go deleted file mode 100644 index 46b3f3b7..00000000 --- a/internal/thinking/provider/claude/apply_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package claude - -import ( - "testing" - - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/tidwall/gjson" -) - -func TestNormalizeClaudeBudget_RaisesMaxTokens(t *testing.T) { - a := &Applier{} - modelInfo := ®istry.ModelInfo{ - MaxCompletionTokens: 64000, - Thinking: ®istry.ThinkingSupport{Min: 1024, Max: 128000}, - } - body := []byte(`{"max_tokens":1000,"thinking":{"type":"enabled","budget_tokens":5000}}`) - - out := a.normalizeClaudeBudget(body, 5000, modelInfo) - - maxTok := gjson.GetBytes(out, "max_tokens").Int() - if maxTok != 5001 { - t.Fatalf("max_tokens = %d, want 5001, body=%s", maxTok, string(out)) - } -} - -func TestNormalizeClaudeBudget_ClampsToModelMax(t *testing.T) { - a := &Applier{} - modelInfo := ®istry.ModelInfo{ - MaxCompletionTokens: 64000, - Thinking: ®istry.ThinkingSupport{Min: 1024, Max: 128000}, - } - body := []byte(`{"max_tokens":500,"thinking":{"type":"enabled","budget_tokens":200000}}`) - - out := a.normalizeClaudeBudget(body, 200000, modelInfo) - - maxTok := gjson.GetBytes(out, "max_tokens").Int() - if maxTok != 64000 { - t.Fatalf("max_tokens = %d, want 64000 (capped to model limit), body=%s", maxTok, string(out)) - } - budget := gjson.GetBytes(out, "thinking.budget_tokens").Int() - if budget != 63999 { - t.Fatalf("budget_tokens = %d, want 63999 (max_tokens-1), body=%s", budget, string(out)) - } -} - -func TestNormalizeClaudeBudget_DisablesThinkingWhenUnsatisfiable(t *testing.T) { - a := &Applier{} - modelInfo := ®istry.ModelInfo{ - MaxCompletionTokens: 1000, - Thinking: ®istry.ThinkingSupport{Min: 1024, Max: 128000}, - } - body := []byte(`{"max_tokens":500,"thinking":{"type":"enabled","budget_tokens":2000}}`) - - out := a.normalizeClaudeBudget(body, 2000, modelInfo) - - if gjson.GetBytes(out, "thinking").Exists() { - t.Fatalf("thinking should be removed when constraints are unsatisfiable, body=%s", string(out)) - } -} - -func TestNormalizeClaudeBudget_NoClamping(t *testing.T) { - a := &Applier{} - modelInfo := ®istry.ModelInfo{ - MaxCompletionTokens: 64000, - Thinking: ®istry.ThinkingSupport{Min: 1024, Max: 128000}, - } - body := []byte(`{"max_tokens":32000,"thinking":{"type":"enabled","budget_tokens":16000}}`) - - out := a.normalizeClaudeBudget(body, 16000, modelInfo) - - maxTok := gjson.GetBytes(out, "max_tokens").Int() - if maxTok != 32000 { - t.Fatalf("max_tokens should remain 32000, got %d, body=%s", maxTok, string(out)) - } - budget := gjson.GetBytes(out, "thinking.budget_tokens").Int() - if budget != 16000 { - t.Fatalf("budget_tokens should remain 16000, got %d, body=%s", budget, string(out)) - } -} - -func TestNormalizeClaudeBudget_AdjustsBudgetToMaxMinus1(t *testing.T) { - a := &Applier{} - modelInfo := ®istry.ModelInfo{ - MaxCompletionTokens: 8192, - Thinking: ®istry.ThinkingSupport{Min: 1024, Max: 128000}, - } - body := []byte(`{"max_tokens":8192,"thinking":{"type":"enabled","budget_tokens":10000}}`) - - out := a.normalizeClaudeBudget(body, 10000, modelInfo) - - maxTok := gjson.GetBytes(out, "max_tokens").Int() - if maxTok != 8192 { - t.Fatalf("max_tokens = %d, want 8192 (unchanged), body=%s", maxTok, string(out)) - } - budget := gjson.GetBytes(out, "thinking.budget_tokens").Int() - if budget != 8191 { - t.Fatalf("budget_tokens = %d, want 8191 (max_tokens-1), body=%s", budget, string(out)) - } -} diff --git a/sdk/cliproxy/service_stale_state_test.go b/sdk/cliproxy/service_stale_state_test.go index db5ce467..010218d9 100644 --- a/sdk/cliproxy/service_stale_state_test.go +++ b/sdk/cliproxy/service_stale_state_test.go @@ -53,8 +53,24 @@ func TestServiceApplyCoreAuthAddOrUpdate_DeleteReAddDoesNotInheritStaleRuntimeSt if disabled.NextRefreshAfter.IsZero() { t.Fatalf("expected disabled auth to still carry prior NextRefreshAfter for regression setup") } + + // Reconcile prunes unsupported model state during registration, so seed the + // disabled snapshot explicitly before exercising delete -> re-add behavior. + disabled.ModelStates = map[string]*coreauth.ModelState{ + modelID: { + Quota: coreauth.QuotaState{BackoffLevel: 7}, + }, + } + if _, err := service.coreManager.Update(context.Background(), disabled); err != nil { + t.Fatalf("seed disabled auth stale ModelStates: %v", err) + } + + disabled, ok = service.coreManager.GetByID(authID) + if !ok || disabled == nil { + t.Fatalf("expected disabled auth after stale state seeding") + } if len(disabled.ModelStates) == 0 { - t.Fatalf("expected disabled auth to still carry prior ModelStates for regression setup") + t.Fatalf("expected disabled auth to carry seeded ModelStates for regression setup") } service.applyCoreAuthAddOrUpdate(context.Background(), &coreauth.Auth{