236 lines
5.7 KiB
Go
236 lines
5.7 KiB
Go
/*
|
|
* 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")
|
|
})
|
|
}
|