263 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
|  * Copyright 2025 coze-dev Authors
 | |
|  *
 | |
|  * 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 code
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/coze-dev/coze-studio/backend/infra/contract/coderunner"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"go.uber.org/mock/gomock"
 | |
| 
 | |
| 	"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
 | |
| 	"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes"
 | |
| 	mockcode "github.com/coze-dev/coze-studio/backend/internal/mock/domain/workflow/crossdomain/code"
 | |
| 	"github.com/coze-dev/coze-studio/backend/pkg/ctxcache"
 | |
| )
 | |
| 
 | |
| var codeTpl string
 | |
| 
 | |
| func TestCode_RunCode(t *testing.T) {
 | |
| 	ctrl := gomock.NewController(t)
 | |
| 	mockRunner := mockcode.NewMockRunner(ctrl)
 | |
| 
 | |
| 	t.Run("normal", func(t *testing.T) {
 | |
| 		var codeTpl = `
 | |
| async def main(args:Args)->Output:
 | |
|     params = args.params
 | |
|     ret: Output = {
 | |
|         "key0": params['input'] + params['input'],
 | |
|         "key1": ["hello", "world"], 
 | |
|   		"key2": [123, "345"], 
 | |
|         "key3": { 
 | |
|             "key31": "hi",
 | |
| 			"key32": "hello",
 | |
| 			"key33": ["123","456"],
 | |
| 			"key34": {
 | |
| 				"key341":"123",			
 | |
| 				"key342":456,
 | |
| 				}
 | |
|         },
 | |
|     }
 | |
|     return ret
 | |
| `
 | |
| 		ret := map[string]any{
 | |
| 			"key0": int64(11231123),
 | |
| 			"key1": []any{"hello", "world"},
 | |
| 			"key2": []interface{}{int64(123), "345"},
 | |
| 			"key3": map[string]interface{}{"key31": "hi", "key32": "hello", "key33": []any{"123", "456"}, "key34": map[string]interface{}{"key341": "123", "key342": int64(456)}},
 | |
| 			"key4": []any{
 | |
| 				map[string]any{"key41": "41"},
 | |
| 				map[string]any{"key42": "42"},
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		response := &coderunner.RunResponse{
 | |
| 			Result: ret,
 | |
| 		}
 | |
| 
 | |
| 		mockRunner.EXPECT().Run(gomock.Any(), gomock.Any()).Return(response, nil)
 | |
| 		ctx := t.Context()
 | |
| 		c := &CodeRunner{
 | |
| 			config: &Config{
 | |
| 				Language: coderunner.Python,
 | |
| 				Code:     codeTpl,
 | |
| 				OutputConfig: map[string]*vo.TypeInfo{
 | |
| 					"key0": {Type: vo.DataTypeInteger},
 | |
| 					"key1": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeString}},
 | |
| 					"key2": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
 | |
| 					"key3": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
 | |
| 						"key31": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						"key32": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						"key33": &vo.TypeInfo{Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
 | |
| 						"key34": &vo.TypeInfo{Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
 | |
| 							"key341": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 							"key342": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						}},
 | |
| 					},
 | |
| 					},
 | |
| 					"key4": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeObject}},
 | |
| 				},
 | |
| 				Runner: mockRunner,
 | |
| 			},
 | |
| 		}
 | |
| 		ret, err := c.RunCode(ctx, map[string]any{
 | |
| 			"input": "1123",
 | |
| 		})
 | |
| 
 | |
| 		bs, _ := json.Marshal(ret)
 | |
| 		fmt.Println(string(bs))
 | |
| 
 | |
| 		assert.NoError(t, err)
 | |
| 		assert.Equal(t, int64(11231123), ret["key0"])
 | |
| 		assert.Equal(t, []any{"hello", "world"}, ret["key1"])
 | |
| 		assert.Equal(t, []any{float64(123), float64(345)}, ret["key2"])
 | |
| 		assert.Equal(t, []any{float64(123), float64(456)}, ret["key3"].(map[string]any)["key33"])
 | |
| 		assert.Equal(t, map[string]any{"key41": "41"}, ret["key4"].([]any)[0].(map[string]any))
 | |
| 
 | |
| 	})
 | |
| 	t.Run("field not in return", func(t *testing.T) {
 | |
| 		codeTpl = `
 | |
| async def main(args:Args)->Output:
 | |
|     params = args.params
 | |
|     ret: Output = {
 | |
|         "key0": params['input'] + params['input'],
 | |
|         "key1": ["hello", "world"], 
 | |
|   		"key2": [123, "345"], 
 | |
|         "key3": { 
 | |
|             "key31": "hi",
 | |
| 			"key32": "hello",
 | |
| 			"key34": {
 | |
| 				"key341":"123"
 | |
| 				}
 | |
|         },
 | |
|     }
 | |
|     return ret
 | |
| `
 | |
| 
 | |
| 		ret := map[string]any{
 | |
| 			"key0": int64(11231123),
 | |
| 			"key1": []any{"hello", "world"},
 | |
| 			"key2": []interface{}{int64(123), "345"},
 | |
| 			"key3": map[string]interface{}{"key31": "hi", "key32": "hello", "key34": map[string]interface{}{"key341": "123"}},
 | |
| 		}
 | |
| 
 | |
| 		response := &coderunner.RunResponse{
 | |
| 			Result: ret,
 | |
| 		}
 | |
| 		mockRunner.EXPECT().Run(gomock.Any(), gomock.Any()).Return(response, nil)
 | |
| 
 | |
| 		ctx := t.Context()
 | |
| 		c := &CodeRunner{
 | |
| 			config: &Config{
 | |
| 				Code:     codeTpl,
 | |
| 				Language: coderunner.Python,
 | |
| 				OutputConfig: map[string]*vo.TypeInfo{
 | |
| 					"key0": {Type: vo.DataTypeInteger},
 | |
| 					"key1": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeString}},
 | |
| 					"key2": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
 | |
| 					"key3": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
 | |
| 						"key31": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						"key32": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						"key33": &vo.TypeInfo{Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
 | |
| 						"key34": &vo.TypeInfo{Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
 | |
| 							"key341": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 							"key342": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						}},
 | |
| 					}},
 | |
| 					"key4": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
 | |
| 						"key31": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						"key32": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						"key33": &vo.TypeInfo{Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
 | |
| 						"key34": &vo.TypeInfo{Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
 | |
| 							"key341": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 							"key342": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						},
 | |
| 						}},
 | |
| 					},
 | |
| 				},
 | |
| 				Runner: mockRunner,
 | |
| 			},
 | |
| 		}
 | |
| 		ret, err := c.RunCode(ctx, map[string]any{
 | |
| 			"input": "1123",
 | |
| 		})
 | |
| 
 | |
| 		assert.NoError(t, err)
 | |
| 		assert.Equal(t, int64(11231123), ret["key0"])
 | |
| 		assert.Equal(t, []any{"hello", "world"}, ret["key1"])
 | |
| 		assert.Equal(t, []any{float64(123), float64(345)}, ret["key2"])
 | |
| 		assert.Equal(t, nil, ret["key4"])
 | |
| 		assert.Equal(t, nil, ret["key3"].(map[string]any)["key33"])
 | |
| 	})
 | |
| 	t.Run("field convert failed", func(t *testing.T) {
 | |
| 		codeTpl = `
 | |
| async def main(args:Args)->Output:
 | |
|     params = args.params
 | |
|     ret: Output = {
 | |
|         "key0": params['input'] + params['input'],
 | |
|         "key1": ["hello", "world"], 
 | |
|   		"key2": [123, "345"], 
 | |
|         "key3": { 
 | |
|             "key31": "hi",
 | |
| 			"key32": "hello",
 | |
| 			"key34": {
 | |
| 				"key341":"123",
 | |
| 				"key343": ["hello", "world"],
 | |
| 				}
 | |
|         },
 | |
|     }
 | |
|     return ret
 | |
| `
 | |
| 		ctx := t.Context()
 | |
| 		ctx = ctxcache.Init(ctx)
 | |
| 		ret := map[string]any{
 | |
| 			"key0": int64(11231123),
 | |
| 			"key1": []any{"hello", "world"},
 | |
| 			"key2": []interface{}{int64(123), "345"},
 | |
| 			"key3": map[string]interface{}{"key31": "hi", "key32": "hello", "key34": map[string]interface{}{"key341": "123", "key343": []any{"hello", "world"}}},
 | |
| 		}
 | |
| 		response := &coderunner.RunResponse{
 | |
| 			Result: ret,
 | |
| 		}
 | |
| 		mockRunner.EXPECT().Run(gomock.Any(), gomock.Any()).Return(response, nil)
 | |
| 
 | |
| 		c := &CodeRunner{
 | |
| 			config: &Config{
 | |
| 				Code:     codeTpl,
 | |
| 				Language: coderunner.Python,
 | |
| 				OutputConfig: map[string]*vo.TypeInfo{
 | |
| 					"key0": {Type: vo.DataTypeInteger},
 | |
| 					"key1": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
 | |
| 					"key2": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
 | |
| 					"key3": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
 | |
| 						"key31": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						"key32": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 						"key33": &vo.TypeInfo{Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
 | |
| 						"key34": &vo.TypeInfo{Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
 | |
| 							"key341": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 							"key342": &vo.TypeInfo{Type: vo.DataTypeString},
 | |
| 							"key343": &vo.TypeInfo{Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
 | |
| 						}},
 | |
| 					},
 | |
| 					},
 | |
| 				},
 | |
| 				Runner: mockRunner,
 | |
| 			},
 | |
| 		}
 | |
| 		ret, err := c.RunCode(ctx, map[string]any{
 | |
| 			"input": "1123",
 | |
| 		})
 | |
| 
 | |
| 		assert.NoError(t, err)
 | |
| 		assert.NoError(t, err)
 | |
| 		assert.Equal(t, int64(11231123), ret["key0"])
 | |
| 		assert.Equal(t, []any{float64(123), float64(345)}, ret["key2"])
 | |
| 
 | |
| 		warnings, ok := ctxcache.Get[nodes.ConversionWarnings](ctx, coderRunnerWarnErrorLevelCtxKey)
 | |
| 		assert.True(t, ok)
 | |
| 		s := warnings.Error()
 | |
| 		assert.Contains(t, s, "field key3.key34.key343.0 is not number")
 | |
| 		assert.Contains(t, s, "field key3.key34.key343.1 is not number")
 | |
| 		assert.Contains(t, s, "field key1.0 is not number")
 | |
| 		assert.Contains(t, s, "field key1.1 is not number")
 | |
| 
 | |
| 	})
 | |
| }
 |