Files
mistral-go-sdk/chat/content_test.go
vikingowl 2b980e14b3 fix: post-review fixes — metadata, unknown types, typed tools, API polish
1. Add README, LICENSE (MIT), .gitignore, Makefile, CHANGELOG
2. Add Version constant and User-Agent header to all requests
3. Rename SetStream to EnableStream (narrower API surface)
4. Fix FinishReason in CompletionStreamChoice to use typed *FinishReason
5. Type conversation entry Content as chat.Content instead of json.RawMessage
6. Graceful unknown type handling — UnknownEntry, UnknownEvent,
   UnknownChunk, UnknownMessage, UnknownAgentTool all return data
   instead of erroring on unrecognized discriminator values
7. Type agent tools with AgentTool sealed interface + UnmarshalAgentTool
8. Add pagination params to ListConversations and ListLibraries
9. Move openapi.yaml to docs/openapi.yaml
2026-03-05 20:51:24 +01:00

368 lines
8.7 KiB
Go

package chat
import (
"encoding/json"
"testing"
)
func TestTextChunk_MarshalJSON(t *testing.T) {
chunk := &TextChunk{Text: "hello"}
data, err := json.Marshal(chunk)
if err != nil {
t.Fatal(err)
}
want := `{"type":"text","text":"hello"}`
if string(data) != want {
t.Errorf("got %s, want %s", data, want)
}
}
func TestTextChunk_RoundTrip(t *testing.T) {
original := &TextChunk{Text: "hello world"}
data, err := json.Marshal(original)
if err != nil {
t.Fatal(err)
}
chunk, err := UnmarshalContentChunk(data)
if err != nil {
t.Fatal(err)
}
tc, ok := chunk.(*TextChunk)
if !ok {
t.Fatalf("expected *TextChunk, got %T", chunk)
}
if tc.Text != original.Text {
t.Errorf("got %q, want %q", tc.Text, original.Text)
}
}
func TestImageURLChunk_MarshalJSON(t *testing.T) {
chunk := &ImageURLChunk{ImageURL: ImageURL{URL: "https://example.com/img.png"}}
data, err := json.Marshal(chunk)
if err != nil {
t.Fatal(err)
}
var m map[string]any
json.Unmarshal(data, &m)
if m["type"] != "image_url" {
t.Errorf("expected type=image_url, got %v", m["type"])
}
imgURL, ok := m["image_url"].(map[string]any)
if !ok {
t.Fatalf("expected image_url object, got %T", m["image_url"])
}
if imgURL["url"] != "https://example.com/img.png" {
t.Errorf("expected url, got %v", imgURL["url"])
}
}
func TestImageURLChunk_RoundTrip(t *testing.T) {
detail := ImageDetailHigh
original := &ImageURLChunk{ImageURL: ImageURL{
URL: "https://example.com/img.png",
Detail: &detail,
}}
data, err := json.Marshal(original)
if err != nil {
t.Fatal(err)
}
chunk, err := UnmarshalContentChunk(data)
if err != nil {
t.Fatal(err)
}
ic, ok := chunk.(*ImageURLChunk)
if !ok {
t.Fatalf("expected *ImageURLChunk, got %T", chunk)
}
if ic.ImageURL.URL != original.ImageURL.URL {
t.Errorf("got URL %q, want %q", ic.ImageURL.URL, original.ImageURL.URL)
}
if ic.ImageURL.Detail == nil || *ic.ImageURL.Detail != ImageDetailHigh {
t.Errorf("got detail %v, want high", ic.ImageURL.Detail)
}
}
func TestImageURL_UnmarshalJSON_String(t *testing.T) {
var u ImageURL
if err := json.Unmarshal([]byte(`"https://example.com/img.png"`), &u); err != nil {
t.Fatal(err)
}
if u.URL != "https://example.com/img.png" {
t.Errorf("got %q, want %q", u.URL, "https://example.com/img.png")
}
}
func TestImageURL_UnmarshalJSON_Object(t *testing.T) {
var u ImageURL
if err := json.Unmarshal([]byte(`{"url":"https://example.com/img.png","detail":"low"}`), &u); err != nil {
t.Fatal(err)
}
if u.URL != "https://example.com/img.png" {
t.Errorf("got url %q", u.URL)
}
if u.Detail == nil || *u.Detail != ImageDetailLow {
t.Errorf("got detail %v, want low", u.Detail)
}
}
func TestDocumentURLChunk_RoundTrip(t *testing.T) {
name := "doc.pdf"
original := &DocumentURLChunk{
DocumentURL: "https://example.com/doc.pdf",
DocumentName: &name,
}
data, err := json.Marshal(original)
if err != nil {
t.Fatal(err)
}
chunk, err := UnmarshalContentChunk(data)
if err != nil {
t.Fatal(err)
}
dc, ok := chunk.(*DocumentURLChunk)
if !ok {
t.Fatalf("expected *DocumentURLChunk, got %T", chunk)
}
if dc.DocumentURL != original.DocumentURL {
t.Errorf("got %q, want %q", dc.DocumentURL, original.DocumentURL)
}
if dc.DocumentName == nil || *dc.DocumentName != name {
t.Errorf("got name %v, want %q", dc.DocumentName, name)
}
}
func TestFileChunk_RoundTrip(t *testing.T) {
original := &FileChunk{FileID: "abc-123"}
data, err := json.Marshal(original)
if err != nil {
t.Fatal(err)
}
chunk, err := UnmarshalContentChunk(data)
if err != nil {
t.Fatal(err)
}
fc, ok := chunk.(*FileChunk)
if !ok {
t.Fatalf("expected *FileChunk, got %T", chunk)
}
if fc.FileID != original.FileID {
t.Errorf("got %q, want %q", fc.FileID, original.FileID)
}
}
func TestReferenceChunk_RoundTrip(t *testing.T) {
original := &ReferenceChunk{ReferenceIDs: []int{1, 2, 3}}
data, err := json.Marshal(original)
if err != nil {
t.Fatal(err)
}
chunk, err := UnmarshalContentChunk(data)
if err != nil {
t.Fatal(err)
}
rc, ok := chunk.(*ReferenceChunk)
if !ok {
t.Fatalf("expected *ReferenceChunk, got %T", chunk)
}
if len(rc.ReferenceIDs) != 3 || rc.ReferenceIDs[0] != 1 {
t.Errorf("got %v, want [1 2 3]", rc.ReferenceIDs)
}
}
func TestThinkChunk_RoundTrip(t *testing.T) {
closed := true
original := &ThinkChunk{
Thinking: []ContentChunk{
&TextChunk{Text: "reasoning step"},
&ReferenceChunk{ReferenceIDs: []int{42}},
},
Closed: &closed,
}
data, err := json.Marshal(original)
if err != nil {
t.Fatal(err)
}
chunk, err := UnmarshalContentChunk(data)
if err != nil {
t.Fatal(err)
}
tc, ok := chunk.(*ThinkChunk)
if !ok {
t.Fatalf("expected *ThinkChunk, got %T", chunk)
}
if len(tc.Thinking) != 2 {
t.Fatalf("got %d thinking chunks, want 2", len(tc.Thinking))
}
if tc.Closed == nil || !*tc.Closed {
t.Error("expected closed=true")
}
text, ok := tc.Thinking[0].(*TextChunk)
if !ok {
t.Fatalf("expected thinking[0] to be *TextChunk, got %T", tc.Thinking[0])
}
if text.Text != "reasoning step" {
t.Errorf("got %q, want %q", text.Text, "reasoning step")
}
}
func TestAudioChunk_RoundTrip(t *testing.T) {
original := &AudioChunk{InputAudio: "base64data=="}
data, err := json.Marshal(original)
if err != nil {
t.Fatal(err)
}
chunk, err := UnmarshalContentChunk(data)
if err != nil {
t.Fatal(err)
}
ac, ok := chunk.(*AudioChunk)
if !ok {
t.Fatalf("expected *AudioChunk, got %T", chunk)
}
if ac.InputAudio != original.InputAudio {
t.Errorf("got %q, want %q", ac.InputAudio, original.InputAudio)
}
}
func TestUnmarshalContentChunk_UnknownType(t *testing.T) {
data := []byte(`{"type":"future_chunk","data":"value"}`)
chunk, err := UnmarshalContentChunk(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
u, ok := chunk.(*UnknownChunk)
if !ok {
t.Fatalf("expected *UnknownChunk, got %T", chunk)
}
if u.Type != "future_chunk" {
t.Errorf("got type %q", u.Type)
}
marshaled, err := json.Marshal(u)
if err != nil {
t.Fatal(err)
}
if string(marshaled) != string(data) {
t.Errorf("round-trip failed: got %s", marshaled)
}
}
func TestContent_TextMarshal(t *testing.T) {
c := TextContent("hello")
data, err := json.Marshal(c)
if err != nil {
t.Fatal(err)
}
if string(data) != `"hello"` {
t.Errorf("got %s, want %q", data, `"hello"`)
}
}
func TestContent_ChunksMarshal(t *testing.T) {
c := ChunksContent(&TextChunk{Text: "hello"})
data, err := json.Marshal(c)
if err != nil {
t.Fatal(err)
}
want := `[{"type":"text","text":"hello"}]`
if string(data) != want {
t.Errorf("got %s, want %s", data, want)
}
}
func TestContent_NullMarshal(t *testing.T) {
var c Content
data, err := json.Marshal(c)
if err != nil {
t.Fatal(err)
}
if string(data) != "null" {
t.Errorf("got %s, want null", data)
}
}
func TestContent_UnmarshalString(t *testing.T) {
var c Content
if err := json.Unmarshal([]byte(`"hello"`), &c); err != nil {
t.Fatal(err)
}
if c.Text == nil || *c.Text != "hello" {
t.Errorf("expected text 'hello', got %+v", c)
}
if c.String() != "hello" {
t.Errorf("String() = %q, want %q", c.String(), "hello")
}
}
func TestContent_UnmarshalArray(t *testing.T) {
var c Content
if err := json.Unmarshal([]byte(`[{"type":"text","text":"hi"}]`), &c); err != nil {
t.Fatal(err)
}
if len(c.Parts) != 1 {
t.Fatalf("got %d parts, want 1", len(c.Parts))
}
tc, ok := c.Parts[0].(*TextChunk)
if !ok {
t.Fatalf("expected *TextChunk, got %T", c.Parts[0])
}
if tc.Text != "hi" {
t.Errorf("got %q, want %q", tc.Text, "hi")
}
}
func TestContent_UnmarshalNull(t *testing.T) {
var c Content
if err := json.Unmarshal([]byte("null"), &c); err != nil {
t.Fatal(err)
}
if !c.IsNull() {
t.Error("expected null content")
}
}
func TestContent_RoundTrip_Text(t *testing.T) {
original := TextContent("round trip")
data, err := json.Marshal(original)
if err != nil {
t.Fatal(err)
}
var decoded Content
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if decoded.String() != "round trip" {
t.Errorf("got %q, want %q", decoded.String(), "round trip")
}
}
func TestContent_RoundTrip_Chunks(t *testing.T) {
original := ChunksContent(
&TextChunk{Text: "hello"},
&ImageURLChunk{ImageURL: ImageURL{URL: "https://example.com/img.png"}},
)
data, err := json.Marshal(original)
if err != nil {
t.Fatal(err)
}
var decoded Content
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Parts) != 2 {
t.Fatalf("got %d parts, want 2", len(decoded.Parts))
}
}
func TestContent_IsNull(t *testing.T) {
var zero Content
if !zero.IsNull() {
t.Error("zero value should be null")
}
if TextContent("x").IsNull() {
t.Error("text content should not be null")
}
if ChunksContent(&TextChunk{Text: "x"}).IsNull() {
t.Error("chunks content should not be null")
}
}