/* * 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/stretchr/testify/assert" "go.uber.org/mock/gomock" "github.com/coze-dev/coze-studio/backend/infra/contract/coderunner" "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") }) }