package shared_test import ( "strings" "testing" "git.flytoex.net/yuanwei/flyto-agent/pkg/flyto" "git.flytoex.net/yuanwei/flyto-agent/pkg/providers/shared" ) // TestCheckNoImageBlocks_NoImages accepts messages without image blocks. // // TestCheckNoImageBlocks_NoImages 无 image block 消息应通过. func TestCheckNoImageBlocks_NoImages(t *testing.T) { msgs := []flyto.Message{ flyto.UserText("hello"), { Role: flyto.RoleAssistant, Blocks: []flyto.Block{ flyto.TextBlock("hi"), flyto.ToolUseBlock("tu1", "Read", map[string]any{"path": "a"}), }, }, } if err := shared.CheckNoImageBlocks(msgs, "openai"); err != nil { t.Errorf("unexpected error: %v", err) } } // TestCheckNoImageBlocks_RejectsImage surfaces an error naming the provider // when an image block appears. Prevents silent drop -- caller must know // the image never reached the model. // // TestCheckNoImageBlocks_RejectsImage 出现 image block 时返 error 并带 // provider 名, 让调用方明白"图没送到". 防 silent drop. func TestCheckNoImageBlocks_RejectsImage(t *testing.T) { msgs := []flyto.Message{ { Role: flyto.RoleUser, Blocks: []flyto.Block{ flyto.TextBlock("see this"), flyto.ImageBlockBase64("image/png", "base64data"), }, }, } err := shared.CheckNoImageBlocks(msgs, "openai") if err == nil { t.Fatal("expected error for image block, got nil") } if !strings.Contains(err.Error(), "openai") { t.Errorf("error should name the provider, got: %v", err) } if !strings.Contains(err.Error(), "anthropic") { t.Errorf("error should point callers to anthropic, got: %v", err) } } // TestCheckNoImageBlocks_EmptyMessages returns nil for no input. // // TestCheckNoImageBlocks_EmptyMessages 空消息列表无 error. func TestCheckNoImageBlocks_EmptyMessages(t *testing.T) { if err := shared.CheckNoImageBlocks(nil, "x"); err != nil { t.Errorf("nil msgs should be OK, got %v", err) } if err := shared.CheckNoImageBlocks([]flyto.Message{}, "x"); err != nil { t.Errorf("empty msgs should be OK, got %v", err) } } // TestCheckNoImageBlocks_NestedInToolResult catches the path-B silent drop // regression: image wrapped inside ToolResult.ResultBlocks must NOT slip // past the guard. This is the scenario where the tool returns an image // (via Result.Data) and the engine wraps it into a tool_result content // array; non-vision providers must still reject it. // // TestCheckNoImageBlocks_NestedInToolResult 防路径 B silent drop 回归: // image 被包进 ToolResult.ResultBlocks 不能绕过 guard. 对应工具 Result.Data // 返图后 engine 包装成 tool_result array 的场景, 非 vision provider 必须 // 照样拒绝. func TestCheckNoImageBlocks_NestedInToolResult(t *testing.T) { msgs := []flyto.Message{ { Role: flyto.RoleUser, Blocks: []flyto.Block{ flyto.ToolResultBlocks("tu1", []flyto.Block{ flyto.TextBlock("Here is the screenshot:"), flyto.ImageBlockBase64("image/png", "base64data"), }, false), }, }, } err := shared.CheckNoImageBlocks(msgs, "gemini") if err == nil { t.Fatal("expected error for image nested in ResultBlocks, got nil (silent-drop regression)") } if !strings.Contains(err.Error(), "gemini") { t.Errorf("error should name provider, got: %v", err) } } // TestCheckNoImageBlocks_ToolResultNoImage accepts array-form tool_result // that contains only text blocks. // // TestCheckNoImageBlocks_ToolResultNoImage 仅含 text 的 ResultBlocks array // 形式 tool_result 应通过. func TestCheckNoImageBlocks_ToolResultNoImage(t *testing.T) { msgs := []flyto.Message{ { Role: flyto.RoleUser, Blocks: []flyto.Block{ flyto.ToolResultBlocks("tu1", []flyto.Block{ flyto.TextBlock("plain text only"), }, false), }, }, } if err := shared.CheckNoImageBlocks(msgs, "openai"); err != nil { t.Errorf("text-only ResultBlocks should pass, got %v", err) } }