mirror of
https://github.com/alibaba/higress.git
synced 2026-03-02 15:40:54 +08:00
293 lines
10 KiB
Go
293 lines
10 KiB
Go
package test
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||
"github.com/higress-group/wasm-go/pkg/test"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
// 测试配置:多 API Token 配置(用于测试 consumer affinity)
|
||
var multiTokenOpenAIConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"provider": map[string]interface{}{
|
||
"type": "openai",
|
||
"apiTokens": []string{"sk-token-1", "sk-token-2", "sk-token-3"},
|
||
"modelMapping": map[string]string{
|
||
"*": "gpt-4",
|
||
},
|
||
},
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:单 API Token 配置
|
||
var singleTokenOpenAIConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"provider": map[string]interface{}{
|
||
"type": "openai",
|
||
"apiTokens": []string{"sk-single-token"},
|
||
"modelMapping": map[string]string{
|
||
"*": "gpt-4",
|
||
},
|
||
},
|
||
})
|
||
return data
|
||
}()
|
||
|
||
func RunConsumerAffinityParseConfigTests(t *testing.T) {
|
||
test.RunGoTest(t, func(t *testing.T) {
|
||
t.Run("multi token config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(multiTokenOpenAIConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
config, err := host.GetMatchConfig()
|
||
require.NoError(t, err)
|
||
require.NotNil(t, config)
|
||
})
|
||
})
|
||
}
|
||
|
||
func RunConsumerAffinityOnHttpRequestHeadersTests(t *testing.T) {
|
||
test.RunTest(t, func(t *testing.T) {
|
||
// 测试 stateful API(responses)使用 consumer affinity
|
||
t.Run("stateful api responses with consumer header", func(t *testing.T) {
|
||
host, status := test.NewTestHost(multiTokenOpenAIConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
// 使用 x-mse-consumer header
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/v1/responses"},
|
||
{":method", "POST"},
|
||
{"Content-Type", "application/json"},
|
||
{"x-mse-consumer", "consumer-alice"},
|
||
})
|
||
|
||
require.Equal(t, types.HeaderStopIteration, action)
|
||
|
||
requestHeaders := host.GetRequestHeaders()
|
||
require.NotNil(t, requestHeaders)
|
||
|
||
// 验证 Authorization header 使用了其中一个 token
|
||
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
|
||
require.True(t, hasAuth, "Authorization header should exist")
|
||
require.True(t, strings.Contains(authValue, "sk-token-"), "Authorization should contain one of the tokens")
|
||
})
|
||
|
||
// 测试 stateful API(files)使用 consumer affinity
|
||
t.Run("stateful api files with consumer header", func(t *testing.T) {
|
||
host, status := test.NewTestHost(multiTokenOpenAIConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/v1/files"},
|
||
{":method", "POST"},
|
||
{"Content-Type", "application/json"},
|
||
{"x-mse-consumer", "consumer-files"},
|
||
})
|
||
|
||
require.Equal(t, types.HeaderStopIteration, action)
|
||
|
||
requestHeaders := host.GetRequestHeaders()
|
||
require.NotNil(t, requestHeaders)
|
||
|
||
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
|
||
require.True(t, hasAuth, "Authorization header should exist")
|
||
require.True(t, strings.Contains(authValue, "sk-token-"), "Authorization should contain one of the tokens")
|
||
})
|
||
|
||
// 测试 stateful API(batches)使用 consumer affinity
|
||
t.Run("stateful api batches with consumer header", func(t *testing.T) {
|
||
host, status := test.NewTestHost(multiTokenOpenAIConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/v1/batches"},
|
||
{":method", "POST"},
|
||
{"Content-Type", "application/json"},
|
||
{"x-mse-consumer", "consumer-batches"},
|
||
})
|
||
|
||
require.Equal(t, types.HeaderStopIteration, action)
|
||
|
||
requestHeaders := host.GetRequestHeaders()
|
||
require.NotNil(t, requestHeaders)
|
||
|
||
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
|
||
require.True(t, hasAuth, "Authorization header should exist")
|
||
require.True(t, strings.Contains(authValue, "sk-token-"), "Authorization should contain one of the tokens")
|
||
})
|
||
|
||
// 测试 stateful API(fine_tuning)使用 consumer affinity
|
||
t.Run("stateful api fine_tuning with consumer header", func(t *testing.T) {
|
||
host, status := test.NewTestHost(multiTokenOpenAIConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/v1/fine_tuning/jobs"},
|
||
{":method", "POST"},
|
||
{"Content-Type", "application/json"},
|
||
{"x-mse-consumer", "consumer-finetuning"},
|
||
})
|
||
|
||
require.Equal(t, types.HeaderStopIteration, action)
|
||
|
||
requestHeaders := host.GetRequestHeaders()
|
||
require.NotNil(t, requestHeaders)
|
||
|
||
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
|
||
require.True(t, hasAuth, "Authorization header should exist")
|
||
require.True(t, strings.Contains(authValue, "sk-token-"), "Authorization should contain one of the tokens")
|
||
})
|
||
|
||
// 测试非 stateful API 正常工作
|
||
t.Run("non stateful api chat completions works normally", func(t *testing.T) {
|
||
host, status := test.NewTestHost(multiTokenOpenAIConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/v1/chat/completions"},
|
||
{":method", "POST"},
|
||
{"Content-Type", "application/json"},
|
||
{"x-mse-consumer", "consumer-chat"},
|
||
})
|
||
|
||
require.Equal(t, types.HeaderStopIteration, action)
|
||
|
||
requestHeaders := host.GetRequestHeaders()
|
||
require.NotNil(t, requestHeaders)
|
||
|
||
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
|
||
require.True(t, hasAuth, "Authorization header should exist")
|
||
require.True(t, strings.Contains(authValue, "sk-token-"), "Authorization should contain one of the tokens")
|
||
})
|
||
|
||
// 测试无 x-mse-consumer header 时正常工作
|
||
t.Run("stateful api without consumer header works normally", func(t *testing.T) {
|
||
host, status := test.NewTestHost(multiTokenOpenAIConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/v1/responses"},
|
||
{":method", "POST"},
|
||
{"Content-Type", "application/json"},
|
||
})
|
||
|
||
require.Equal(t, types.HeaderStopIteration, action)
|
||
|
||
requestHeaders := host.GetRequestHeaders()
|
||
require.NotNil(t, requestHeaders)
|
||
|
||
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
|
||
require.True(t, hasAuth, "Authorization header should exist")
|
||
require.True(t, strings.Contains(authValue, "sk-token-"), "Authorization should contain one of the tokens")
|
||
})
|
||
|
||
// 测试单个 token 时始终使用该 token
|
||
t.Run("single token always used", func(t *testing.T) {
|
||
host, status := test.NewTestHost(singleTokenOpenAIConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/v1/responses"},
|
||
{":method", "POST"},
|
||
{"Content-Type", "application/json"},
|
||
{"x-mse-consumer", "consumer-test"},
|
||
})
|
||
|
||
require.Equal(t, types.HeaderStopIteration, action)
|
||
|
||
requestHeaders := host.GetRequestHeaders()
|
||
authValue, _ := test.GetHeaderValue(requestHeaders, "Authorization")
|
||
require.Contains(t, authValue, "sk-single-token", "Single token should always be used")
|
||
})
|
||
|
||
// 测试同一 consumer 多次请求获得相同 token(consumer affinity 一致性)
|
||
t.Run("same consumer gets consistent token across requests", func(t *testing.T) {
|
||
consumer := "consumer-consistency-test"
|
||
var firstToken string
|
||
|
||
// 运行 5 次请求,验证同一个 consumer 始终获得相同的 token
|
||
for i := 0; i < 5; i++ {
|
||
host, status := test.NewTestHost(multiTokenOpenAIConfig)
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/v1/responses"},
|
||
{":method", "POST"},
|
||
{"Content-Type", "application/json"},
|
||
{"x-mse-consumer", consumer},
|
||
})
|
||
|
||
require.Equal(t, types.HeaderStopIteration, action)
|
||
|
||
requestHeaders := host.GetRequestHeaders()
|
||
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
|
||
require.True(t, hasAuth, "Authorization header should exist")
|
||
require.True(t, strings.Contains(authValue, "sk-token-"), "Should use one of the configured tokens")
|
||
|
||
if i == 0 {
|
||
firstToken = authValue
|
||
} else {
|
||
require.Equal(t, firstToken, authValue, "Same consumer should get same token consistently (consumer affinity)")
|
||
}
|
||
|
||
host.Reset()
|
||
}
|
||
})
|
||
|
||
// 测试不同 consumer 可能获得不同 token
|
||
t.Run("different consumers get tokens based on hash", func(t *testing.T) {
|
||
tokens := make(map[string]string)
|
||
|
||
consumers := []string{"consumer-alpha", "consumer-beta", "consumer-gamma", "consumer-delta", "consumer-epsilon"}
|
||
for _, consumer := range consumers {
|
||
host, status := test.NewTestHost(multiTokenOpenAIConfig)
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/v1/responses"},
|
||
{":method", "POST"},
|
||
{"Content-Type", "application/json"},
|
||
{"x-mse-consumer", consumer},
|
||
})
|
||
|
||
require.Equal(t, types.HeaderStopIteration, action)
|
||
|
||
requestHeaders := host.GetRequestHeaders()
|
||
authValue, _ := test.GetHeaderValue(requestHeaders, "Authorization")
|
||
tokens[consumer] = authValue
|
||
|
||
host.Reset()
|
||
}
|
||
|
||
// 验证至少使用了多个不同的 token(hash 分布)
|
||
uniqueTokens := make(map[string]bool)
|
||
for _, token := range tokens {
|
||
uniqueTokens[token] = true
|
||
}
|
||
require.GreaterOrEqual(t, len(uniqueTokens), 2, "Different consumers should use at least 2 different tokens")
|
||
})
|
||
})
|
||
}
|