internal/message/ — Content discriminated union, Message, Usage, StopReason, Response. 22 tests. internal/stream/ — Stream pull-based iterator interface, Event types, Accumulator (assembles Response from events). 8 tests. internal/provider/ — Provider interface, Request, ToolDefinition, Registry with factory pattern, ProviderError with HTTP status classification. errors.AsType[E] for Go 1.26. 13 tests. 43 tests total, all passing.
175 lines
4.2 KiB
Go
175 lines
4.2 KiB
Go
package message
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewTextContent(t *testing.T) {
|
|
c := NewTextContent("hello world")
|
|
|
|
if c.Type != ContentText {
|
|
t.Errorf("Type = %v, want %v", c.Type, ContentText)
|
|
}
|
|
if c.Text != "hello world" {
|
|
t.Errorf("Text = %q, want %q", c.Text, "hello world")
|
|
}
|
|
if c.ToolCall != nil {
|
|
t.Error("ToolCall should be nil for text content")
|
|
}
|
|
if c.ToolResult != nil {
|
|
t.Error("ToolResult should be nil for text content")
|
|
}
|
|
if c.Thinking != nil {
|
|
t.Error("Thinking should be nil for text content")
|
|
}
|
|
}
|
|
|
|
func TestNewToolCallContent(t *testing.T) {
|
|
args := json.RawMessage(`{"command":"ls -la"}`)
|
|
tc := ToolCall{
|
|
ID: "tc_001",
|
|
Name: "bash",
|
|
Arguments: args,
|
|
}
|
|
c := NewToolCallContent(tc)
|
|
|
|
if c.Type != ContentToolCall {
|
|
t.Errorf("Type = %v, want %v", c.Type, ContentToolCall)
|
|
}
|
|
if c.ToolCall == nil {
|
|
t.Fatal("ToolCall should not be nil")
|
|
}
|
|
if c.ToolCall.ID != "tc_001" {
|
|
t.Errorf("ToolCall.ID = %q, want %q", c.ToolCall.ID, "tc_001")
|
|
}
|
|
if c.ToolCall.Name != "bash" {
|
|
t.Errorf("ToolCall.Name = %q, want %q", c.ToolCall.Name, "bash")
|
|
}
|
|
if string(c.ToolCall.Arguments) != `{"command":"ls -la"}` {
|
|
t.Errorf("ToolCall.Arguments = %s, want %s", c.ToolCall.Arguments, args)
|
|
}
|
|
if c.Text != "" {
|
|
t.Error("Text should be empty for tool call content")
|
|
}
|
|
}
|
|
|
|
func TestNewToolResultContent(t *testing.T) {
|
|
tr := ToolResult{
|
|
ToolCallID: "tc_001",
|
|
Content: "file1.go\nfile2.go",
|
|
IsError: false,
|
|
}
|
|
c := NewToolResultContent(tr)
|
|
|
|
if c.Type != ContentToolResult {
|
|
t.Errorf("Type = %v, want %v", c.Type, ContentToolResult)
|
|
}
|
|
if c.ToolResult == nil {
|
|
t.Fatal("ToolResult should not be nil")
|
|
}
|
|
if c.ToolResult.ToolCallID != "tc_001" {
|
|
t.Errorf("ToolResult.ToolCallID = %q, want %q", c.ToolResult.ToolCallID, "tc_001")
|
|
}
|
|
if c.ToolResult.IsError {
|
|
t.Error("ToolResult.IsError should be false")
|
|
}
|
|
}
|
|
|
|
func TestNewToolResultContent_Error(t *testing.T) {
|
|
tr := ToolResult{
|
|
ToolCallID: "tc_002",
|
|
Content: "permission denied",
|
|
IsError: true,
|
|
}
|
|
c := NewToolResultContent(tr)
|
|
|
|
if !c.ToolResult.IsError {
|
|
t.Error("ToolResult.IsError should be true")
|
|
}
|
|
if c.ToolResult.Content != "permission denied" {
|
|
t.Errorf("ToolResult.Content = %q, want %q", c.ToolResult.Content, "permission denied")
|
|
}
|
|
}
|
|
|
|
func TestNewThinkingContent(t *testing.T) {
|
|
th := Thinking{
|
|
Text: "Let me think about this...",
|
|
Signature: "sig_abc123",
|
|
}
|
|
c := NewThinkingContent(th)
|
|
|
|
if c.Type != ContentThinking {
|
|
t.Errorf("Type = %v, want %v", c.Type, ContentThinking)
|
|
}
|
|
if c.Thinking == nil {
|
|
t.Fatal("Thinking should not be nil")
|
|
}
|
|
if c.Thinking.Text != "Let me think about this..." {
|
|
t.Errorf("Thinking.Text = %q", c.Thinking.Text)
|
|
}
|
|
if c.Thinking.Signature != "sig_abc123" {
|
|
t.Errorf("Thinking.Signature = %q", c.Thinking.Signature)
|
|
}
|
|
if c.Thinking.Redacted {
|
|
t.Error("Thinking.Redacted should be false")
|
|
}
|
|
}
|
|
|
|
func TestNewRedactedThinkingContent(t *testing.T) {
|
|
th := Thinking{
|
|
Redacted: true,
|
|
}
|
|
c := NewThinkingContent(th)
|
|
|
|
if !c.Thinking.Redacted {
|
|
t.Error("Thinking.Redacted should be true")
|
|
}
|
|
}
|
|
|
|
func TestContentType_String(t *testing.T) {
|
|
tests := []struct {
|
|
ct ContentType
|
|
want string
|
|
}{
|
|
{ContentText, "text"},
|
|
{ContentToolCall, "tool_call"},
|
|
{ContentToolResult, "tool_result"},
|
|
{ContentThinking, "thinking"},
|
|
{ContentType(99), "unknown(99)"},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := tt.ct.String(); got != tt.want {
|
|
t.Errorf("ContentType(%d).String() = %q, want %q", tt.ct, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestToolCall_JSON_RoundTrip(t *testing.T) {
|
|
original := ToolCall{
|
|
ID: "tc_100",
|
|
Name: "fs.read",
|
|
Arguments: json.RawMessage(`{"path":"/tmp/test.go","offset":0}`),
|
|
}
|
|
|
|
data, err := json.Marshal(original)
|
|
if err != nil {
|
|
t.Fatalf("Marshal: %v", err)
|
|
}
|
|
|
|
var decoded ToolCall
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("Unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.ID != original.ID {
|
|
t.Errorf("ID = %q, want %q", decoded.ID, original.ID)
|
|
}
|
|
if decoded.Name != original.Name {
|
|
t.Errorf("Name = %q, want %q", decoded.Name, original.Name)
|
|
}
|
|
if string(decoded.Arguments) != string(original.Arguments) {
|
|
t.Errorf("Arguments = %s, want %s", decoded.Arguments, original.Arguments)
|
|
}
|
|
}
|