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:
Hein
2026-03-31 15:10:07 +02:00
parent acd780ac9c
commit f41c512f36
54 changed files with 1937 additions and 365 deletions

View File

@@ -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)
}