package service import ( "fmt" "net/http" "net/http/httptest" "strings" "testing" "time" relaycommon "github.com/QuantumNous/new-api/relay/common" "github.com/QuantumNous/new-api/setting/operation_setting" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" ) func buildChannelAffinityTemplateContextForTest(meta channelAffinityMeta) *gin.Context { rec := httptest.NewRecorder() ctx, _ := gin.CreateTestContext(rec) setChannelAffinityContext(ctx, meta) return ctx } func TestApplyChannelAffinityOverrideTemplate_NoTemplate(t *testing.T) { ctx := buildChannelAffinityTemplateContextForTest(channelAffinityMeta{ RuleName: "rule-no-template", }) base := map[string]interface{}{ "temperature": 0.7, } merged, applied := ApplyChannelAffinityOverrideTemplate(ctx, base) require.False(t, applied) require.Equal(t, base, merged) } func TestApplyChannelAffinityOverrideTemplate_MergeTemplate(t *testing.T) { ctx := buildChannelAffinityTemplateContextForTest(channelAffinityMeta{ RuleName: "rule-with-template", ParamTemplate: map[string]interface{}{ "temperature": 0.2, "top_p": 0.95, }, UsingGroup: "default", ModelName: "gpt-4.1", RequestPath: "/v1/responses", KeySourceType: "gjson", KeySourcePath: "prompt_cache_key", KeyHint: "abcd...wxyz", KeyFingerprint: "abcd1234", }) base := map[string]interface{}{ "temperature": 0.7, "max_tokens": 2000, } merged, applied := ApplyChannelAffinityOverrideTemplate(ctx, base) require.True(t, applied) require.Equal(t, 0.7, merged["temperature"]) require.Equal(t, 0.95, merged["top_p"]) require.Equal(t, 2000, merged["max_tokens"]) require.Equal(t, 0.7, base["temperature"]) anyInfo, ok := ctx.Get(ginKeyChannelAffinityLogInfo) require.True(t, ok) info, ok := anyInfo.(map[string]interface{}) require.True(t, ok) overrideInfoAny, ok := info["override_template"] require.True(t, ok) overrideInfo, ok := overrideInfoAny.(map[string]interface{}) require.True(t, ok) require.Equal(t, true, overrideInfo["applied"]) require.Equal(t, "rule-with-template", overrideInfo["rule_name"]) require.EqualValues(t, 2, overrideInfo["param_override_keys"]) } func TestApplyChannelAffinityOverrideTemplate_MergeOperations(t *testing.T) { ctx := buildChannelAffinityTemplateContextForTest(channelAffinityMeta{ RuleName: "rule-with-ops-template", ParamTemplate: map[string]interface{}{ "operations": []map[string]interface{}{ { "mode": "pass_headers", "value": []string{"Originator"}, }, }, }, }) base := map[string]interface{}{ "temperature": 0.7, "operations": []map[string]interface{}{ { "path": "model", "mode": "trim_prefix", "value": "openai/", }, }, } merged, applied := ApplyChannelAffinityOverrideTemplate(ctx, base) require.True(t, applied) require.Equal(t, 0.7, merged["temperature"]) opsAny, ok := merged["operations"] require.True(t, ok) ops, ok := opsAny.([]interface{}) require.True(t, ok) require.Len(t, ops, 2) firstOp, ok := ops[0].(map[string]interface{}) require.True(t, ok) require.Equal(t, "pass_headers", firstOp["mode"]) secondOp, ok := ops[1].(map[string]interface{}) require.True(t, ok) require.Equal(t, "trim_prefix", secondOp["mode"]) } func TestChannelAffinityHitCodexTemplatePassHeadersEffective(t *testing.T) { gin.SetMode(gin.TestMode) setting := operation_setting.GetChannelAffinitySetting() require.NotNil(t, setting) var codexRule *operation_setting.ChannelAffinityRule for i := range setting.Rules { rule := &setting.Rules[i] if strings.EqualFold(strings.TrimSpace(rule.Name), "codex cli trace") { codexRule = rule break } } require.NotNil(t, codexRule) affinityValue := fmt.Sprintf("pc-hit-%d", time.Now().UnixNano()) cacheKeySuffix := buildChannelAffinityCacheKeySuffix(*codexRule, "default", affinityValue) cache := getChannelAffinityCache() require.NoError(t, cache.SetWithTTL(cacheKeySuffix, 9527, time.Minute)) t.Cleanup(func() { _, _ = cache.DeleteMany([]string{cacheKeySuffix}) }) rec := httptest.NewRecorder() ctx, _ := gin.CreateTestContext(rec) ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(fmt.Sprintf(`{"prompt_cache_key":"%s"}`, affinityValue))) ctx.Request.Header.Set("Content-Type", "application/json") channelID, found := GetPreferredChannelByAffinity(ctx, "gpt-5", "default") require.True(t, found) require.Equal(t, 9527, channelID) baseOverride := map[string]interface{}{ "temperature": 0.2, } mergedOverride, applied := ApplyChannelAffinityOverrideTemplate(ctx, baseOverride) require.True(t, applied) require.Equal(t, 0.2, mergedOverride["temperature"]) info := &relaycommon.RelayInfo{ RequestHeaders: map[string]string{ "Originator": "Codex CLI", "Session_id": "sess-123", "User-Agent": "codex-cli-test", }, ChannelMeta: &relaycommon.ChannelMeta{ ParamOverride: mergedOverride, HeadersOverride: map[string]interface{}{ "X-Static": "legacy-static", }, }, } _, err := relaycommon.ApplyParamOverrideWithRelayInfo([]byte(`{"model":"gpt-5"}`), info) require.NoError(t, err) require.True(t, info.UseRuntimeHeadersOverride) require.Equal(t, "legacy-static", info.RuntimeHeadersOverride["x-static"]) require.Equal(t, "Codex CLI", info.RuntimeHeadersOverride["originator"]) require.Equal(t, "sess-123", info.RuntimeHeadersOverride["session_id"]) require.Equal(t, "codex-cli-test", info.RuntimeHeadersOverride["user-agent"]) _, exists := info.RuntimeHeadersOverride["x-codex-beta-features"] require.False(t, exists) _, exists = info.RuntimeHeadersOverride["x-codex-turn-metadata"] require.False(t, exists) }