test(tools): add unit tests for error handling functions
* Implement tests for error functions like errRequiredField, errInvalidField, and errEntityNotFound. * Ensure proper metadata is returned for various error scenarios. * Validate error handling in CRM, Files, and other tools. * Introduce tests for parsing stored file IDs and UUIDs. * Enhance coverage for helper functions related to project resolution and session management.
This commit is contained in:
@@ -52,7 +52,7 @@ type SaveFileOutput struct {
|
||||
}
|
||||
|
||||
type LoadFileInput struct {
|
||||
ID string `json:"id" jsonschema:"the stored file id"`
|
||||
ID string `json:"id" jsonschema:"the stored file id or amcs://files/{id} URI"`
|
||||
}
|
||||
|
||||
type LoadFileOutput struct {
|
||||
@@ -95,7 +95,7 @@ func (t *FilesTool) Upload(ctx context.Context, req *mcp.CallToolRequest, in Upl
|
||||
b64 := strings.TrimSpace(in.ContentBase64)
|
||||
|
||||
if path != "" && b64 != "" {
|
||||
return nil, UploadFileOutput{}, errInvalidInput("provide content_path or content_base64, not both")
|
||||
return nil, UploadFileOutput{}, errMutuallyExclusiveFields("content_path", "content_base64")
|
||||
}
|
||||
|
||||
var content []byte
|
||||
@@ -103,7 +103,11 @@ func (t *FilesTool) Upload(ctx context.Context, req *mcp.CallToolRequest, in Upl
|
||||
|
||||
if path != "" {
|
||||
if !filepath.IsAbs(path) {
|
||||
return nil, UploadFileOutput{}, errInvalidInput("content_path must be an absolute path")
|
||||
return nil, UploadFileOutput{}, errInvalidField(
|
||||
"content_path",
|
||||
"content_path must be an absolute path",
|
||||
"pass an absolute path on the server filesystem",
|
||||
)
|
||||
}
|
||||
var err error
|
||||
content, err = os.ReadFile(path)
|
||||
@@ -112,7 +116,7 @@ func (t *FilesTool) Upload(ctx context.Context, req *mcp.CallToolRequest, in Upl
|
||||
}
|
||||
} else {
|
||||
if b64 == "" {
|
||||
return nil, UploadFileOutput{}, errInvalidInput("content_path or content_base64 is required")
|
||||
return nil, UploadFileOutput{}, errOneOfRequired("content_path", "content_base64")
|
||||
}
|
||||
if len(b64) > maxBase64ToolBytes {
|
||||
return nil, UploadFileOutput{}, errInvalidInput(
|
||||
@@ -123,7 +127,11 @@ func (t *FilesTool) Upload(ctx context.Context, req *mcp.CallToolRequest, in Upl
|
||||
var err error
|
||||
content, err = decodeBase64(raw)
|
||||
if err != nil {
|
||||
return nil, UploadFileOutput{}, errInvalidInput("content_base64 must be valid base64")
|
||||
return nil, UploadFileOutput{}, errInvalidField(
|
||||
"content_base64",
|
||||
"content_base64 must be valid base64",
|
||||
"pass valid base64 data or a data URL",
|
||||
)
|
||||
}
|
||||
mediaTypeFromSource = dataURLMediaType
|
||||
}
|
||||
@@ -149,7 +157,7 @@ func (t *FilesTool) Save(ctx context.Context, req *mcp.CallToolRequest, in SaveF
|
||||
b64 := strings.TrimSpace(in.ContentBase64)
|
||||
|
||||
if uri != "" && b64 != "" {
|
||||
return nil, SaveFileOutput{}, errInvalidInput("provide content_uri or content_base64, not both")
|
||||
return nil, SaveFileOutput{}, errMutuallyExclusiveFields("content_uri", "content_base64")
|
||||
}
|
||||
if len(b64) > maxBase64ToolBytes {
|
||||
return nil, SaveFileOutput{}, errInvalidInput(
|
||||
@@ -162,28 +170,44 @@ func (t *FilesTool) Save(ctx context.Context, req *mcp.CallToolRequest, in SaveF
|
||||
|
||||
if uri != "" {
|
||||
if !strings.HasPrefix(uri, fileURIPrefix) {
|
||||
return nil, SaveFileOutput{}, errInvalidInput("content_uri must be an amcs://files/{id} URI")
|
||||
return nil, SaveFileOutput{}, errInvalidField(
|
||||
"content_uri",
|
||||
"content_uri must be an amcs://files/{id} URI",
|
||||
"pass an amcs://files/{id} URI returned by upload_file or POST /files",
|
||||
)
|
||||
}
|
||||
rawID := strings.TrimPrefix(uri, fileURIPrefix)
|
||||
id, err := parseUUID(rawID)
|
||||
if err != nil {
|
||||
return nil, SaveFileOutput{}, errInvalidInput("content_uri contains an invalid file id")
|
||||
return nil, SaveFileOutput{}, errInvalidField(
|
||||
"content_uri",
|
||||
"content_uri contains an invalid file id",
|
||||
"pass a valid amcs://files/{id} URI",
|
||||
)
|
||||
}
|
||||
file, err := t.store.GetStoredFile(ctx, id)
|
||||
if err != nil {
|
||||
return nil, SaveFileOutput{}, errInvalidInput("content_uri references a file that does not exist")
|
||||
return nil, SaveFileOutput{}, errInvalidField(
|
||||
"content_uri",
|
||||
"content_uri references a file that does not exist",
|
||||
"upload the file first or pass an existing amcs://files/{id} URI",
|
||||
)
|
||||
}
|
||||
content = file.Content
|
||||
mediaTypeFromSource = file.MediaType
|
||||
} else {
|
||||
contentBase64, mediaTypeFromDataURL := splitDataURL(b64)
|
||||
if contentBase64 == "" {
|
||||
return nil, SaveFileOutput{}, errInvalidInput("content_base64 or content_uri is required")
|
||||
return nil, SaveFileOutput{}, errOneOfRequired("content_base64", "content_uri")
|
||||
}
|
||||
var err error
|
||||
content, err = decodeBase64(contentBase64)
|
||||
if err != nil {
|
||||
return nil, SaveFileOutput{}, errInvalidInput("content_base64 must be valid base64")
|
||||
return nil, SaveFileOutput{}, errInvalidField(
|
||||
"content_base64",
|
||||
"content_base64 must be valid base64",
|
||||
"pass valid base64 data or a data URL",
|
||||
)
|
||||
}
|
||||
mediaTypeFromSource = mediaTypeFromDataURL
|
||||
}
|
||||
@@ -205,7 +229,7 @@ func (t *FilesTool) Save(ctx context.Context, req *mcp.CallToolRequest, in SaveF
|
||||
const fileURIPrefix = "amcs://files/"
|
||||
|
||||
func (t *FilesTool) GetRaw(ctx context.Context, rawID string) (thoughttypes.StoredFile, error) {
|
||||
id, err := parseUUID(strings.TrimSpace(rawID))
|
||||
id, err := parseStoredFileID(rawID)
|
||||
if err != nil {
|
||||
return thoughttypes.StoredFile{}, err
|
||||
}
|
||||
@@ -213,7 +237,7 @@ func (t *FilesTool) GetRaw(ctx context.Context, rawID string) (thoughttypes.Stor
|
||||
}
|
||||
|
||||
func (t *FilesTool) Load(ctx context.Context, _ *mcp.CallToolRequest, in LoadFileInput) (*mcp.CallToolResult, LoadFileOutput, error) {
|
||||
id, err := parseUUID(in.ID)
|
||||
id, err := parseStoredFileID(in.ID)
|
||||
if err != nil {
|
||||
return nil, LoadFileOutput{}, err
|
||||
}
|
||||
@@ -243,8 +267,7 @@ func (t *FilesTool) Load(ctx context.Context, _ *mcp.CallToolRequest, in LoadFil
|
||||
}
|
||||
|
||||
func (t *FilesTool) ReadResource(ctx context.Context, req *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
|
||||
rawID := strings.TrimPrefix(req.Params.URI, fileURIPrefix)
|
||||
id, err := parseUUID(strings.TrimSpace(rawID))
|
||||
id, err := parseStoredFileID(req.Params.URI)
|
||||
if err != nil {
|
||||
return nil, mcp.ResourceNotFoundError(req.Params.URI)
|
||||
}
|
||||
@@ -309,7 +332,7 @@ func (t *FilesTool) List(ctx context.Context, req *mcp.CallToolRequest, in ListF
|
||||
func (t *FilesTool) SaveDecoded(ctx context.Context, req *mcp.CallToolRequest, in SaveFileDecodedInput) (SaveFileOutput, error) {
|
||||
name := strings.TrimSpace(in.Name)
|
||||
if name == "" {
|
||||
return SaveFileOutput{}, errInvalidInput("name is required")
|
||||
return SaveFileOutput{}, errRequiredField("name")
|
||||
}
|
||||
if len(in.Content) == 0 {
|
||||
return SaveFileOutput{}, errInvalidInput("decoded file content must not be empty")
|
||||
@@ -492,3 +515,9 @@ func normalizeFileLimit(limit int) int {
|
||||
return limit
|
||||
}
|
||||
}
|
||||
|
||||
func parseStoredFileID(raw string) (uuid.UUID, error) {
|
||||
value := strings.TrimSpace(raw)
|
||||
value = strings.TrimPrefix(value, fileURIPrefix)
|
||||
return parseUUID(value)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user