package providers import ( "encoding/json" "testing" ) // ── ContentPart Tests ────────────────────── func TestContentPart_TextOnly(t *testing.T) { msg := Message{ Role: "user", Content: "hello", ContentParts: []ContentPart{ {Type: "text", Text: "hello"}, }, } if len(msg.ContentParts) != 1 { t.Fatalf("expected 1 part, got %d", len(msg.ContentParts)) } if msg.ContentParts[0].Type != "text" { t.Errorf("type = %q, want text", msg.ContentParts[0].Type) } } func TestContentPart_WithImage(t *testing.T) { msg := Message{ Role: "user", Content: "describe this", ContentParts: []ContentPart{ {Type: "text", Text: "describe this"}, {Type: "image_url", ImageURL: &ImageURL{URL: "data:image/png;base64,abc123", Detail: "auto"}}, }, } if len(msg.ContentParts) != 2 { t.Fatalf("expected 2 parts, got %d", len(msg.ContentParts)) } if msg.ContentParts[1].ImageURL == nil { t.Fatal("expected non-nil ImageURL") } if msg.ContentParts[1].ImageURL.URL != "data:image/png;base64,abc123" { t.Errorf("URL = %q", msg.ContentParts[1].ImageURL.URL) } } // ── OpenAI Multimodal Serialization ──────── func TestOpenAI_TextOnlyContent_String(t *testing.T) { // When Content is a string, it should serialize as a JSON string msg := openaiMessage{ Role: "user", Content: "hello world", } data, err := json.Marshal(msg) if err != nil { t.Fatalf("Marshal: %v", err) } var raw map[string]interface{} json.Unmarshal(data, &raw) content := raw["content"] if s, ok := content.(string); !ok || s != "hello world" { t.Errorf("content = %v (%T), want string 'hello world'", content, content) } } func TestOpenAI_MultimodalContent_Array(t *testing.T) { // When Content is an array, it should serialize as a JSON array msg := openaiMessage{ Role: "user", Content: []openaiContentPart{ {Type: "text", Text: "describe this"}, {Type: "image_url", ImageURL: &openaiImageURL{URL: "data:image/png;base64,abc", Detail: "auto"}}, }, } data, err := json.Marshal(msg) if err != nil { t.Fatalf("Marshal: %v", err) } var raw map[string]interface{} json.Unmarshal(data, &raw) content := raw["content"] arr, ok := content.([]interface{}) if !ok { t.Fatalf("content is %T, want array", content) } if len(arr) != 2 { t.Fatalf("content has %d elements, want 2", len(arr)) } // Verify first part is text part0 := arr[0].(map[string]interface{}) if part0["type"] != "text" { t.Errorf("part[0].type = %v, want text", part0["type"]) } // Verify second part is image_url part1 := arr[1].(map[string]interface{}) if part1["type"] != "image_url" { t.Errorf("part[1].type = %v, want image_url", part1["type"]) } } // ── Anthropic Image Source ───────────────── func TestAnthropic_ImageSourceBlock(t *testing.T) { block := anthropicContentBlock{ Type: "image", Source: &anthropicImageSource{ Type: "base64", MediaType: "image/jpeg", Data: "abc123", }, } data, err := json.Marshal(block) if err != nil { t.Fatalf("Marshal: %v", err) } var raw map[string]interface{} json.Unmarshal(data, &raw) if raw["type"] != "image" { t.Errorf("type = %v", raw["type"]) } source := raw["source"].(map[string]interface{}) if source["type"] != "base64" { t.Errorf("source.type = %v", source["type"]) } if source["media_type"] != "image/jpeg" { t.Errorf("source.media_type = %v", source["media_type"]) } if source["data"] != "abc123" { t.Errorf("source.data = %v", source["data"]) } } func TestAnthropic_TextBlockUnchanged(t *testing.T) { block := anthropicContentBlock{ Type: "text", Text: "hello", } data, err := json.Marshal(block) if err != nil { t.Fatalf("Marshal: %v", err) } var raw map[string]interface{} json.Unmarshal(data, &raw) if raw["type"] != "text" { t.Errorf("type = %v", raw["type"]) } if raw["text"] != "hello" { t.Errorf("text = %v", raw["text"]) } // Should NOT have source field if _, ok := raw["source"]; ok { t.Error("text block should not have source field") } } // ── parseDataURI ─────────────────────────── func TestParseDataURI_Valid(t *testing.T) { tests := []struct { uri string wantType string wantData string }{ {"data:image/jpeg;base64,/9j/4AAQ", "image/jpeg", "/9j/4AAQ"}, {"data:image/png;base64,abc123", "image/png", "abc123"}, {"data:image/webp;base64,RIFF", "image/webp", "RIFF"}, } for _, tc := range tests { mediaType, data := parseDataURI(tc.uri) if mediaType != tc.wantType { t.Errorf("parseDataURI(%q) mediaType = %q, want %q", tc.uri, mediaType, tc.wantType) } if data != tc.wantData { t.Errorf("parseDataURI(%q) data = %q, want %q", tc.uri, data, tc.wantData) } } } func TestParseDataURI_Invalid(t *testing.T) { tests := []string{ "", "not-a-data-uri", "data:nocomma", "https://example.com/image.png", } for _, uri := range tests { mediaType, data := parseDataURI(uri) if mediaType != "" || data != "" { t.Errorf("parseDataURI(%q) = (%q, %q), want empty", uri, mediaType, data) } } }