Files
higress/plugins/wasm-go/extensions/ai-proxy/test/kling.go
2026-05-14 16:18:00 +08:00

419 lines
15 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"
"github.com/tidwall/gjson"
)
var klingOfficialConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"provider": map[string]interface{}{
"type": "kling",
"klingAccessKey": "kling-ak-test",
"klingSecretKey": "kling-sk-test",
"klingTokenRefreshAhead": 60,
"modelMapping": map[string]string{
"client-video": "kling-v2-1",
},
},
})
return data
}()
var klingGatewayConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"provider": map[string]interface{}{
"type": "kling",
"apiTokens": []string{"gateway-token"},
"providerDomain": "api.302.ai",
"providerBasePath": "/klingai",
"modelMapping": map[string]string{
"client-video": "kling-v2-1",
},
},
})
return data
}()
var klingGatewayCustomImageRetrieveConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"provider": map[string]interface{}{
"type": "kling",
"apiTokens": []string{"gateway-token"},
"providerDomain": "api.302.ai",
"providerBasePath": "/klingai",
"modelMapping": map[string]string{
"client-video": "kling-v2-1",
},
"capabilities": map[string]string{
"openai/v1/videos": "/gateway/text2video?mode=text",
"kling/v1/image2video": "/gateway/image2video?mode=image",
"kling/v1/retrieveimagevideo": "/gateway/image-tasks/{video_id}?version=1",
},
},
})
return data
}()
var klingOriginalConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"provider": map[string]interface{}{
"type": "kling",
"apiTokens": []string{"gateway-token"},
"providerDomain": "api.302.ai",
"providerBasePath": "/klingai",
"protocol": "original",
},
})
return data
}()
func RunKlingParseConfigTests(t *testing.T) {
test.RunGoTest(t, func(t *testing.T) {
t.Run("kling official config", func(t *testing.T) {
host, status := test.NewTestHost(klingOfficialConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
})
t.Run("kling gateway config", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
})
})
}
func RunKlingOnHttpRequestHeadersTests(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
t.Run("official mode sets jwt bearer and default host", func(t *testing.T) {
host, status := test.NewTestHost(klingOfficialConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
require.Equal(t, types.HeaderStopIteration, action)
requestHeaders := host.GetRequestHeaders()
hostValue, hasHost := test.GetHeaderValue(requestHeaders, ":authority")
require.True(t, hasHost)
require.Equal(t, "api-singapore.klingai.com", hostValue)
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
require.True(t, hasAuth)
require.True(t, strings.HasPrefix(authValue, "Bearer "))
require.Len(t, strings.Split(strings.TrimPrefix(authValue, "Bearer "), "."), 3)
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/v1/videos/text2video", pathValue)
})
t.Run("providerDomain and providerBasePath apply to gateway mode", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
require.Equal(t, types.HeaderStopIteration, action)
requestHeaders := host.GetRequestHeaders()
hostValue, hasHost := test.GetHeaderValue(requestHeaders, ":authority")
require.True(t, hasHost)
require.Equal(t, "api.302.ai", hostValue)
authValue, hasAuth := test.GetHeaderValue(requestHeaders, "Authorization")
require.True(t, hasAuth)
require.Equal(t, "Bearer gateway-token", authValue)
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/v1/videos/text2video", pathValue)
})
t.Run("retrieve video query path is mapped under providerBasePath", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos/task-123?with_status=true"},
{":method", "GET"},
})
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/v1/videos/text2video/task-123?with_status=true", pathValue)
})
t.Run("prefixed image task query path is mapped to image endpoint", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos/kling-i2v-task-123?with_status=true"},
{":method", "GET"},
})
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/v1/videos/image2video/task-123?with_status=true", pathValue)
})
t.Run("prefixed image task query strips task type hint", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos/kling-i2v-task-123?kling_task_type=image2video&with_status=true"},
{":method", "GET"},
})
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/v1/videos/image2video/task-123?with_status=true", pathValue)
})
t.Run("raw image task query path uses explicit task type hint", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos/raw-task-123?kling_task_type=image2video&with_status=true"},
{":method", "GET"},
})
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/v1/videos/image2video/raw-task-123?with_status=true", pathValue)
})
t.Run("raw retrieve strips unknown task type hint before fallback mapping", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos/raw-task-123?kling_task_type=bad&with_status=true"},
{":method", "GET"},
})
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/v1/videos/text2video/raw-task-123?with_status=true", pathValue)
})
t.Run("image retrieve path uses configured image capability", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayCustomImageRetrieveConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos/kling-i2v-task-123?with_status=true"},
{":method", "GET"},
})
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/gateway/image-tasks/task-123?version=1&with_status=true", pathValue)
})
})
}
func RunKlingOnHttpRequestBodyTests(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
t.Run("text to video keeps text endpoint and maps model_name", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos?gateway_param=1"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
action := host.CallOnHttpRequestBody([]byte(`{"model":"client-video","prompt":"sunrise","duration":"5"}`))
require.Equal(t, types.ActionContinue, action)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/v1/videos/text2video?gateway_param=1", pathValue)
processedBody := host.GetRequestBody()
require.Equal(t, "kling-v2-1", gjson.GetBytes(processedBody, "model_name").String())
require.False(t, gjson.GetBytes(processedBody, "model").Exists())
require.Equal(t, "sunrise", gjson.GetBytes(processedBody, "prompt").String())
})
t.Run("image to video switches endpoint after body inspection", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos?gateway_param=1"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
action := host.CallOnHttpRequestBody([]byte(`{"model":"client-video","prompt":"animate","image":"https://example.com/a.png"}`))
require.Equal(t, types.ActionContinue, action)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/v1/videos/image2video?gateway_param=1", pathValue)
processedBody := host.GetRequestBody()
require.Equal(t, "kling-v2-1", gjson.GetBytes(processedBody, "model_name").String())
require.Equal(t, "https://example.com/a.png", gjson.GetBytes(processedBody, "image").String())
})
t.Run("image to video uses configured image capability and merges query", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayCustomImageRetrieveConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos?gateway_param=1"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
action := host.CallOnHttpRequestBody([]byte(`{"model":"client-video","prompt":"animate","image":"https://example.com/a.png"}`))
require.Equal(t, types.ActionContinue, action)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/gateway/image2video?mode=image&gateway_param=1", pathValue)
processedBody := host.GetRequestBody()
require.Equal(t, "kling-v2-1", gjson.GetBytes(processedBody, "model_name").String())
})
t.Run("original protocol does not expose request body handler", func(t *testing.T) {
host, status := test.NewTestHost(klingOriginalConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos/image2video"},
{":method", "POST"},
{"Content-Type", "application/json"},
{"Content-Length", "64"},
})
require.Equal(t, types.ActionContinue, action)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/v1/videos/image2video", pathValue)
contentLengthValue, hasContentLength := test.GetHeaderValue(requestHeaders, "Content-Length")
require.True(t, hasContentLength)
require.Equal(t, "64", contentLengthValue)
})
t.Run("original protocol recognizes native retrieve video path", func(t *testing.T) {
host, status := test.NewTestHost(klingOriginalConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos/text2video/task-123"},
{":method", "GET"},
})
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
requestHeaders := host.GetRequestHeaders()
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
require.True(t, hasPath)
require.Equal(t, "/klingai/v1/videos/text2video/task-123", pathValue)
})
})
}
func RunKlingOnHttpResponseBodyTests(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
t.Run("image creation response prefixes task id", func(t *testing.T) {
host, status := test.NewTestHost(klingGatewayConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/videos"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
action := host.CallOnHttpRequestBody([]byte(`{"model":"client-video","prompt":"animate","image":"https://example.com/a.png"}`))
require.Equal(t, types.ActionContinue, action)
require.NoError(t, host.SetProperty([]string{"response", "code_details"}, []byte("via_upstream")))
action = host.CallOnHttpResponseHeaders([][2]string{
{":status", "200"},
{"Content-Type", "application/json"},
})
require.Equal(t, types.ActionContinue, action)
action = host.CallOnHttpResponseBody([]byte(`{"id":"root-task","data":{"task_id":"task-123"}}`))
require.Equal(t, types.ActionContinue, action)
processedBody := host.GetResponseBody()
require.Equal(t, "root-task", gjson.GetBytes(processedBody, "id").String())
require.Equal(t, "kling-i2v-task-123", gjson.GetBytes(processedBody, "data.task_id").String())
})
})
}