上下文传递
This commit is contained in:
parent
4dcbef8307
commit
eba56bc756
255
docs/activity_parameter_passing.md
Normal file
255
docs/activity_parameter_passing.md
Normal file
@ -0,0 +1,255 @@
|
||||
# Activity 之间参数传递指南
|
||||
|
||||
## 概述
|
||||
|
||||
在动态测试工作流中,多个 Activity 之间经常需要传递参数。例如,第二个 Activity 可能需要使用第一个 Activity 响应的某个字段作为入参。本文档详细说明了如何实现这种参数传递机制。
|
||||
|
||||
## 支持的变量格式
|
||||
|
||||
### 1. 全局变量
|
||||
格式:`${global.key}`
|
||||
- 用于访问工作流级别的全局变量
|
||||
- 这些变量在工作流启动时通过 `GlobalParameters` 传入
|
||||
|
||||
### 2. 步骤结果变量
|
||||
格式:`${step.stepId.field}`
|
||||
- 用于访问之前步骤的执行结果
|
||||
- `stepId` 是步骤的ID
|
||||
- `field` 是结果中的具体字段
|
||||
|
||||
## 变量引用示例
|
||||
|
||||
### API 测试结果变量
|
||||
```json
|
||||
{
|
||||
"test_case_id": "api_test_001",
|
||||
"endpoint": "/api/login",
|
||||
"http_method": "POST",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"request_body": "{\"username\": \"testuser\", \"password\": \"testpass\"}",
|
||||
"expected_status_code": 200
|
||||
}
|
||||
```
|
||||
|
||||
假设这个API测试的步骤ID是123,执行后返回:
|
||||
```json
|
||||
{
|
||||
"base_result": {
|
||||
"success": true,
|
||||
"message": "API Test Passed"
|
||||
},
|
||||
"actual_status_code": 200,
|
||||
"response_body": "{\"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\", \"user_id\": 456}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 可用的变量引用
|
||||
|
||||
1. **响应体**:`${step.123.response_body}`
|
||||
2. **状态码**:`${step.123.actual_status_code}`
|
||||
3. **响应头**:`${step.123.headers.Authorization}`
|
||||
4. **JSON字段**:`${step.123.json_token}` (自动提取的token字段)
|
||||
5. **用户ID**:`${step.123.json_user_id}` (自动提取的user_id字段)
|
||||
|
||||
## 实际使用场景
|
||||
|
||||
### 场景1:登录后使用Token进行API调用
|
||||
|
||||
**步骤1:登录API**
|
||||
```json
|
||||
{
|
||||
"test_case_id": "login_test",
|
||||
"endpoint": "/api/login",
|
||||
"http_method": "POST",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"request_body": "{\"username\": \"testuser\", \"password\": \"testpass\"}",
|
||||
"expected_status_code": 200
|
||||
}
|
||||
```
|
||||
|
||||
**步骤2:使用Token调用受保护的API**
|
||||
```json
|
||||
{
|
||||
"test_case_id": "protected_api_test",
|
||||
"endpoint": "/api/user/profile",
|
||||
"http_method": "GET",
|
||||
"headers": {
|
||||
"Authorization": "${step.123.json_token}",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"expected_status_code": 200
|
||||
}
|
||||
```
|
||||
|
||||
### 场景2:使用响应头中的Token
|
||||
|
||||
```json
|
||||
{
|
||||
"test_case_id": "api_with_header_token",
|
||||
"endpoint": "/api/data",
|
||||
"http_method": "GET",
|
||||
"headers": {
|
||||
"Authorization": "${step.123.headers.Authorization}",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"expected_status_code": 200
|
||||
}
|
||||
```
|
||||
|
||||
### 场景3:使用响应体中的用户ID
|
||||
|
||||
```json
|
||||
{
|
||||
"test_case_id": "user_data_test",
|
||||
"endpoint": "/api/users/${step.123.json_user_id}/data",
|
||||
"http_method": "GET",
|
||||
"headers": {
|
||||
"Authorization": "${step.123.json_token}",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"expected_status_code": 200
|
||||
}
|
||||
```
|
||||
|
||||
## UI 测试结果变量
|
||||
|
||||
### UI 测试结果示例
|
||||
```json
|
||||
{
|
||||
"base_result": {
|
||||
"success": true,
|
||||
"message": "UI Test Passed"
|
||||
},
|
||||
"screenshot_url": "https://s3.amazonaws.com/screenshots/test_001.png",
|
||||
"html_report_url": "https://s3.amazonaws.com/reports/test_001.html"
|
||||
}
|
||||
```
|
||||
|
||||
### 可用的变量引用
|
||||
1. **截图URL**:`${step.456.screenshot_url}`
|
||||
2. **报告URL**:`${step.456.html_report_url}`
|
||||
|
||||
## 全局变量使用
|
||||
|
||||
### 工作流启动时传入全局变量
|
||||
```go
|
||||
input := &pb.DynamicTestRunInput{
|
||||
RunId: "test_run_001",
|
||||
CompositeCaseId: "composite_case_001",
|
||||
GlobalParameters: map[string]string{
|
||||
"base_url": "https://api.example.com",
|
||||
"environment": "staging",
|
||||
"api_version": "v1",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 在步骤参数中使用全局变量
|
||||
```json
|
||||
{
|
||||
"test_case_id": "api_test",
|
||||
"endpoint": "${global.base_url}/${global.api_version}/users",
|
||||
"http_method": "GET",
|
||||
"headers": {
|
||||
"X-Environment": "${global.environment}",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"expected_status_code": 200
|
||||
}
|
||||
```
|
||||
|
||||
## 复杂场景示例
|
||||
|
||||
### 多步骤参数传递链
|
||||
|
||||
**步骤1:用户注册**
|
||||
```json
|
||||
{
|
||||
"test_case_id": "user_registration",
|
||||
"endpoint": "/api/register",
|
||||
"http_method": "POST",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"request_body": "{\"username\": \"newuser\", \"email\": \"newuser@example.com\", \"password\": \"password123\"}",
|
||||
"expected_status_code": 201
|
||||
}
|
||||
```
|
||||
|
||||
**步骤2:用户登录(使用注册返回的用户ID)**
|
||||
```json
|
||||
{
|
||||
"test_case_id": "user_login",
|
||||
"endpoint": "/api/login",
|
||||
"http_method": "POST",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"request_body": "{\"username\": \"newuser\", \"password\": \"password123\"}",
|
||||
"expected_status_code": 200
|
||||
}
|
||||
```
|
||||
|
||||
**步骤3:获取用户信息(使用登录返回的token)**
|
||||
```json
|
||||
{
|
||||
"test_case_id": "get_user_info",
|
||||
"endpoint": "/api/users/${step.123.json_user_id}",
|
||||
"http_method": "GET",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${step.124.json_token}",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"expected_status_code": 200
|
||||
}
|
||||
```
|
||||
|
||||
**步骤4:更新用户信息(使用步骤3的token和用户ID)**
|
||||
```json
|
||||
{
|
||||
"test_case_id": "update_user_info",
|
||||
"endpoint": "/api/users/${step.123.json_user_id}",
|
||||
"http_method": "PUT",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${step.124.json_token}",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"request_body": "{\"display_name\": \"Updated User\", \"bio\": \"This is my updated bio\"}",
|
||||
"expected_status_code": 200
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **变量解析顺序**:先解析全局变量,再解析步骤变量
|
||||
2. **错误处理**:如果变量不存在,会保持原始占位符不变
|
||||
3. **类型安全**:所有变量都会被转换为字符串
|
||||
4. **性能考虑**:变量解析在每次步骤执行时进行,避免频繁的字符串操作
|
||||
5. **调试支持**:工作流日志会记录变量解析过程
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用有意义的变量名**:避免使用过于简单的变量名
|
||||
2. **文档化变量**:在步骤描述中说明使用的变量
|
||||
3. **测试变量解析**:在开发环境中测试变量解析是否正确
|
||||
4. **错误处理**:在步骤参数中提供默认值或错误处理逻辑
|
||||
5. **版本控制**:记录变量格式的变更,确保向后兼容
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 自定义变量提取器
|
||||
可以通过扩展 `extractApiResultToGlobalVariables` 和 `extractUiResultToGlobalVariables` 函数来支持更多自定义字段的提取。
|
||||
|
||||
### 条件变量
|
||||
可以基于步骤的成功/失败状态来设置不同的变量值。
|
||||
|
||||
### 变量验证
|
||||
可以在变量解析时添加验证逻辑,确保必需的变量存在且格式正确。
|
@ -18,7 +18,7 @@ func NewWorkflowHandler() *WorkflowHandler {
|
||||
// StartWorkflow POST /api/workflow/start
|
||||
func (h *WorkflowHandler) StartWorkflow(c *gin.Context) {
|
||||
|
||||
err := h.service.Start("11")
|
||||
err := h.service.Start("13")
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
"code": -1,
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"beacon/models"
|
||||
"beacon/pkg/dao/mysql"
|
||||
"beacon/utils"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@ -63,7 +64,7 @@ func (s *CompositeCaseService) CreateCompositeCase(req *models.CreateCompositeCa
|
||||
StepDescription: stepReq.StepDescription,
|
||||
StepType: stepReq.StepType,
|
||||
ActivityName: activityName,
|
||||
ParametersJson: stepReq.ParametersJson,
|
||||
ParametersJson: fixParametersJson(stepReq.ParametersJson),
|
||||
IsRequired: stepReq.IsRequired,
|
||||
}
|
||||
steps = append(steps, step)
|
||||
@ -187,7 +188,7 @@ func (s *CompositeCaseService) UpdateCompositeCase(id uint, req *models.UpdateCo
|
||||
StepDescription: stepReq.StepDescription,
|
||||
StepType: stepReq.StepType,
|
||||
ActivityName: activityName,
|
||||
ParametersJson: stepReq.ParametersJson,
|
||||
ParametersJson: fixParametersJson(stepReq.ParametersJson),
|
||||
IsRequired: stepReq.IsRequired,
|
||||
}
|
||||
steps = append(steps, step)
|
||||
@ -213,6 +214,28 @@ func (s *CompositeCaseService) UpdateCompositeCase(id uint, req *models.UpdateCo
|
||||
return s.GetCompositeCaseByID(id)
|
||||
}
|
||||
|
||||
func fixParametersJson(jsonStr string) string {
|
||||
var params map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(jsonStr), ¶ms); err != nil {
|
||||
// Not a valid json string, return as is.
|
||||
return jsonStr
|
||||
}
|
||||
|
||||
if rb, ok := params["request_body"]; ok {
|
||||
if rbs, ok := rb.(string); ok && rbs == "{" {
|
||||
params["request_body"] = "{}"
|
||||
}
|
||||
}
|
||||
|
||||
fixedJSONBytes, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
// Failed to marshal back, return original.
|
||||
return jsonStr
|
||||
}
|
||||
|
||||
return string(fixedJSONBytes)
|
||||
}
|
||||
|
||||
// DeleteCompositeCase 删除复合案例
|
||||
func (s *CompositeCaseService) DeleteCompositeCase(id uint) error {
|
||||
zap.L().Info("开始删除复合案例", zap.Uint("id", id))
|
||||
|
484
utils/parameter_processor.go
Normal file
484
utils/parameter_processor.go
Normal file
@ -0,0 +1,484 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"beacon/pkg/pb"
|
||||
)
|
||||
|
||||
// ParameterProcessor 参数处理器,用于处理activity之间的参数传递
|
||||
type ParameterProcessor struct {
|
||||
globalVariables map[string]interface{}
|
||||
activityResults map[int64]*ActivityResult
|
||||
}
|
||||
|
||||
// ActivityResult 用于存储每个activity的执行结果
|
||||
type ActivityResult struct {
|
||||
StepID int64 `json:"step_id"`
|
||||
StepName string `json:"step_name"`
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// NewParameterProcessor 创建新的参数处理器
|
||||
func NewParameterProcessor(globalVariables map[string]interface{}) *ParameterProcessor {
|
||||
return &ParameterProcessor{
|
||||
globalVariables: globalVariables,
|
||||
activityResults: make(map[int64]*ActivityResult),
|
||||
}
|
||||
}
|
||||
|
||||
// AddActivityResult 添加activity执行结果
|
||||
func (p *ParameterProcessor) AddActivityResult(stepID int64, result *ActivityResult) {
|
||||
p.activityResults[stepID] = result
|
||||
}
|
||||
|
||||
// ProcessTemplate 处理参数模板,替换其中的变量引用
|
||||
func (p *ParameterProcessor) ProcessTemplate(template string) (string, error) {
|
||||
if template == "" {
|
||||
return template, nil
|
||||
}
|
||||
|
||||
result := template
|
||||
|
||||
// 1. 替换全局变量引用 ${global.key}
|
||||
result = p.replaceGlobalVariables(result)
|
||||
|
||||
// 2. 替换步骤结果变量引用 ${step.stepId.field}
|
||||
result = p.replaceStepVariables(result)
|
||||
|
||||
// 3. 替换条件变量引用 ${if.condition:value1:value2}
|
||||
result = p.replaceConditionalVariables(result)
|
||||
|
||||
// 4. 替换函数调用 ${func:arg1:arg2}
|
||||
result = p.replaceFunctionCalls(result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// replaceGlobalVariables 替换全局变量引用
|
||||
func (p *ParameterProcessor) replaceGlobalVariables(template string) string {
|
||||
// 使用正则表达式匹配 ${global.key} 格式
|
||||
re := regexp.MustCompile(`\$\{global\.([^}]+)\}`)
|
||||
|
||||
return re.ReplaceAllStringFunc(template, func(match string) string {
|
||||
// 提取变量名
|
||||
matches := re.FindStringSubmatch(match)
|
||||
if len(matches) < 2 {
|
||||
return match // 保持原样
|
||||
}
|
||||
|
||||
key := matches[1]
|
||||
if value, exists := p.globalVariables[key]; exists {
|
||||
return fmt.Sprintf("%v", value)
|
||||
}
|
||||
|
||||
return match // 变量不存在,保持原样
|
||||
})
|
||||
}
|
||||
|
||||
// replaceStepVariables 替换步骤结果变量引用
|
||||
func (p *ParameterProcessor) replaceStepVariables(template string) string {
|
||||
// 匹配 ${step.stepId.field} 格式
|
||||
re := regexp.MustCompile(`\$\{step\.(\d+)\.([^}]+)\}`)
|
||||
|
||||
return re.ReplaceAllStringFunc(template, func(match string) string {
|
||||
matches := re.FindStringSubmatch(match)
|
||||
if len(matches) < 3 {
|
||||
return match
|
||||
}
|
||||
|
||||
stepIDStr := matches[1]
|
||||
field := matches[2]
|
||||
|
||||
stepID, err := strconv.ParseInt(stepIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return match
|
||||
}
|
||||
|
||||
result, exists := p.activityResults[stepID]
|
||||
if !exists {
|
||||
return match
|
||||
}
|
||||
|
||||
return p.extractFieldValue(result, field)
|
||||
})
|
||||
}
|
||||
|
||||
// replaceConditionalVariables 替换条件变量引用
|
||||
func (p *ParameterProcessor) replaceConditionalVariables(template string) string {
|
||||
// 匹配 ${if.condition:value1:value2} 格式
|
||||
re := regexp.MustCompile(`\$\{if\.([^:]+):([^:]*):([^}]*)\}`)
|
||||
|
||||
return re.ReplaceAllStringFunc(template, func(match string) string {
|
||||
matches := re.FindStringSubmatch(match)
|
||||
if len(matches) < 4 {
|
||||
return match
|
||||
}
|
||||
|
||||
condition := matches[1]
|
||||
valueIfTrue := matches[2]
|
||||
valueIfFalse := matches[3]
|
||||
|
||||
if p.evaluateCondition(condition) {
|
||||
return valueIfTrue
|
||||
}
|
||||
return valueIfFalse
|
||||
})
|
||||
}
|
||||
|
||||
// replaceFunctionCalls 替换函数调用
|
||||
func (p *ParameterProcessor) replaceFunctionCalls(template string) string {
|
||||
// 匹配 ${func:arg1:arg2} 格式
|
||||
re := regexp.MustCompile(`\$\{([^:]+):([^}]*)\}`)
|
||||
|
||||
return re.ReplaceAllStringFunc(template, func(match string) string {
|
||||
matches := re.FindStringSubmatch(match)
|
||||
if len(matches) < 3 {
|
||||
return match
|
||||
}
|
||||
|
||||
funcName := matches[1]
|
||||
args := matches[2]
|
||||
|
||||
return p.executeFunction(funcName, args)
|
||||
})
|
||||
}
|
||||
|
||||
// extractFieldValue 从activity结果中提取指定字段的值
|
||||
func (p *ParameterProcessor) extractFieldValue(result *ActivityResult, field string) string {
|
||||
switch result.Data.(type) {
|
||||
case *pb.ApiTestResult:
|
||||
return p.extractApiResultField(result.Data.(*pb.ApiTestResult), field)
|
||||
case *pb.UiTestResult:
|
||||
return p.extractUiResultField(result.Data.(*pb.UiTestResult), field)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// extractApiResultField 从API测试结果中提取字段值
|
||||
func (p *ParameterProcessor) extractApiResultField(result *pb.ApiTestResult, field string) string {
|
||||
switch field {
|
||||
case "response_body":
|
||||
// 保证输出是 JSON 字符串格式
|
||||
b, err := json.Marshal(result.ResponseBody)
|
||||
if err == nil {
|
||||
return string(b[1 : len(b)-1]) // 去掉外层引号
|
||||
}
|
||||
return result.ResponseBody
|
||||
case "actual_status_code":
|
||||
return strconv.Itoa(int(result.ActualStatusCode))
|
||||
case "success":
|
||||
return strconv.FormatBool(result.BaseResult.Success)
|
||||
case "message":
|
||||
return result.BaseResult.Message
|
||||
default:
|
||||
zap.L().Debug("未知字段", zap.String("field", field), zap.String("ResponseBody", result.ResponseBody))
|
||||
|
||||
// 检查是否是响应头
|
||||
if strings.HasPrefix(field, "headers.") {
|
||||
headerKey := strings.TrimPrefix(field, "headers.")
|
||||
if headerValue, exists := result.Headers[headerKey]; exists {
|
||||
return headerValue
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否是JSON字段
|
||||
if strings.HasPrefix(field, "json.") {
|
||||
jsonField := strings.TrimPrefix(field, "json.")
|
||||
return p.extractJsonField(result.ResponseBody, jsonField)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// extractUiResultField 从UI测试结果中提取字段值
|
||||
func (p *ParameterProcessor) extractUiResultField(result *pb.UiTestResult, field string) string {
|
||||
switch field {
|
||||
case "screenshot_url":
|
||||
return result.ScreenshotUrl
|
||||
case "html_report_url":
|
||||
return result.HtmlReportUrl
|
||||
case "success":
|
||||
return strconv.FormatBool(result.BaseResult.Success)
|
||||
case "message":
|
||||
return result.BaseResult.Message
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// extractJsonField 从JSON响应体中提取指定字段
|
||||
func (p *ParameterProcessor) extractJsonField(responseBody, field string) string {
|
||||
if responseBody == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(responseBody), &data); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 支持嵌套字段,如 "user.id"
|
||||
keys := strings.Split(field, ".")
|
||||
current := data
|
||||
|
||||
for i, key := range keys {
|
||||
if i == len(keys)-1 {
|
||||
// 最后一个键
|
||||
if value, exists := current[key]; exists {
|
||||
return fmt.Sprintf("%v", value)
|
||||
}
|
||||
} else {
|
||||
// 中间键,需要继续深入
|
||||
if next, exists := current[key]; exists {
|
||||
if nextMap, ok := next.(map[string]interface{}); ok {
|
||||
current = nextMap
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// evaluateCondition 评估条件表达式
|
||||
func (p *ParameterProcessor) evaluateCondition(condition string) bool {
|
||||
// 支持简单的条件表达式
|
||||
// 例如:step.123.success, global.environment == "production"
|
||||
|
||||
// 检查步骤成功状态
|
||||
if strings.HasSuffix(condition, ".success") {
|
||||
stepIDStr := strings.TrimSuffix(condition, ".success")
|
||||
if strings.HasPrefix(stepIDStr, "step.") {
|
||||
stepIDStr = strings.TrimPrefix(stepIDStr, "step.")
|
||||
stepID, err := strconv.ParseInt(stepIDStr, 10, 64)
|
||||
if err == nil {
|
||||
if result, exists := p.activityResults[stepID]; exists {
|
||||
return result.Success
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查全局变量
|
||||
if strings.HasPrefix(condition, "global.") {
|
||||
key := strings.TrimPrefix(condition, "global.")
|
||||
if value, exists := p.globalVariables[key]; exists {
|
||||
// 转换为布尔值
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
return v
|
||||
case string:
|
||||
return v != "" && v != "false" && v != "0"
|
||||
case int, int64, float64:
|
||||
return fmt.Sprintf("%v", v) != "0"
|
||||
default:
|
||||
return value != nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查相等性
|
||||
if strings.Contains(condition, "==") {
|
||||
parts := strings.Split(condition, "==")
|
||||
if len(parts) == 2 {
|
||||
left := strings.TrimSpace(parts[0])
|
||||
right := strings.TrimSpace(strings.Trim(parts[1], `"'`))
|
||||
|
||||
// 检查全局变量
|
||||
if strings.HasPrefix(left, "global.") {
|
||||
key := strings.TrimPrefix(left, "global.")
|
||||
if value, exists := p.globalVariables[key]; exists {
|
||||
return fmt.Sprintf("%v", value) == right
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// executeFunction 执行函数调用
|
||||
func (p *ParameterProcessor) executeFunction(funcName, args string) string {
|
||||
argList := strings.Split(args, ":")
|
||||
|
||||
switch funcName {
|
||||
case "concat":
|
||||
// 连接字符串
|
||||
return strings.Join(argList, "")
|
||||
case "upper":
|
||||
// 转换为大写
|
||||
if len(argList) > 0 {
|
||||
return strings.ToUpper(argList[0])
|
||||
}
|
||||
case "lower":
|
||||
// 转换为小写
|
||||
if len(argList) > 0 {
|
||||
return strings.ToLower(argList[0])
|
||||
}
|
||||
case "substring":
|
||||
// 子字符串
|
||||
if len(argList) >= 3 {
|
||||
str := argList[0]
|
||||
start, _ := strconv.Atoi(argList[1])
|
||||
end, _ := strconv.Atoi(argList[2])
|
||||
if start < len(str) && end <= len(str) && start < end {
|
||||
return str[start:end]
|
||||
}
|
||||
}
|
||||
case "default":
|
||||
// 默认值
|
||||
if len(argList) >= 2 {
|
||||
value := argList[0]
|
||||
defaultValue := argList[1]
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetAvailableVariables 获取所有可用的变量列表
|
||||
func (p *ParameterProcessor) GetAvailableVariables() map[string][]string {
|
||||
variables := make(map[string][]string)
|
||||
|
||||
// 全局变量
|
||||
var globalVars []string
|
||||
for key := range p.globalVariables {
|
||||
globalVars = append(globalVars, key)
|
||||
}
|
||||
variables["global"] = globalVars
|
||||
|
||||
// 步骤变量
|
||||
for stepID, result := range p.activityResults {
|
||||
stepKey := fmt.Sprintf("step.%d", stepID)
|
||||
var stepVars []string
|
||||
|
||||
switch result.Data.(type) {
|
||||
case *pb.ApiTestResult:
|
||||
stepVars = []string{
|
||||
"response_body",
|
||||
"actual_status_code",
|
||||
"success",
|
||||
"message",
|
||||
"headers.*",
|
||||
"json.*",
|
||||
}
|
||||
case *pb.UiTestResult:
|
||||
stepVars = []string{
|
||||
"screenshot_url",
|
||||
"html_report_url",
|
||||
"success",
|
||||
"message",
|
||||
}
|
||||
}
|
||||
|
||||
variables[stepKey] = stepVars
|
||||
}
|
||||
|
||||
return variables
|
||||
}
|
||||
|
||||
// ValidateTemplate 验证模板中的变量引用是否有效
|
||||
func (p *ParameterProcessor) ValidateTemplate(template string) (bool, []string) {
|
||||
var errors []string
|
||||
|
||||
// 检查全局变量引用
|
||||
globalRe := regexp.MustCompile(`\$\{global\.([^}]+)\}`)
|
||||
matches := globalRe.FindAllStringSubmatch(template, -1)
|
||||
for _, match := range matches {
|
||||
if len(match) >= 2 {
|
||||
key := match[1]
|
||||
if _, exists := p.globalVariables[key]; !exists {
|
||||
errors = append(errors, fmt.Sprintf("全局变量 '%s' 不存在", key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查步骤变量引用
|
||||
stepRe := regexp.MustCompile(`\$\{step\.(\d+)\.([^}]+)\}`)
|
||||
matches = stepRe.FindAllStringSubmatch(template, -1)
|
||||
for _, match := range matches {
|
||||
if len(match) >= 3 {
|
||||
stepIDStr := match[1]
|
||||
field := match[2]
|
||||
|
||||
stepID, err := strconv.ParseInt(stepIDStr, 10, 64)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Sprintf("无效的步骤ID: %s", stepIDStr))
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := p.activityResults[stepID]; !exists {
|
||||
errors = append(errors, fmt.Sprintf("步骤 %d 不存在", stepID))
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查字段是否有效
|
||||
if !p.isValidField(stepID, field) {
|
||||
errors = append(errors, fmt.Sprintf("步骤 %d 不支持字段: %s", stepID, field))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len(errors) == 0, errors
|
||||
}
|
||||
|
||||
// isValidField 检查字段是否有效
|
||||
func (p *ParameterProcessor) isValidField(stepID int64, field string) bool {
|
||||
result, exists := p.activityResults[stepID]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
switch result.Data.(type) {
|
||||
case *pb.ApiTestResult:
|
||||
validFields := map[string]bool{
|
||||
"response_body": true,
|
||||
"actual_status_code": true,
|
||||
"success": true,
|
||||
"message": true,
|
||||
}
|
||||
|
||||
if validFields[field] {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否是响应头
|
||||
if strings.HasPrefix(field, "headers.") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否是JSON字段
|
||||
if strings.HasPrefix(field, "json.") {
|
||||
return true
|
||||
}
|
||||
|
||||
case *pb.UiTestResult:
|
||||
validFields := map[string]bool{
|
||||
"screenshot_url": true,
|
||||
"html_report_url": true,
|
||||
"success": true,
|
||||
"message": true,
|
||||
}
|
||||
|
||||
return validFields[field]
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
385
utils/parameter_processor_test.go
Normal file
385
utils/parameter_processor_test.go
Normal file
@ -0,0 +1,385 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"beacon/pkg/pb"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParameterProcessor_ProcessTemplate(t *testing.T) {
|
||||
// 创建测试用的全局变量
|
||||
globalVariables := map[string]interface{}{
|
||||
"base_url": "https://api.example.com",
|
||||
"environment": "staging",
|
||||
"api_version": "v1",
|
||||
"timeout": "30",
|
||||
"retry_count": "3",
|
||||
}
|
||||
|
||||
// 创建参数处理器
|
||||
processor := NewParameterProcessor(globalVariables)
|
||||
|
||||
// 模拟API测试结果
|
||||
apiResult := &pb.ApiTestResult{
|
||||
BaseResult: &pb.BaseTestResult{
|
||||
Success: true,
|
||||
Message: "API Test Passed",
|
||||
},
|
||||
ActualStatusCode: 200,
|
||||
ResponseBody: `{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user_id": 456, "name": "John Doe"}`,
|
||||
Headers: map[string]string{
|
||||
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
|
||||
// 添加activity结果
|
||||
processor.AddActivityResult(123, &ActivityResult{
|
||||
StepID: 123,
|
||||
StepName: "RunApiTest",
|
||||
Success: true,
|
||||
Data: apiResult,
|
||||
})
|
||||
|
||||
// 测试用例
|
||||
tests := []struct {
|
||||
name string
|
||||
template string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "全局变量替换",
|
||||
template: `{"endpoint": "${global.base_url}/${global.api_version}/users"}`,
|
||||
expected: `{"endpoint": "https://api.example.com/v1/users"}`,
|
||||
},
|
||||
{
|
||||
name: "API响应体替换",
|
||||
template: `{"token": "${step.123.response_body}"}`,
|
||||
expected: `{"token": "{\"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\", \"user_id\": 456, \"name\": \"John Doe\"}"}`,
|
||||
},
|
||||
{
|
||||
name: "API状态码替换",
|
||||
template: `{"status": "${step.123.actual_status_code}"}`,
|
||||
expected: `{"status": "200"}`,
|
||||
},
|
||||
{
|
||||
name: "API响应头替换",
|
||||
template: `{"auth": "${step.123.headers.Authorization}"}`,
|
||||
expected: `{"auth": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}`,
|
||||
},
|
||||
{
|
||||
name: "JSON字段提取",
|
||||
template: `{"user_id": "${step.123.json.user_id}", "name": "${step.123.json.name}"}`,
|
||||
expected: `{"user_id": "456", "name": "John Doe"}`,
|
||||
},
|
||||
{
|
||||
name: "混合变量替换",
|
||||
template: `{"endpoint": "${global.base_url}/${global.api_version}/users/${step.123.json.user_id}", "headers": {"Authorization": "${step.123.json.token}"}}`,
|
||||
expected: `{"endpoint": "https://api.example.com/v1/users/456", "headers": {"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}}`,
|
||||
},
|
||||
{
|
||||
name: "条件变量替换",
|
||||
template: `{"env": "${if.step.123.success:production:staging}"}`,
|
||||
expected: `{"env": "production"}`,
|
||||
},
|
||||
{
|
||||
name: "函数调用",
|
||||
template: `{"upper_name": "${upper:${step.123.json.name}}"}`,
|
||||
expected: `{"upper_name": "JOHN DOE"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := processor.ProcessTemplate(tt.template)
|
||||
if err != nil {
|
||||
t.Errorf("ProcessTemplate() error = %v", err)
|
||||
return
|
||||
}
|
||||
if result != tt.expected {
|
||||
t.Errorf("ProcessTemplate() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParameterProcessor_ValidateTemplate(t *testing.T) {
|
||||
// 创建测试用的全局变量
|
||||
globalVariables := map[string]interface{}{
|
||||
"base_url": "https://api.example.com",
|
||||
"environment": "staging",
|
||||
}
|
||||
|
||||
// 创建参数处理器
|
||||
processor := NewParameterProcessor(globalVariables)
|
||||
|
||||
// 添加一个activity结果
|
||||
apiResult := &pb.ApiTestResult{
|
||||
BaseResult: &pb.BaseTestResult{
|
||||
Success: true,
|
||||
Message: "API Test Passed",
|
||||
},
|
||||
ActualStatusCode: 200,
|
||||
ResponseBody: `{"token": "test-token"}`,
|
||||
Headers: map[string]string{
|
||||
"Authorization": "Bearer test-token",
|
||||
},
|
||||
}
|
||||
|
||||
processor.AddActivityResult(123, &ActivityResult{
|
||||
StepID: 123,
|
||||
StepName: "RunApiTest",
|
||||
Success: true,
|
||||
Data: apiResult,
|
||||
})
|
||||
|
||||
// 测试用例
|
||||
tests := []struct {
|
||||
name string
|
||||
template string
|
||||
expectValid bool
|
||||
expectErrors int
|
||||
}{
|
||||
{
|
||||
name: "有效模板",
|
||||
template: `{"endpoint": "${global.base_url}/users", "token": "${step.123.json.token}"}`,
|
||||
expectValid: true,
|
||||
expectErrors: 0,
|
||||
},
|
||||
{
|
||||
name: "无效的全局变量",
|
||||
template: `{"endpoint": "${global.invalid_key}/users"}`,
|
||||
expectValid: false,
|
||||
expectErrors: 1,
|
||||
},
|
||||
{
|
||||
name: "无效的步骤ID",
|
||||
template: `{"token": "${step.999.json.token}"}`,
|
||||
expectValid: false,
|
||||
expectErrors: 1,
|
||||
},
|
||||
{
|
||||
name: "无效的字段",
|
||||
template: `{"invalid": "${step.123.invalid_field}"}`,
|
||||
expectValid: false,
|
||||
expectErrors: 1,
|
||||
},
|
||||
{
|
||||
name: "多个错误",
|
||||
template: `{"endpoint": "${global.invalid_key}/users", "token": "${step.999.json.token}"}`,
|
||||
expectValid: false,
|
||||
expectErrors: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid, errors := processor.ValidateTemplate(tt.template)
|
||||
if isValid != tt.expectValid {
|
||||
t.Errorf("ValidateTemplate() isValid = %v, want %v", isValid, tt.expectValid)
|
||||
}
|
||||
if len(errors) != tt.expectErrors {
|
||||
t.Errorf("ValidateTemplate() errors count = %d, want %d", len(errors), tt.expectErrors)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParameterProcessor_GetAvailableVariables(t *testing.T) {
|
||||
// 创建测试用的全局变量
|
||||
globalVariables := map[string]interface{}{
|
||||
"base_url": "https://api.example.com",
|
||||
"environment": "staging",
|
||||
"api_version": "v1",
|
||||
}
|
||||
|
||||
// 创建参数处理器
|
||||
processor := NewParameterProcessor(globalVariables)
|
||||
|
||||
// 添加API测试结果
|
||||
apiResult := &pb.ApiTestResult{
|
||||
BaseResult: &pb.BaseTestResult{
|
||||
Success: true,
|
||||
Message: "API Test Passed",
|
||||
},
|
||||
ActualStatusCode: 200,
|
||||
ResponseBody: `{"token": "test-token"}`,
|
||||
Headers: map[string]string{
|
||||
"Authorization": "Bearer test-token",
|
||||
},
|
||||
}
|
||||
|
||||
processor.AddActivityResult(123, &ActivityResult{
|
||||
StepID: 123,
|
||||
StepName: "RunApiTest",
|
||||
Success: true,
|
||||
Data: apiResult,
|
||||
})
|
||||
|
||||
// 添加UI测试结果
|
||||
uiResult := &pb.UiTestResult{
|
||||
BaseResult: &pb.BaseTestResult{
|
||||
Success: true,
|
||||
Message: "UI Test Passed",
|
||||
},
|
||||
ScreenshotUrl: "https://s3.amazonaws.com/screenshots/test.png",
|
||||
HtmlReportUrl: "https://s3.amazonaws.com/reports/test.html",
|
||||
}
|
||||
|
||||
processor.AddActivityResult(456, &ActivityResult{
|
||||
StepID: 456,
|
||||
StepName: "RunUiTest",
|
||||
Success: true,
|
||||
Data: uiResult,
|
||||
})
|
||||
|
||||
// 获取可用变量
|
||||
variables := processor.GetAvailableVariables()
|
||||
|
||||
// 验证全局变量
|
||||
if globalVars, exists := variables["global"]; !exists {
|
||||
t.Error("Global variables not found")
|
||||
} else {
|
||||
expectedGlobalVars := []string{"base_url", "environment", "api_version"}
|
||||
for _, expected := range expectedGlobalVars {
|
||||
found := false
|
||||
for _, actual := range globalVars {
|
||||
if actual == expected {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Global variable '%s' not found", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证步骤变量
|
||||
if step123Vars, exists := variables["step.123"]; !exists {
|
||||
t.Error("Step 123 variables not found")
|
||||
} else {
|
||||
expectedStepVars := []string{"response_body", "actual_status_code", "success", "message", "headers.*", "json.*"}
|
||||
for _, expected := range expectedStepVars {
|
||||
found := false
|
||||
for _, actual := range step123Vars {
|
||||
if actual == expected {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Step 123 variable '%s' not found", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if step456Vars, exists := variables["step.456"]; !exists {
|
||||
t.Error("Step 456 variables not found")
|
||||
} else {
|
||||
expectedStepVars := []string{"screenshot_url", "html_report_url", "success", "message"}
|
||||
for _, expected := range expectedStepVars {
|
||||
found := false
|
||||
for _, actual := range step456Vars {
|
||||
if actual == expected {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Step 456 variable '%s' not found", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParameterProcessor_ComplexScenarios(t *testing.T) {
|
||||
// 创建测试用的全局变量
|
||||
globalVariables := map[string]interface{}{
|
||||
"base_url": "https://api.example.com",
|
||||
"environment": "production",
|
||||
"api_version": "v2",
|
||||
"timeout": "60",
|
||||
}
|
||||
|
||||
// 创建参数处理器
|
||||
processor := NewParameterProcessor(globalVariables)
|
||||
|
||||
// 模拟用户注册结果
|
||||
registerResult := &pb.ApiTestResult{
|
||||
BaseResult: &pb.BaseTestResult{
|
||||
Success: true,
|
||||
Message: "User registered successfully",
|
||||
},
|
||||
ActualStatusCode: 201,
|
||||
ResponseBody: `{"user_id": 789, "username": "newuser", "email": "newuser@example.com"}`,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
|
||||
// 模拟用户登录结果
|
||||
loginResult := &pb.ApiTestResult{
|
||||
BaseResult: &pb.BaseTestResult{
|
||||
Success: true,
|
||||
Message: "User logged in successfully",
|
||||
},
|
||||
ActualStatusCode: 200,
|
||||
ResponseBody: `{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "refresh123", "expires_in": 3600}`,
|
||||
Headers: map[string]string{
|
||||
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
|
||||
// 添加activity结果
|
||||
processor.AddActivityResult(201, &ActivityResult{
|
||||
StepID: 201,
|
||||
StepName: "RunApiTest",
|
||||
Success: true,
|
||||
Data: registerResult,
|
||||
})
|
||||
|
||||
processor.AddActivityResult(202, &ActivityResult{
|
||||
StepID: 202,
|
||||
StepName: "RunApiTest",
|
||||
Success: true,
|
||||
Data: loginResult,
|
||||
})
|
||||
|
||||
// 测试复杂的参数传递场景
|
||||
complexTemplate := `{
|
||||
"test_case_id": "get_user_profile",
|
||||
"endpoint": "${global.base_url}/${global.api_version}/users/${step.201.json.user_id}/profile",
|
||||
"http_method": "GET",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${step.202.json.token}",
|
||||
"X-Environment": "${global.environment}",
|
||||
"X-Timeout": "${global.timeout}",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"expected_status_code": 200
|
||||
}`
|
||||
|
||||
expected := `{
|
||||
"test_case_id": "get_user_profile",
|
||||
"endpoint": "https://api.example.com/v2/users/789/profile",
|
||||
"http_method": "GET",
|
||||
"headers": {
|
||||
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"X-Environment": "production",
|
||||
"X-Timeout": "60",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"expected_status_code": 200
|
||||
}`
|
||||
|
||||
result, err := processor.ProcessTemplate(complexTemplate)
|
||||
if err != nil {
|
||||
t.Errorf("ProcessTemplate() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("ProcessTemplate() = %v, want %v", result, expected)
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ def execute_api_test_case(test_case_id: str, endpoint: str, http_method: str, he
|
||||
实际执行API测试的函数。
|
||||
可以集成 pytest, requests 等库。
|
||||
"""
|
||||
base_url = "http://101.89.127.197:9080" # 假设 API 服务的基地址
|
||||
base_url = "" # 假设 API 服务的基地址
|
||||
|
||||
full_url = f"{base_url}{endpoint}"
|
||||
log_output = []
|
||||
@ -25,7 +25,12 @@ def execute_api_test_case(test_case_id: str, endpoint: str, http_method: str, he
|
||||
if http_method.upper() == "GET":
|
||||
response = requests.get(full_url, headers=headers, timeout=10)
|
||||
elif http_method.upper() == "POST":
|
||||
json_data = json.loads(request_body) if isinstance(request_body, str) else request_body
|
||||
print(request_body)
|
||||
if request_body == "":
|
||||
json_data = "{}"
|
||||
else:
|
||||
# 如果 request_body 是字符串,尝试将其解析为 JSON
|
||||
json_data = json.loads(request_body) if isinstance(request_body, str) else request_body
|
||||
response = requests.post(full_url, headers=headers, data=json_data, timeout=10)
|
||||
# ... 其他 HTTP 方法
|
||||
else:
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"beacon/activities" // 假设你的 activity 包在这个路径
|
||||
"beacon/pkg/pb" // 你的 Protobuf 路径
|
||||
"beacon/utils" // 导入参数处理器
|
||||
"go.temporal.io/sdk/workflow"
|
||||
// 假设你有数据库访问层,Workflow 不能直接访问 DB,需要通过 Activity 或预加载数据
|
||||
// "your_module_path/go-server/dal" // 例如 Data Access Layer
|
||||
@ -78,6 +79,19 @@ func DynamicTestSuiteWorkflow(ctx workflow.Context, input *pb.DynamicTestRunInpu
|
||||
currentStepOrder = 0 // 当前执行步骤的索引,支持非线性跳转
|
||||
)
|
||||
|
||||
// 初始化全局变量,包含输入参数中的全局参数
|
||||
globalVariables := make(map[string]interface{})
|
||||
if input.GlobalParameters != nil {
|
||||
for key, value := range input.GlobalParameters {
|
||||
globalVariables[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// 创建参数处理器
|
||||
parameterProcessor := utils.NewParameterProcessor(globalVariables)
|
||||
|
||||
logger.Info("Initialized global variables", "variables", globalVariables)
|
||||
|
||||
// ========================================================================================
|
||||
// 步骤4: 主执行循环 - 动态执行各个测试步骤
|
||||
// ========================================================================================
|
||||
@ -87,30 +101,45 @@ func DynamicTestSuiteWorkflow(ctx workflow.Context, input *pb.DynamicTestRunInpu
|
||||
logger.Info("Executing step", "stepOrder", step.StepOrder, "activityName", step.ActivityName)
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// 步骤4.1: 动态构造 Activity 输入参数
|
||||
// 步骤4.1: 动态构造 Activity 输入参数(支持参数模板解析)
|
||||
// ------------------------------------------------------------------------------------
|
||||
// 因为不同的 Activity 需要不同类型的输入参数,这里使用工厂模式
|
||||
// 根据 activity_name 决定使用哪个 Protobuf 结构来反序列化 JSON 参数
|
||||
var activityInput interface{} // 存储特定 Activity 的输入参数(类型由 activity_name 决定)
|
||||
var activityResult interface{} // 存储特定 Activity 的输出结果(类型由 activity_name 决定)
|
||||
|
||||
// 使用参数处理器解析参数模板,替换其中的变量引用
|
||||
processedParametersJson, err := parameterProcessor.ProcessTemplate(step.ParametersJson)
|
||||
if err != nil {
|
||||
logger.Error("Failed to process parameter template", "error", err, "stepOrder", step.StepOrder)
|
||||
overallSuccess = false
|
||||
break
|
||||
}
|
||||
|
||||
// 验证模板中的变量引用是否有效
|
||||
if isValid, errors := parameterProcessor.ValidateTemplate(step.ParametersJson); !isValid {
|
||||
logger.Error("Parameter template validation failed", "errors", errors, "stepOrder", step.StepOrder)
|
||||
overallSuccess = false
|
||||
break
|
||||
}
|
||||
|
||||
// 根据不同的 Activity 类型,将 JSON 字符串参数反序列化为对应的 Protobuf 结构
|
||||
// 这是动态工作流的核心:同一个工作流可以执行不同类型的测试
|
||||
switch step.ActivityName {
|
||||
case "RunApiTest":
|
||||
apiReq := &pb.ApiTestRequest{}
|
||||
// 1. 首先验证JSON格式
|
||||
if !json.Valid([]byte(step.ParametersJson)) {
|
||||
if !json.Valid([]byte(processedParametersJson)) {
|
||||
logger.Error("Invalid JSON format in parameters")
|
||||
overallSuccess = false
|
||||
break
|
||||
}
|
||||
|
||||
// 2. 解析JSON时增加错误详情
|
||||
if err := json.Unmarshal([]byte(step.ParametersJson), apiReq); err != nil {
|
||||
if err := json.Unmarshal([]byte(processedParametersJson), apiReq); err != nil {
|
||||
logger.Error("Failed to unmarshal API test parameters",
|
||||
"error", err,
|
||||
"raw_json", step.ParametersJson)
|
||||
"raw_json", processedParametersJson)
|
||||
overallSuccess = false
|
||||
break
|
||||
}
|
||||
@ -121,22 +150,13 @@ func DynamicTestSuiteWorkflow(ctx workflow.Context, input *pb.DynamicTestRunInpu
|
||||
overallSuccess = false
|
||||
break
|
||||
}
|
||||
/*// API 测试:创建 API 测试请求结构并解析参数
|
||||
logger.Info("Running api test activity", "ParametersJson", step.ParametersJson)
|
||||
fmt.Println(reflect.TypeOf(step.ParametersJson))
|
||||
apiReq := &pb.ApiTestRequest{}
|
||||
if err := json.Unmarshal([]byte(step.ParametersJson), apiReq); err != nil {
|
||||
logger.Error("Failed to unmarshal API test parameters", "error", err)
|
||||
overallSuccess = false
|
||||
break // 参数解析失败,跳出当前步骤
|
||||
}*/
|
||||
activityInput = apiReq
|
||||
activityResult = &pb.ApiTestResult{} // 预创建结果容器
|
||||
|
||||
case "RunUiTest":
|
||||
// UI 测试:创建 UI 测试请求结构并解析参数
|
||||
uiReq := &pb.UiTestRequest{}
|
||||
if err := json.Unmarshal([]byte(step.ParametersJson), uiReq); err != nil {
|
||||
if err := json.Unmarshal([]byte(processedParametersJson), uiReq); err != nil {
|
||||
logger.Error("Failed to unmarshal UI test parameters", "error", err)
|
||||
overallSuccess = false
|
||||
break // 参数解析失败,跳出当前步骤
|
||||
@ -149,7 +169,7 @@ func DynamicTestSuiteWorkflow(ctx workflow.Context, input *pb.DynamicTestRunInpu
|
||||
// TODO: 实现环境准备的参数解析
|
||||
/*
|
||||
prepReq := &pb.PrepareEnvRequest{}
|
||||
if err := json.Unmarshal([]byte(step.ParametersJson), prepReq); err != nil {
|
||||
if err := json.Unmarshal([]byte(processedParametersJson), prepReq); err != nil {
|
||||
logger.Error("Failed to unmarshal prepare env parameters", "error", err)
|
||||
overallSuccess = false
|
||||
break
|
||||
@ -215,12 +235,29 @@ func DynamicTestSuiteWorkflow(ctx workflow.Context, input *pb.DynamicTestRunInpu
|
||||
stepPassed = false
|
||||
overallSuccess = false
|
||||
}
|
||||
// 存储activity结果用于参数传递
|
||||
activityResult := &utils.ActivityResult{
|
||||
StepID: step.StepId,
|
||||
StepName: step.ActivityName,
|
||||
Success: res.BaseResult.Success,
|
||||
Data: res,
|
||||
}
|
||||
parameterProcessor.AddActivityResult(step.StepId, activityResult)
|
||||
|
||||
case *pb.UiTestResult:
|
||||
uiResults = append(uiResults, res) // 收集 UI 测试结果
|
||||
if !res.BaseResult.Success { // 检查业务逻辑是否成功
|
||||
stepPassed = false
|
||||
overallSuccess = false
|
||||
}
|
||||
// 存储activity结果用于参数传递
|
||||
activityResult := &utils.ActivityResult{
|
||||
StepID: step.StepId,
|
||||
StepName: step.ActivityName,
|
||||
Success: res.BaseResult.Success,
|
||||
Data: res,
|
||||
}
|
||||
parameterProcessor.AddActivityResult(step.StepId, activityResult)
|
||||
// 可以在这里添加更多结果类型的处理
|
||||
}
|
||||
logger.Info("Activity execution finished", "activityName", step.ActivityName, "success", stepPassed)
|
||||
|
Loading…
Reference in New Issue
Block a user