Files
sub2api/backend/internal/service/upstream_request_id_test.go
T
feeeei de27905e88 feat(usage): 用量记录落库上游请求标识并支持按账户配置响应头名
- usage_logs 新增 upstream_request_id 列(迁移 232)与仅索引非空行的 CONCURRENTLY 部分索引(迁移 233,runner 登记失效索引清理)
- 账户 extra.upstream_request_id_header 指定要读取的上游响应头,只读该头;留空不记录;创建/更新/批量入口校验头名合法且不超过 64 字节
- ForwardResult/OpenAIForwardResult 携带 UpstreamHeaders,中央计费按账户解析落库,WS 轮次保持 NULL
- 管理端用量明细新增默认隐藏的"上游ID"列,带与请求ID列一致的复制按钮,并同步导出
- 创建/编辑账户弹窗新增"上游ID"输入(UpstreamRequestIdHeaderField),提示按接入类型与平台分条列出常见头名
- HelpTooltip 悬停模式下指针移入提示框时保持打开,便于选中复制提示内容
2026-09-03 18:02:15 +08:00

79 lines
3.5 KiB
Go

package service
import (
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestUpstreamRequestIDFromHeaders_UnconfiguredAccountRecordsNothing(t *testing.T) {
h := http.Header{}
h.Set("X-Client-Request-ID", "sub2api-client")
h.Set("X-Request-ID", "sub2api-local")
h.Set("X-Oneapi-Request-Id", "oneapi-1")
h.Set("Request-Id", "req_official")
h.Set("xai-request-id", "xai-1")
h.Set("x-goog-request-id", "goog-1")
require.Equal(t, "", UpstreamRequestIDFromHeaders(nil, h))
for _, platform := range []string{PlatformAnthropic, PlatformOpenAI, PlatformGemini, PlatformAntigravity, PlatformGrok} {
require.Equal(t, "", UpstreamRequestIDFromHeaders(&Account{Platform: platform}, h), platform)
}
blank := &Account{Platform: PlatformOpenAI, Extra: map[string]any{AccountExtraUpstreamRequestIDHeader: " "}}
require.Equal(t, "", UpstreamRequestIDFromHeaders(blank, h))
}
func TestUpstreamRequestIDFromHeaders_ReadsOnlyConfiguredHeader(t *testing.T) {
account := &Account{
Platform: PlatformOpenAI,
Extra: map[string]any{AccountExtraUpstreamRequestIDHeader: " x-oneapi-request-id "},
}
h := http.Header{}
h.Set("X-Request-ID", "passthrough-from-real-upstream")
require.Equal(t, "", UpstreamRequestIDFromHeaders(account, h))
h.Set("X-Oneapi-Request-Id", " oneapi-2 ")
require.Equal(t, "oneapi-2", UpstreamRequestIDFromHeaders(account, h))
require.Equal(t, "", UpstreamRequestIDFromHeaders(account, nil))
official := &Account{Platform: PlatformAnthropic, Extra: map[string]any{AccountExtraUpstreamRequestIDHeader: "request-id"}}
only := http.Header{}
only.Set("Request-Id", "req_official")
require.Equal(t, "req_official", UpstreamRequestIDFromHeaders(official, only))
}
func TestUsageUpstreamRequestIDPtr(t *testing.T) {
account := &Account{Extra: map[string]any{AccountExtraUpstreamRequestIDHeader: "X-Request-ID"}}
h := http.Header{}
h.Set("X-Request-ID", strings.Repeat("a", 200))
require.Nil(t, usageUpstreamRequestIDPtr(account, h, true))
require.Nil(t, usageUpstreamRequestIDPtr(account, http.Header{}, false))
require.Nil(t, usageUpstreamRequestIDPtr(nil, h, false))
require.Nil(t, usageUpstreamRequestIDPtr(&Account{}, h, false))
got := usageUpstreamRequestIDPtr(account, h, false)
require.NotNil(t, got)
require.Len(t, *got, maxUsageUpstreamRequestIDLen)
}
func TestValidateUpstreamRequestIDHeaderExtra(t *testing.T) {
require.NoError(t, ValidateUpstreamRequestIDHeaderExtra(nil))
require.NoError(t, ValidateUpstreamRequestIDHeaderExtra(map[string]any{}))
blank := map[string]any{AccountExtraUpstreamRequestIDHeader: " "}
require.NoError(t, ValidateUpstreamRequestIDHeaderExtra(blank))
_, present := blank[AccountExtraUpstreamRequestIDHeader]
require.False(t, present, "blank header name must be removed")
valid := map[string]any{AccountExtraUpstreamRequestIDHeader: " X-Oneapi-Request-Id "}
require.NoError(t, ValidateUpstreamRequestIDHeaderExtra(valid))
require.Equal(t, "X-Oneapi-Request-Id", valid[AccountExtraUpstreamRequestIDHeader])
require.Error(t, ValidateUpstreamRequestIDHeaderExtra(map[string]any{AccountExtraUpstreamRequestIDHeader: 1}))
require.Error(t, ValidateUpstreamRequestIDHeaderExtra(map[string]any{AccountExtraUpstreamRequestIDHeader: "X Request Id"}))
require.Error(t, ValidateUpstreamRequestIDHeaderExtra(map[string]any{AccountExtraUpstreamRequestIDHeader: "X-Request-Id:"}))
require.Error(t, ValidateUpstreamRequestIDHeaderExtra(map[string]any{AccountExtraUpstreamRequestIDHeader: strings.Repeat("x", 65)}))
}