203 lines
5.1 KiB
Go
203 lines
5.1 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|