mirror of
https://github.com/alibaba/higress.git
synced 2026-03-07 01:50:51 +08:00
528 lines
17 KiB
Go
528 lines
17 KiB
Go
package test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
|
"github.com/higress-group/wasm-go/pkg/test"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Test config: Basic Bedrock config with AWS Access Key/Secret Key (AWS Signature V4)
|
|
var basicBedrockConfig = func() json.RawMessage {
|
|
data, _ := json.Marshal(map[string]interface{}{
|
|
"provider": map[string]interface{}{
|
|
"type": "bedrock",
|
|
"awsAccessKey": "test-ak-for-unit-test",
|
|
"awsSecretKey": "test-sk-for-unit-test",
|
|
"awsRegion": "us-east-1",
|
|
"modelMapping": map[string]string{
|
|
"*": "anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
},
|
|
},
|
|
})
|
|
return data
|
|
}()
|
|
|
|
// Test config: Bedrock config with Bearer Token authentication
|
|
var bedrockApiTokenConfig = func() json.RawMessage {
|
|
data, _ := json.Marshal(map[string]interface{}{
|
|
"provider": map[string]interface{}{
|
|
"type": "bedrock",
|
|
"apiTokens": []string{
|
|
"test-token-for-unit-test",
|
|
},
|
|
"awsRegion": "us-east-1",
|
|
"modelMapping": map[string]string{
|
|
"*": "anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
},
|
|
},
|
|
})
|
|
return data
|
|
}()
|
|
|
|
// Test config: Bedrock config with multiple Bearer Tokens
|
|
var bedrockMultiTokenConfig = func() json.RawMessage {
|
|
data, _ := json.Marshal(map[string]interface{}{
|
|
"provider": map[string]interface{}{
|
|
"type": "bedrock",
|
|
"apiTokens": []string{
|
|
"test-token-1-for-unit-test",
|
|
"test-token-2-for-unit-test",
|
|
},
|
|
"awsRegion": "us-west-2",
|
|
"modelMapping": map[string]string{
|
|
"gpt-4": "anthropic.claude-3-opus-20240229-v1:0",
|
|
"*": "anthropic.claude-3-haiku-20240307-v1:0",
|
|
},
|
|
},
|
|
})
|
|
return data
|
|
}()
|
|
|
|
// Test config: Bedrock config with additional fields
|
|
var bedrockWithAdditionalFieldsConfig = func() json.RawMessage {
|
|
data, _ := json.Marshal(map[string]interface{}{
|
|
"provider": map[string]interface{}{
|
|
"type": "bedrock",
|
|
"awsAccessKey": "test-ak-for-unit-test",
|
|
"awsSecretKey": "test-sk-for-unit-test",
|
|
"awsRegion": "us-east-1",
|
|
"bedrockAdditionalFields": map[string]interface{}{
|
|
"top_k": 200,
|
|
},
|
|
"modelMapping": map[string]string{
|
|
"*": "anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
},
|
|
},
|
|
})
|
|
return data
|
|
}()
|
|
|
|
// Test config: Invalid config - missing both apiTokens and ak/sk
|
|
var bedrockInvalidConfigMissingAuth = func() json.RawMessage {
|
|
data, _ := json.Marshal(map[string]interface{}{
|
|
"provider": map[string]interface{}{
|
|
"type": "bedrock",
|
|
"awsRegion": "us-east-1",
|
|
"modelMapping": map[string]string{
|
|
"*": "anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
},
|
|
},
|
|
})
|
|
return data
|
|
}()
|
|
|
|
// Test config: Invalid config - missing region
|
|
var bedrockInvalidConfigMissingRegion = func() json.RawMessage {
|
|
data, _ := json.Marshal(map[string]interface{}{
|
|
"provider": map[string]interface{}{
|
|
"type": "bedrock",
|
|
"apiTokens": []string{
|
|
"test-token-for-unit-test",
|
|
},
|
|
"modelMapping": map[string]string{
|
|
"*": "anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
},
|
|
},
|
|
})
|
|
return data
|
|
}()
|
|
|
|
// Test config: Invalid config - only has access key without secret key
|
|
var bedrockInvalidConfigPartialAkSk = func() json.RawMessage {
|
|
data, _ := json.Marshal(map[string]interface{}{
|
|
"provider": map[string]interface{}{
|
|
"type": "bedrock",
|
|
"awsAccessKey": "test-ak-for-unit-test",
|
|
"awsRegion": "us-east-1",
|
|
"modelMapping": map[string]string{
|
|
"*": "anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
},
|
|
},
|
|
})
|
|
return data
|
|
}()
|
|
|
|
func RunBedrockParseConfigTests(t *testing.T) {
|
|
test.RunGoTest(t, func(t *testing.T) {
|
|
// Test basic Bedrock config with AWS Signature V4 authentication
|
|
t.Run("basic bedrock config with ak/sk", func(t *testing.T) {
|
|
host, status := test.NewTestHost(basicBedrockConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
config, err := host.GetMatchConfig()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
})
|
|
|
|
// Test Bedrock config with Bearer Token authentication
|
|
t.Run("bedrock config with api token", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockApiTokenConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
config, err := host.GetMatchConfig()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
})
|
|
|
|
// Test Bedrock config with multiple tokens
|
|
t.Run("bedrock config with multiple tokens", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockMultiTokenConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
config, err := host.GetMatchConfig()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
})
|
|
|
|
// Test Bedrock config with additional fields
|
|
t.Run("bedrock config with additional fields", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockWithAdditionalFieldsConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
config, err := host.GetMatchConfig()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
})
|
|
|
|
// Test invalid config - missing authentication
|
|
t.Run("bedrock invalid config missing auth", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockInvalidConfigMissingAuth)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusFailed, status)
|
|
})
|
|
|
|
// Test invalid config - missing region
|
|
t.Run("bedrock invalid config missing region", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockInvalidConfigMissingRegion)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusFailed, status)
|
|
})
|
|
|
|
// Test invalid config - partial ak/sk (only access key, no secret key)
|
|
t.Run("bedrock invalid config partial ak/sk", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockInvalidConfigPartialAkSk)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusFailed, status)
|
|
})
|
|
})
|
|
}
|
|
|
|
func RunBedrockOnHttpRequestHeadersTests(t *testing.T) {
|
|
test.RunTest(t, func(t *testing.T) {
|
|
// Test Bedrock request headers processing with AWS Signature V4
|
|
t.Run("bedrock chat completion request headers with ak/sk", func(t *testing.T) {
|
|
host, status := test.NewTestHost(basicBedrockConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
// Set request headers
|
|
action := host.CallOnHttpRequestHeaders([][2]string{
|
|
{":authority", "example.com"},
|
|
{":path", "/v1/chat/completions"},
|
|
{":method", "POST"},
|
|
{"Content-Type", "application/json"},
|
|
})
|
|
|
|
require.Equal(t, types.HeaderStopIteration, action)
|
|
|
|
// Verify request headers
|
|
requestHeaders := host.GetRequestHeaders()
|
|
require.NotNil(t, requestHeaders)
|
|
|
|
// Verify Host is changed to Bedrock service domain
|
|
hostValue, hasHost := test.GetHeaderValue(requestHeaders, ":authority")
|
|
require.True(t, hasHost, "Host header should exist")
|
|
require.Contains(t, hostValue, "bedrock-runtime.us-east-1.amazonaws.com", "Host should be changed to Bedrock service domain")
|
|
})
|
|
|
|
// Test Bedrock request headers processing with Bearer Token
|
|
t.Run("bedrock chat completion request headers with api token", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockApiTokenConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
// Set request headers
|
|
action := host.CallOnHttpRequestHeaders([][2]string{
|
|
{":authority", "example.com"},
|
|
{":path", "/v1/chat/completions"},
|
|
{":method", "POST"},
|
|
{"Content-Type", "application/json"},
|
|
})
|
|
|
|
require.Equal(t, types.HeaderStopIteration, action)
|
|
|
|
// Verify request headers
|
|
requestHeaders := host.GetRequestHeaders()
|
|
require.NotNil(t, requestHeaders)
|
|
|
|
// Verify Host is changed to Bedrock service domain
|
|
hostValue, hasHost := test.GetHeaderValue(requestHeaders, ":authority")
|
|
require.True(t, hasHost, "Host header should exist")
|
|
require.Contains(t, hostValue, "bedrock-runtime.us-east-1.amazonaws.com", "Host should be changed to Bedrock service domain")
|
|
})
|
|
})
|
|
}
|
|
|
|
func RunBedrockOnHttpRequestBodyTests(t *testing.T) {
|
|
test.RunTest(t, func(t *testing.T) {
|
|
// Test Bedrock request body processing with Bearer Token authentication
|
|
t.Run("bedrock chat completion request body with api token", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockApiTokenConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
// Set request headers
|
|
action := host.CallOnHttpRequestHeaders([][2]string{
|
|
{":authority", "example.com"},
|
|
{":path", "/v1/chat/completions"},
|
|
{":method", "POST"},
|
|
{"Content-Type", "application/json"},
|
|
})
|
|
require.Equal(t, types.HeaderStopIteration, action)
|
|
|
|
// Set request body
|
|
requestBody := `{
|
|
"model": "gpt-4",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": "Hello, how are you?"
|
|
}
|
|
],
|
|
"temperature": 0.7
|
|
}`
|
|
|
|
action = host.CallOnHttpRequestBody([]byte(requestBody))
|
|
require.Equal(t, types.ActionContinue, action)
|
|
|
|
// Verify request headers for Bearer Token authentication
|
|
requestHeaders := host.GetRequestHeaders()
|
|
require.NotNil(t, requestHeaders)
|
|
|
|
// Verify Authorization header uses Bearer token
|
|
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
|
|
require.True(t, hasAuth, "Authorization header should exist")
|
|
require.Contains(t, authValue, "Bearer ", "Authorization should use Bearer token")
|
|
require.Contains(t, authValue, "test-token-for-unit-test", "Authorization should contain the configured token")
|
|
|
|
// Verify path is transformed to Bedrock format
|
|
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
|
|
require.True(t, hasPath, "Path header should exist")
|
|
require.Contains(t, pathValue, "/model/", "Path should contain Bedrock model path")
|
|
require.Contains(t, pathValue, "/converse", "Path should contain converse endpoint")
|
|
})
|
|
|
|
// Test Bedrock request body processing with AWS Signature V4 authentication
|
|
t.Run("bedrock chat completion request body with ak/sk", func(t *testing.T) {
|
|
host, status := test.NewTestHost(basicBedrockConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
// Set request headers
|
|
action := host.CallOnHttpRequestHeaders([][2]string{
|
|
{":authority", "example.com"},
|
|
{":path", "/v1/chat/completions"},
|
|
{":method", "POST"},
|
|
{"Content-Type", "application/json"},
|
|
})
|
|
require.Equal(t, types.HeaderStopIteration, action)
|
|
|
|
// Set request body
|
|
requestBody := `{
|
|
"model": "gpt-4",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": "Hello, how are you?"
|
|
}
|
|
],
|
|
"temperature": 0.7
|
|
}`
|
|
|
|
action = host.CallOnHttpRequestBody([]byte(requestBody))
|
|
require.Equal(t, types.ActionContinue, action)
|
|
|
|
// Verify request headers for AWS Signature V4 authentication
|
|
requestHeaders := host.GetRequestHeaders()
|
|
require.NotNil(t, requestHeaders)
|
|
|
|
// Verify Authorization header uses AWS Signature
|
|
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
|
|
require.True(t, hasAuth, "Authorization header should exist")
|
|
require.Contains(t, authValue, "AWS4-HMAC-SHA256", "Authorization should use AWS4-HMAC-SHA256 signature")
|
|
require.Contains(t, authValue, "Credential=", "Authorization should contain Credential")
|
|
require.Contains(t, authValue, "Signature=", "Authorization should contain Signature")
|
|
|
|
// Verify X-Amz-Date header exists
|
|
dateValue, hasDate := test.GetHeaderValue(requestHeaders, "X-Amz-Date")
|
|
require.True(t, hasDate, "X-Amz-Date header should exist for AWS Signature V4")
|
|
require.NotEmpty(t, dateValue, "X-Amz-Date should not be empty")
|
|
|
|
// Verify path is transformed to Bedrock format
|
|
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
|
|
require.True(t, hasPath, "Path header should exist")
|
|
require.Contains(t, pathValue, "/model/", "Path should contain Bedrock model path")
|
|
require.Contains(t, pathValue, "/converse", "Path should contain converse endpoint")
|
|
})
|
|
|
|
// Test Bedrock streaming request
|
|
t.Run("bedrock streaming request", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockApiTokenConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
// Set request headers
|
|
action := host.CallOnHttpRequestHeaders([][2]string{
|
|
{":authority", "example.com"},
|
|
{":path", "/v1/chat/completions"},
|
|
{":method", "POST"},
|
|
{"Content-Type", "application/json"},
|
|
})
|
|
require.Equal(t, types.HeaderStopIteration, action)
|
|
|
|
// Set streaming request body
|
|
requestBody := `{
|
|
"model": "gpt-4",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": "Hello"
|
|
}
|
|
],
|
|
"stream": true
|
|
}`
|
|
|
|
action = host.CallOnHttpRequestBody([]byte(requestBody))
|
|
require.Equal(t, types.ActionContinue, action)
|
|
|
|
// Verify path is transformed to Bedrock streaming format
|
|
requestHeaders := host.GetRequestHeaders()
|
|
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
|
|
require.True(t, hasPath, "Path header should exist")
|
|
require.Contains(t, pathValue, "/model/", "Path should contain Bedrock model path")
|
|
require.Contains(t, pathValue, "/converse-stream", "Path should contain converse-stream endpoint for streaming")
|
|
})
|
|
})
|
|
}
|
|
|
|
func RunBedrockOnHttpResponseHeadersTests(t *testing.T) {
|
|
test.RunTest(t, func(t *testing.T) {
|
|
// Test Bedrock response headers processing
|
|
t.Run("bedrock response headers", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockApiTokenConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
// Set request headers
|
|
action := host.CallOnHttpRequestHeaders([][2]string{
|
|
{":authority", "example.com"},
|
|
{":path", "/v1/chat/completions"},
|
|
{":method", "POST"},
|
|
{"Content-Type", "application/json"},
|
|
})
|
|
require.Equal(t, types.HeaderStopIteration, action)
|
|
|
|
// Set request body
|
|
requestBody := `{
|
|
"model": "gpt-4",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": "Hello"
|
|
}
|
|
]
|
|
}`
|
|
action = host.CallOnHttpRequestBody([]byte(requestBody))
|
|
require.Equal(t, types.ActionContinue, action)
|
|
|
|
// Process response headers
|
|
action = host.CallOnHttpResponseHeaders([][2]string{
|
|
{":status", "200"},
|
|
{"Content-Type", "application/json"},
|
|
{"X-Amzn-Requestid", "test-request-id-12345"},
|
|
})
|
|
require.Equal(t, types.ActionContinue, action)
|
|
|
|
// Verify response headers
|
|
responseHeaders := host.GetResponseHeaders()
|
|
require.NotNil(t, responseHeaders)
|
|
|
|
// Verify status code
|
|
statusValue, hasStatus := test.GetHeaderValue(responseHeaders, ":status")
|
|
require.True(t, hasStatus, "Status header should exist")
|
|
require.Equal(t, "200", statusValue, "Status should be 200")
|
|
})
|
|
})
|
|
}
|
|
|
|
func RunBedrockOnHttpResponseBodyTests(t *testing.T) {
|
|
test.RunTest(t, func(t *testing.T) {
|
|
// Test Bedrock response body processing
|
|
t.Run("bedrock response body", func(t *testing.T) {
|
|
host, status := test.NewTestHost(bedrockApiTokenConfig)
|
|
defer host.Reset()
|
|
require.Equal(t, types.OnPluginStartStatusOK, status)
|
|
|
|
// Set request headers
|
|
action := host.CallOnHttpRequestHeaders([][2]string{
|
|
{":authority", "example.com"},
|
|
{":path", "/v1/chat/completions"},
|
|
{":method", "POST"},
|
|
{"Content-Type", "application/json"},
|
|
})
|
|
require.Equal(t, types.HeaderStopIteration, action)
|
|
|
|
// Set request body
|
|
requestBody := `{
|
|
"model": "gpt-4",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": "Hello"
|
|
}
|
|
]
|
|
}`
|
|
action = host.CallOnHttpRequestBody([]byte(requestBody))
|
|
require.Equal(t, types.ActionContinue, action)
|
|
|
|
// Set response property to ensure IsResponseFromUpstream() returns true
|
|
host.SetProperty([]string{"response", "code_details"}, []byte("via_upstream"))
|
|
|
|
// Process response headers (must include :status 200 for body processing)
|
|
action = host.CallOnHttpResponseHeaders([][2]string{
|
|
{":status", "200"},
|
|
{"Content-Type", "application/json"},
|
|
})
|
|
require.Equal(t, types.ActionContinue, action)
|
|
|
|
// Process response body (Bedrock format)
|
|
responseBody := `{
|
|
"output": {
|
|
"message": {
|
|
"role": "assistant",
|
|
"content": [
|
|
{
|
|
"text": "Hello! How can I help you today?"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"stopReason": "end_turn",
|
|
"usage": {
|
|
"inputTokens": 10,
|
|
"outputTokens": 15,
|
|
"totalTokens": 25
|
|
}
|
|
}`
|
|
|
|
action = host.CallOnHttpResponseBody([]byte(responseBody))
|
|
require.Equal(t, types.ActionContinue, action)
|
|
|
|
// Verify response body is transformed to OpenAI format
|
|
transformedResponseBody := host.GetResponseBody()
|
|
require.NotNil(t, transformedResponseBody)
|
|
|
|
var responseMap map[string]interface{}
|
|
err := json.Unmarshal(transformedResponseBody, &responseMap)
|
|
require.NoError(t, err)
|
|
|
|
// Verify choices exist in transformed response
|
|
choices, exists := responseMap["choices"]
|
|
require.True(t, exists, "Choices should exist in response body")
|
|
require.NotNil(t, choices, "Choices should not be nil")
|
|
|
|
// Verify usage exists
|
|
usage, exists := responseMap["usage"]
|
|
require.True(t, exists, "Usage should exist in response body")
|
|
require.NotNil(t, usage, "Usage should not be nil")
|
|
})
|
|
})
|
|
}
|