/* * Copyright 2024 CloudWeGo 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 schema import ( "bytes" "encoding/gob" "encoding/json" "testing" "github.com/eino-contrib/jsonschema" "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestParamsOneOfToJSONSchema(t *testing.T) { convey.Convey("ParamsOneOfToJSONSchema", t, func() { var ( oneOf ParamsOneOf converted any err error ) convey.Convey("user provides JSON schema directly, use what the user provides", func() { oneOf.jsonschema = &jsonschema.Schema{ Type: "string", Description: "this is the only argument", } converted, err = oneOf.ToJSONSchema() convey.So(err, convey.ShouldBeNil) convey.So(converted, convey.ShouldResemble, oneOf.jsonschema) }) convey.Convey("user provides map[string]ParameterInfo, converts to json schema", func() { oneOf.params = map[string]*ParameterInfo{ "arg1": { Type: String, Desc: "this is the first argument", Required: true, Enum: []string{"1", "2"}, }, "arg2": { Type: Object, Desc: "this is the second argument", SubParams: map[string]*ParameterInfo{ "sub_arg1": { Type: String, Desc: "this is the sub argument", Required: true, Enum: []string{"1", "2"}, }, "sub_arg2": { Type: String, Desc: "this is the sub argument 2", }, }, Required: true, }, "arg3": { Type: Array, Desc: "this is the third argument", ElemInfo: &ParameterInfo{ Type: String, Desc: "this is the element of the third argument", Required: true, Enum: []string{"1", "2"}, }, Required: true, }, } converted, err = oneOf.ToJSONSchema() convey.So(err, convey.ShouldBeNil) }) convey.Convey("user provides map[string]ParameterInfo, converts to json schema in order", func() { params := &ParamsOneOf{ params: map[string]*ParameterInfo{ "c": { Type: "string", }, "a": { Type: "object", SubParams: map[string]*ParameterInfo{ "z": { Type: "number", }, "y": { Type: "string", }, }, }, "b": { Type: "array", ElemInfo: &ParameterInfo{ Type: "object", SubParams: map[string]*ParameterInfo{ "p": { Type: "integer", }, "o": { Type: "boolean", }, }, }, }, }, } schema1, err := params.ToJSONSchema() assert.NoError(t, err) json1, err := json.Marshal(schema1) assert.NoError(t, err) schema2, err := params.ToJSONSchema() assert.NoError(t, err) json2, err := json.Marshal(schema2) assert.NoError(t, err) assert.Equal(t, string(json1), string(json2)) }) }) } func TestToolInfoSerialization(t *testing.T) { ti1 := &ToolInfo{ ParamsOneOf: NewParamsOneOfByParams(map[string]*ParameterInfo{ "a": { Type: String, Desc: "desc", }, }), } ti2 := &ToolInfo{ ParamsOneOf: NewParamsOneOfByJSONSchema(&jsonschema.Schema{ Type: "string", }), } // json b, err := json.Marshal(ti1) assert.NoError(t, err) result := &ToolInfo{} err = json.Unmarshal(b, result) assert.NoError(t, err) assert.Equal(t, ti1, result) b, err = json.Marshal(ti2) assert.NoError(t, err) result = &ToolInfo{} err = json.Unmarshal(b, result) assert.NoError(t, err) assert.Equal(t, ti2, result) // gob buf := new(bytes.Buffer) err = gob.NewEncoder(buf).Encode(ti1) assert.NoError(t, err) result = &ToolInfo{} err = gob.NewDecoder(buf).Decode(result) assert.NoError(t, err) assert.Equal(t, ti1, result) buf = new(bytes.Buffer) err = gob.NewEncoder(buf).Encode(ti2) assert.NoError(t, err) result = &ToolInfo{} err = gob.NewDecoder(buf).Decode(result) assert.NoError(t, err) assert.Equal(t, ti2, result) // json roundtrip with empty-but-non-nil params map: must not collapse to nil, // otherwise the params form is silently dropped. tiEmpty := &ToolInfo{ ParamsOneOf: NewParamsOneOfByParams(map[string]*ParameterInfo{}), } b, err = json.Marshal(tiEmpty) assert.NoError(t, err) result = &ToolInfo{} err = json.Unmarshal(b, result) assert.NoError(t, err) assert.NotNil(t, result.ParamsOneOf) assert.NotNil(t, result.ParamsOneOf.params) assert.Equal(t, tiEmpty, result) } func TestMCPToolResult_NilErrorCode(t *testing.T) { result := &MCPToolResult{ CallID: "test-call", Name: "test-tool", Content: "some result", Error: &MCPToolCallError{ Code: nil, Message: "something went wrong", }, } require.NotPanics(t, func() { s := result.String() t.Logf("String output: %s", s) assert.Contains(t, s, "something went wrong") }, "BUG: MCPToolResult.String() should not panic when Error.Code is nil") } func TestMCPToolResult_WithErrorCode(t *testing.T) { code := int64(500) result := &MCPToolResult{ CallID: "test-call", Name: "test-tool", Content: "", Error: &MCPToolCallError{ Code: &code, Message: "internal server error", }, } require.NotPanics(t, func() { s := result.String() assert.Contains(t, s, "500") assert.Contains(t, s, "internal server error") }) }