Files
higress/plugins/wasm-go/extensions/api-workflow/main_test.go

436 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2024 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
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"
)
// 测试配置:基本工作流配置
var basicWorkflowConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"env": map[string]interface{}{
"timeout": 5000,
"max_depth": 100,
},
"workflow": map[string]interface{}{
"edges": []map[string]interface{}{
{
"source": "start",
"target": "A",
},
{
"source": "A",
"target": "end",
},
},
"nodes": []map[string]interface{}{
{
"name": "A",
"service_name": "test-service.static",
"service_port": 80,
"service_path": "/api/test",
"service_method": "POST",
"service_body_tmpl": map[string]interface{}{
"message": "hello",
"data": "",
},
"service_body_replace_keys": []map[string]interface{}{
{
"from": "start||message",
"to": "data",
},
},
"service_headers": []map[string]interface{}{
{
"key": "Content-Type",
"value": "application/json",
},
},
},
},
},
})
return data
}()
// 测试配置:条件分支工作流配置
var conditionalWorkflowConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"env": map[string]interface{}{
"timeout": 3000,
"max_depth": 50,
},
"workflow": map[string]interface{}{
"edges": []map[string]interface{}{
{
"source": "start",
"target": "A",
},
{
"source": "A",
"target": "end",
"conditional": "gt {{A||score}} 0.5",
},
{
"source": "A",
"target": "B",
"conditional": "lt {{A||score}} 0.5",
},
{
"source": "B",
"target": "end",
},
},
"nodes": []map[string]interface{}{
{
"name": "A",
"service_name": "service-a.static",
"service_port": 80,
"service_path": "/api/score",
"service_method": "GET",
},
{
"name": "B",
"service_name": "service-b.static",
"service_port": 80,
"service_path": "/api/fallback",
"service_method": "POST",
"service_body_tmpl": map[string]interface{}{
"fallback": "default",
},
},
},
},
})
return data
}()
// 测试配置:并行执行工作流配置
var parallelWorkflowConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"env": map[string]interface{}{
"timeout": 5000,
"max_depth": 100,
},
"workflow": map[string]interface{}{
"edges": []map[string]interface{}{
{
"source": "start",
"target": "A",
},
{
"source": "start",
"target": "B",
},
{
"source": "start",
"target": "C",
},
{
"source": "A",
"target": "D",
},
{
"source": "B",
"target": "D",
},
{
"source": "C",
"target": "D",
},
{
"source": "D",
"target": "end",
},
},
"nodes": []map[string]interface{}{
{
"name": "A",
"service_name": "service-a.static",
"service_port": 80,
"service_path": "/api/a",
"service_method": "GET",
},
{
"name": "B",
"service_name": "service-b.static",
"service_port": 80,
"service_path": "/api/b",
"service_method": "GET",
},
{
"name": "C",
"service_name": "service-c.static",
"service_port": 80,
"service_path": "/api/c",
"service_method": "GET",
},
{
"name": "D",
"service_name": "service-d.static",
"service_port": 80,
"service_path": "/api/d",
"service_method": "POST",
"service_body_tmpl": map[string]interface{}{
"a_result": "",
"b_result": "",
"c_result": "",
},
"service_body_replace_keys": []map[string]interface{}{
{
"from": "A||result",
"to": "a_result",
},
{
"from": "B||result",
"to": "b_result",
},
{
"from": "C||result",
"to": "c_result",
},
},
},
},
},
})
return data
}()
// 测试配置continue 工作流配置
var continueWorkflowConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"env": map[string]interface{}{
"timeout": 5000,
"max_depth": 100,
},
"workflow": map[string]interface{}{
"edges": []map[string]interface{}{
{
"source": "start",
"target": "A",
},
{
"source": "A",
"target": "continue",
},
},
"nodes": []map[string]interface{}{
{
"name": "A",
"service_name": "service-a.static",
"service_port": 80,
"service_path": "/api/process",
"service_method": "POST",
"service_body_tmpl": map[string]interface{}{
"processed": true,
},
},
},
},
})
return data
}()
func TestParseConfig(t *testing.T) {
test.RunGoTest(t, func(t *testing.T) {
// 测试基本工作流配置解析
t.Run("basic workflow config", func(t *testing.T) {
host, status := test.NewTestHost(basicWorkflowConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
})
// 测试条件分支工作流配置解析
t.Run("conditional workflow config", func(t *testing.T) {
host, status := test.NewTestHost(conditionalWorkflowConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
})
// 测试并行执行工作流配置解析
t.Run("parallel workflow config", func(t *testing.T) {
host, status := test.NewTestHost(parallelWorkflowConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
})
// 测试 continue 工作流配置解析
t.Run("continue workflow config", func(t *testing.T) {
host, status := test.NewTestHost(continueWorkflowConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
})
})
}
func TestOnHttpRequestBody(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
// 测试基本工作流执行
t.Run("basic workflow execution", func(t *testing.T) {
host, status := test.NewTestHost(basicWorkflowConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求体
requestBody := []byte(`{"message": "test message"}`)
action := host.CallOnHttpRequestBody(requestBody)
// 应该返回 ActionPause因为需要等待外部 HTTP 调用完成
require.Equal(t, types.ActionPause, action)
// 模拟外部服务的 HTTP 调用响应
// 模拟成功响应
host.CallOnHttpCall([][2]string{
{"Content-Type", "application/json"},
{":status", "200"},
}, []byte(`{"result": "success", "data": "processed"}`))
// 检查插件的响应状态
localResponse := host.GetLocalResponse()
require.NotNil(t, localResponse)
// 如果插件发送了响应,验证响应内容
require.Equal(t, uint32(200), localResponse.StatusCode)
require.Contains(t, string(localResponse.Data), "success")
host.CompleteHttp()
})
// 测试条件分支工作流执行
t.Run("conditional workflow execution", func(t *testing.T) {
host, status := test.NewTestHost(conditionalWorkflowConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求体
requestBody := []byte(`{"input": "test"}`)
action := host.CallOnHttpRequestBody(requestBody)
// 应该返回 ActionPause因为需要等待外部 HTTP 调用完成
require.Equal(t, types.ActionPause, action)
// 模拟外部服务的 HTTP 调用响应
// 模拟成功响应
host.CallOnHttpCall([][2]string{
{"Content-Type", "application/json"},
{":status", "200"},
}, []byte(`{"score": 0.8}`))
// 检查插件的响应状态
localResponse := host.GetLocalResponse()
require.NotNil(t, localResponse)
// 如果插件发送了响应,验证响应内容
require.Equal(t, uint32(200), localResponse.StatusCode)
host.CompleteHttp()
})
// 测试并行执行工作流执行
t.Run("parallel workflow execution", func(t *testing.T) {
host, status := test.NewTestHost(parallelWorkflowConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求体
requestBody := []byte(`{"data": "test data"}`)
action := host.CallOnHttpRequestBody(requestBody)
// 应该返回 ActionPause因为需要等待外部 HTTP 调用完成
require.Equal(t, types.ActionPause, action)
// 模拟外部服务的 HTTP 调用响应
// 模拟 A 服务的响应
host.CallOnHttpCall([][2]string{
{"Content-Type", "application/json"},
{":status", "200"},
}, []byte(`{"result": "a_result"}`))
// 模拟 B 服务的响应
host.CallOnHttpCall([][2]string{
{"Content-Type", "application/json"},
{":status", "200"},
}, []byte(`{"result": "b_result"}`))
// 模拟 C 服务的响应
host.CallOnHttpCall([][2]string{
{"Content-Type", "application/json"},
{":status", "200"},
}, []byte(`{"result": "c_result"}`))
// 模拟 D 服务的响应(这是汇聚节点)
host.CallOnHttpCall([][2]string{
{"Content-Type", "application/json"},
{":status", "200"},
}, []byte(`{"final_result": "success"}`))
// 检查插件的响应状态
localResponse := host.GetLocalResponse()
require.NotNil(t, localResponse)
// 如果插件发送了响应,验证响应内容
require.Equal(t, uint32(200), localResponse.StatusCode)
host.CompleteHttp()
})
// 测试 continue 工作流执行
t.Run("continue workflow execution", func(t *testing.T) {
host, status := test.NewTestHost(continueWorkflowConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求体
requestBody := []byte(`{"process": true}`)
action := host.CallOnHttpRequestBody(requestBody)
// 应该返回 ActionPause因为需要等待外部 HTTP 调用完成
require.Equal(t, types.ActionPause, action)
// 模拟外部服务的 HTTP 调用响应
// 模拟成功响应
host.CallOnHttpCall([][2]string{
{"Content-Type", "application/json"},
{":status", "200"},
}, []byte(`{"processed": true, "status": "success"}`))
// 检查插件的响应状态
action = host.GetHttpStreamAction()
require.Equal(t, types.ActionContinue, action)
host.CompleteHttp()
})
})
}