/* * 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 service import ( "bytes" "encoding/json" "errors" "testing" "github.com/bytedance/mockey" "github.com/bytedance/sonic" "github.com/getkin/kin-openapi/openapi3" "github.com/stretchr/testify/assert" "github.com/coze-dev/coze-studio/backend/pkg/errorx" model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin" ) func TestToolExecutorProcessWithInvalidRespProcessStrategyOfReturnDefault(t *testing.T) { executor := &toolExecutor{ invalidRespProcessStrategy: model.InvalidResponseProcessStrategyOfReturnDefault, } paramVal := ` { "a1": 1, "b1": { "a2": 2.1 }, "c1": [ { "a2": 3.1 } ], "d1": "hello", "f1": true } ` decoder := sonic.ConfigDefault.NewDecoder(bytes.NewBufferString(paramVal)) decoder.UseNumber() paramValMap := map[string]any{} err := decoder.Decode(¶mValMap) assert.NoError(t, err) paramSchema := &openapi3.Schema{ Type: openapi3.TypeObject, Properties: map[string]*openapi3.SchemaRef{ "a1": { Value: &openapi3.Schema{ Type: openapi3.TypeInteger, }, }, "b1": { Value: &openapi3.Schema{ Type: openapi3.TypeObject, Properties: map[string]*openapi3.SchemaRef{ "a2": { Value: &openapi3.Schema{ Type: openapi3.TypeNumber, }, }, }, }, }, "c1": { Value: &openapi3.Schema{ Type: openapi3.TypeArray, Items: &openapi3.SchemaRef{ Value: &openapi3.Schema{ Type: openapi3.TypeObject, Properties: map[string]*openapi3.SchemaRef{ "a2": { Value: &openapi3.Schema{ Type: openapi3.TypeNumber, }, }, }, }, }, }, }, "d1": { Value: &openapi3.Schema{ Type: openapi3.TypeString, }, }, "f1": { Value: &openapi3.Schema{ Type: openapi3.TypeBoolean, }, }, }, } processedParamValMap, err := executor.processWithInvalidRespProcessStrategyOfReturnDefault(nil, paramValMap, paramSchema) assert.NoError(t, err) assert.NotNil(t, processedParamValMap) assert.Equal(t, int64(1), processedParamValMap["a1"]) assert.Equal(t, json.Number("2.1"), processedParamValMap["b1"].(map[string]any)["a2"]) assert.Equal(t, json.Number("3.1"), processedParamValMap["c1"].([]any)[0].(map[string]any)["a2"]) assert.Equal(t, "hello", processedParamValMap["d1"]) assert.Equal(t, true, processedParamValMap["f1"]) } func TestToolExecutorProcessWithInvalidRespProcessStrategyOfReturnErr(t *testing.T) { executor := &toolExecutor{ invalidRespProcessStrategy: model.InvalidResponseProcessStrategyOfReturnErr, } mockey.PatchConvey("integer", t, func() { paramVal := ` { "a": 1 } ` decoder := sonic.ConfigDefault.NewDecoder(bytes.NewBufferString(paramVal)) decoder.UseNumber() paramValMap := map[string]any{} err := decoder.Decode(¶mValMap) assert.NoError(t, err) paramSchema := &openapi3.Schema{ Type: openapi3.TypeObject, Properties: map[string]*openapi3.SchemaRef{ "a": { Value: &openapi3.Schema{ Type: openapi3.TypeString, }, }, }, } _, err = executor.processWithInvalidRespProcessStrategyOfReturnErr(nil, paramValMap, paramSchema) var customErr errorx.StatusError assert.True(t, errors.As(err, &customErr)) assert.Equal(t, "execute tool failed : expected 'a' to be of type 'string', but got 'json.Number'", customErr.Msg()) paramSchema = &openapi3.Schema{ Type: openapi3.TypeObject, Properties: map[string]*openapi3.SchemaRef{ "a1": { Value: &openapi3.Schema{ Type: openapi3.TypeInteger, }, }, }, } _, err = executor.processWithInvalidRespProcessStrategyOfReturnErr(nil, paramValMap, paramSchema) assert.NoError(t, err) }) mockey.PatchConvey("string", t, func() { paramVal := ` { "a": "1" } ` decoder := sonic.ConfigDefault.NewDecoder(bytes.NewBufferString(paramVal)) decoder.UseNumber() paramValMap := map[string]any{} err := decoder.Decode(¶mValMap) assert.NoError(t, err) paramSchema := &openapi3.Schema{ Type: openapi3.TypeObject, Properties: map[string]*openapi3.SchemaRef{ "a": { Value: &openapi3.Schema{ Type: openapi3.TypeInteger, }, }, }, } _, err = executor.processWithInvalidRespProcessStrategyOfReturnErr(nil, paramValMap, paramSchema) var customErr errorx.StatusError assert.True(t, errors.As(err, &customErr)) assert.Equal(t, "execute tool failed : expected 'a' to be of type 'integer', but got 'string'", customErr.Msg()) paramSchema = &openapi3.Schema{ Type: openapi3.TypeObject, Properties: map[string]*openapi3.SchemaRef{ "a": { Value: &openapi3.Schema{ Type: openapi3.TypeString, }, }, }, } _, err = executor.processWithInvalidRespProcessStrategyOfReturnErr(nil, paramValMap, paramSchema) assert.NoError(t, err) }) mockey.PatchConvey("boolean", t, func() { paramVal := ` { "a": false } ` decoder := sonic.ConfigDefault.NewDecoder(bytes.NewBufferString(paramVal)) decoder.UseNumber() paramValMap := map[string]any{} err := decoder.Decode(¶mValMap) assert.NoError(t, err) paramSchema := &openapi3.Schema{ Type: openapi3.TypeObject, Properties: map[string]*openapi3.SchemaRef{ "a": { Value: &openapi3.Schema{ Type: openapi3.TypeString, }, }, }, } _, err = executor.processWithInvalidRespProcessStrategyOfReturnErr(nil, paramValMap, paramSchema) var customErr errorx.StatusError assert.True(t, errors.As(err, &customErr)) assert.Equal(t, "execute tool failed : expected 'a' to be of type 'string', but got 'bool'", customErr.Msg()) paramSchema = &openapi3.Schema{ Type: openapi3.TypeObject, Properties: map[string]*openapi3.SchemaRef{ "a": { Value: &openapi3.Schema{ Type: openapi3.TypeBoolean, }, }, }, } _, err = executor.processWithInvalidRespProcessStrategyOfReturnErr(nil, paramValMap, paramSchema) assert.NoError(t, err) }) }