test: use cmp.Diff for assertions and refactor migrate_sessions tests

Replace assert.Equal/require.Equal with assert.Empty(cmp.Diff(...)) across
73 test files. cmp.Diff produces clearer failure output by showing the
exact difference between expected and actual values.

Refactor pkg/memory/migrate_sessions_test.go: consolidate TestMigrateFileSessions_Basic
and related cases into a single table-driven TestMigrateFileSessions with
structured test cases (setup, want, extra assertions).

Packages affected: eval/go_evals, internal/fantasy/*, pkg/agent, pkg/itr/*,
pkg/memory/*, pkg/rlm/*, pkg/runtime, pkg/security/*, pkg/session, pkg/skills,
pkg/sync, pkg/tools/*, pkg/worker.
This commit is contained in:
ZanzyTHEbar 2026-02-21 22:27:02 +00:00
parent 8d5d3eef69
commit 7a7eab6403
73 changed files with 1135 additions and 994 deletions

View file

@ -9,6 +9,7 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg/config"
"github.com/ZanzyTHEbar/dragonscale/pkg/tools"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -484,11 +485,11 @@ func TestConfig_DefaultValues(t *testing.T) {
cfg := config.DefaultConfig()
assert.True(t, cfg.Agents.Defaults.RestrictToSandbox, "restrict to sandbox should be on by default")
assert.Equal(t, 20, cfg.Agents.Defaults.MaxToolIterations, "max tool iterations default")
assert.Equal(t, 0.7, cfg.Agents.Defaults.Temperature, "temperature default")
assert.Equal(t, 8192, cfg.Agents.Defaults.MaxTokens, "max tokens default")
assert.Equal(t, 768, cfg.Memory.EmbeddingDims, "embedding dims default")
assert.Equal(t, 4000, cfg.Memory.OffloadThresholdTokens, "offload threshold default")
assert.Empty(t, cmp.Diff(20, cfg.Agents.Defaults.MaxToolIterations), "max tool iterations default")
assert.Empty(t, cmp.Diff(0.7, cfg.Agents.Defaults.Temperature), "temperature default")
assert.Empty(t, cmp.Diff(8192, cfg.Agents.Defaults.MaxTokens), "max tokens default")
assert.Empty(t, cmp.Diff(768, cfg.Memory.EmbeddingDims), "embedding dims default")
assert.Empty(t, cmp.Diff(4000, cfg.Memory.OffloadThresholdTokens), "offload threshold default")
}
func TestConfig_LoadEvalConfigs(t *testing.T) {
@ -510,7 +511,7 @@ func TestConfig_LoadEvalConfigs(t *testing.T) {
require.NoError(t, err, "config should load without error")
assert.True(t, cfg.Agents.Defaults.RestrictToSandbox, "restrict_to_sandbox should always be true for eval")
assert.Equal(t, tc.expectIterations, cfg.Agents.Defaults.MaxToolIterations, "max_tool_iterations")
assert.Empty(t, cmp.Diff(tc.expectIterations, cfg.Agents.Defaults.MaxToolIterations), "max_tool_iterations")
})
}
}
@ -519,7 +520,7 @@ func TestConfig_MissingFileReturnsDefaults(t *testing.T) {
t.Parallel()
cfg, err := config.LoadConfig("/nonexistent/path/config.json")
require.NoError(t, err, "missing config should return defaults, not error")
assert.Equal(t, 768, cfg.Memory.EmbeddingDims, "should have default embedding dims")
assert.Empty(t, cmp.Diff(768, cfg.Memory.EmbeddingDims), "should have default embedding dims")
}
// ---------------------------------------------------------------------------
@ -573,7 +574,7 @@ func TestToolSchema_JSONRoundtrip(t *testing.T) {
require.NoError(t, err, "schema JSON should parse back")
fn := parsed["function"].(map[string]interface{})
assert.Equal(t, tool.Name(), fn["name"])
assert.Empty(t, cmp.Diff(tool.Name(), fn["name"]))
})
}
}

View file

@ -6,7 +6,9 @@ import (
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -316,30 +318,30 @@ func TestStreamingAgentWithTools(t *testing.T) {
Prompt: "Echo 'test'",
OnToolInputStart: func(id, toolName string) error {
toolInputStartCalled = true
require.Equal(t, "tool-1", id)
require.Equal(t, "echo", toolName)
assert.Empty(t, cmp.Diff("tool-1", id))
assert.Empty(t, cmp.Diff("echo", toolName))
return nil
},
OnToolInputDelta: func(id, delta string) error {
toolInputDeltaCalled = true
require.Equal(t, "tool-1", id)
assert.Empty(t, cmp.Diff("tool-1", id))
require.Contains(t, []string{`{"message"`, `: "test"}`}, delta)
return nil
},
OnToolInputEnd: func(id string) error {
toolInputEndCalled = true
require.Equal(t, "tool-1", id)
assert.Empty(t, cmp.Diff("tool-1", id))
return nil
},
OnToolCall: func(toolCall ToolCallContent) error {
toolCallCalled = true
require.Equal(t, "echo", toolCall.ToolName)
require.Equal(t, `{"message": "test"}`, toolCall.Input)
assert.Empty(t, cmp.Diff("echo", toolCall.ToolName))
assert.Empty(t, cmp.Diff(`{"message": "test"}`, toolCall.Input))
return nil
},
OnToolResult: func(result ToolResultContent) error {
toolResultCalled = true
require.Equal(t, "echo", result.ToolName)
assert.Empty(t, cmp.Diff("echo", result.ToolName))
return nil
},
}
@ -354,17 +356,17 @@ func TestStreamingAgentWithTools(t *testing.T) {
require.True(t, toolInputEndCalled, "OnToolInputEnd should have been called")
require.True(t, toolCallCalled, "OnToolCall should have been called")
require.True(t, toolResultCalled, "OnToolResult should have been called")
require.Equal(t, 2, len(result.Steps)) // Two steps: tool call + final response
assert.Empty(t, cmp.Diff(2, len(result.Steps))) // Two steps: tool call + final response
// Check that tool was executed in first step
firstStep := result.Steps[0]
toolCalls := firstStep.Content.ToolCalls()
require.Equal(t, 1, len(toolCalls))
require.Equal(t, "echo", toolCalls[0].ToolName)
assert.Empty(t, cmp.Diff(1, len(toolCalls)))
assert.Empty(t, cmp.Diff("echo", toolCalls[0].ToolName))
toolResults := firstStep.Content.ToolResults()
require.Equal(t, 1, len(toolResults))
require.Equal(t, "echo", toolResults[0].ToolName)
assert.Empty(t, cmp.Diff(1, len(toolResults)))
assert.Empty(t, cmp.Diff("echo", toolResults[0].ToolName))
}
// TestStreamingAgentTextDeltas tests text streaming (mirrors TS textStream tests)
@ -417,11 +419,12 @@ func TestStreamingAgentTextDeltas(t *testing.T) {
result, err := agent.Stream(ctx, streamCall)
require.NoError(t, err)
assert.
// Verify text deltas match expected pattern
require.Equal(t, []string{"Hello", ", ", "world!"}, textDeltas)
require.Equal(t, "Hello, world!", result.Response.Content.Text())
require.Equal(t, int64(13), result.TotalUsage.TotalTokens)
// Verify text deltas match expected pattern
Empty(t, cmp.Diff([]string{"Hello", ", ", "world!"}, textDeltas))
assert.Empty(t, cmp.Diff("Hello, world!", result.Response.Content.Text()))
assert.Empty(t, cmp.Diff(int64(13), result.TotalUsage.TotalTokens))
}
// TestStreamingAgentReasoning tests reasoning content (mirrors TS reasoning tests)
@ -481,12 +484,13 @@ func TestStreamingAgentReasoning(t *testing.T) {
result, err := agent.Stream(ctx, streamCall)
require.NoError(t, err)
assert.
// Verify reasoning and text are separate
require.Equal(t, []string{"I will open the conversation", " with witty banter."}, reasoningDeltas)
require.Equal(t, []string{"Hi there!"}, textDeltas)
require.Equal(t, "Hi there!", result.Response.Content.Text())
require.Equal(t, "I will open the conversation with witty banter.", result.Response.Content.ReasoningText())
// Verify reasoning and text are separate
Empty(t, cmp.Diff([]string{"I will open the conversation", " with witty banter."}, reasoningDeltas))
assert.Empty(t, cmp.Diff([]string{"Hi there!"}, textDeltas))
assert.Empty(t, cmp.Diff("Hi there!", result.Response.Content.Text()))
assert.Empty(t, cmp.Diff("I will open the conversation with witty banter.", result.Response.Content.ReasoningText()))
}
// TestStreamingAgentError tests error handling (mirrors TS error tests)
@ -583,16 +587,17 @@ func TestStreamingAgentSources(t *testing.T) {
result, err := agent.Stream(ctx, streamCall)
require.NoError(t, err)
assert.
// Verify sources were captured
require.Equal(t, 2, len(sources))
require.Equal(t, SourceTypeURL, sources[0].SourceType)
require.Equal(t, "https://example.com", sources[0].URL)
require.Equal(t, "Example", sources[0].Title)
require.Equal(t, SourceTypeDocument, sources[1].SourceType)
require.Equal(t, "Document Example", sources[1].Title)
// Verify sources were captured
Empty(t, cmp.Diff(2, len(sources)))
assert.Empty(t, cmp.Diff(SourceTypeURL, sources[0].SourceType))
assert.Empty(t, cmp.Diff("https://example.com", sources[0].URL))
assert.Empty(t, cmp.Diff("Example", sources[0].Title))
assert.Empty(t, cmp.Diff(SourceTypeDocument, sources[1].SourceType))
assert.Empty(t, cmp.Diff("Document Example", sources[1].Title))
// Verify sources are in final result
resultSources := result.Response.Content.Sources()
require.Equal(t, 2, len(resultSources))
assert.Empty(t, cmp.Diff(2, len(resultSources)))
}

View file

@ -7,7 +7,9 @@ import (
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -104,7 +106,7 @@ func TestAgent_Generate_ResultContent_AllTypes(t *testing.T) {
"tool1",
"Test tool",
func(ctx context.Context, input TestInput, _ ToolCall) (ToolResponse, error) {
require.Equal(t, "value", input.Value)
assert.Empty(t, cmp.Diff("value", input.Value))
return ToolResponse{Content: "result1", IsError: false}, nil
},
)
@ -159,33 +161,33 @@ func TestAgent_Generate_ResultContent_AllTypes(t *testing.T) {
// Verify each content type in order
textContent, ok := AsContentType[TextContent](result.Response.Content[0])
require.True(t, ok)
require.Equal(t, "Hello, world!", textContent.Text)
assert.Empty(t, cmp.Diff("Hello, world!", textContent.Text))
sourceContent, ok := AsContentType[SourceContent](result.Response.Content[1])
require.True(t, ok)
require.Equal(t, "123", sourceContent.ID)
assert.Empty(t, cmp.Diff("123", sourceContent.ID))
fileContent, ok := AsContentType[FileContent](result.Response.Content[2])
require.True(t, ok)
require.Equal(t, []byte{1, 2, 3}, fileContent.Data)
assert.Empty(t, cmp.Diff([]byte{1, 2, 3}, fileContent.Data))
reasoningContent, ok := AsContentType[ReasoningContent](result.Response.Content[3])
require.True(t, ok)
require.Equal(t, "I will open the conversation with witty banter.", reasoningContent.Text)
assert.Empty(t, cmp.Diff("I will open the conversation with witty banter.", reasoningContent.Text))
toolCallContent, ok := AsContentType[ToolCallContent](result.Response.Content[4])
require.True(t, ok)
require.Equal(t, "call-1", toolCallContent.ToolCallID)
assert.Empty(t, cmp.Diff("call-1", toolCallContent.ToolCallID))
moreTextContent, ok := AsContentType[TextContent](result.Response.Content[5])
require.True(t, ok)
require.Equal(t, "More text", moreTextContent.Text)
assert.Empty(t, cmp.Diff("More text", moreTextContent.Text))
// Tool result should be appended
toolResultContent, ok := AsContentType[ToolResultContent](result.Response.Content[6])
require.True(t, ok)
require.Equal(t, "call-1", toolResultContent.ToolCallID)
require.Equal(t, "tool1", toolResultContent.ToolName)
assert.Empty(t, cmp.Diff("call-1", toolResultContent.ToolCallID))
assert.Empty(t, cmp.Diff("tool1", toolResultContent.ToolName))
}
// Test result.text extraction
@ -218,7 +220,7 @@ func TestAgent_Generate_ResultText(t *testing.T) {
// Test text extraction from content
text := result.Response.Content.Text()
require.Equal(t, "Hello, world!", text)
assert.Empty(t, cmp.Diff("Hello, world!", text))
}
// Test result.toolCalls extraction (matches TS test exactly)
@ -254,11 +256,11 @@ func TestAgent_Generate_ResultToolCalls(t *testing.T) {
generateFunc: func(ctx context.Context, call Call) (*Response, error) {
// Verify tools are passed correctly
require.Len(t, call.Tools, 2)
require.Equal(t, ToolChoiceAuto, *call.ToolChoice) // Should be auto, not required
assert.Empty(t, cmp.Diff(ToolChoiceAuto, *call.ToolChoice)) // Should be auto, not required
// Verify prompt structure
require.Len(t, call.Prompt, 1)
require.Equal(t, MessageRoleUser, call.Prompt[0].Role)
assert.Empty(t, cmp.Diff(MessageRoleUser, call.Prompt[0].Role))
return &Response{
Content: []Content{
@ -296,14 +298,14 @@ func TestAgent_Generate_ResultToolCalls(t *testing.T) {
}
require.Len(t, toolCalls, 1)
require.Equal(t, "call-1", toolCalls[0].ToolCallID)
require.Equal(t, "tool1", toolCalls[0].ToolName)
assert.Empty(t, cmp.Diff("call-1", toolCalls[0].ToolCallID))
assert.Empty(t, cmp.Diff("tool1", toolCalls[0].ToolName))
// Parse and verify input
var input map[string]any
err = jsonv2.Unmarshal([]byte(toolCalls[0].Input), &input)
require.NoError(t, err)
require.Equal(t, "value", input["value"])
assert.Empty(t, cmp.Diff("value", input["value"]))
}
// Test result.toolResults extraction (matches TS test exactly)
@ -319,7 +321,7 @@ func TestAgent_Generate_ResultToolResults(t *testing.T) {
"tool1",
"Test tool",
func(ctx context.Context, input TestInput, _ ToolCall) (ToolResponse, error) {
require.Equal(t, "value", input.Value)
assert.Empty(t, cmp.Diff("value", input.Value))
return ToolResponse{Content: "result1", IsError: false}, nil
},
)
@ -328,11 +330,11 @@ func TestAgent_Generate_ResultToolResults(t *testing.T) {
generateFunc: func(ctx context.Context, call Call) (*Response, error) {
// Verify tools and tool choice
require.Len(t, call.Tools, 1)
require.Equal(t, ToolChoiceAuto, *call.ToolChoice)
assert.Empty(t, cmp.Diff(ToolChoiceAuto, *call.ToolChoice))
// Verify prompt
require.Len(t, call.Prompt, 1)
require.Equal(t, MessageRoleUser, call.Prompt[0].Role)
assert.Empty(t, cmp.Diff(MessageRoleUser, call.Prompt[0].Role))
return &Response{
Content: []Content{
@ -370,13 +372,13 @@ func TestAgent_Generate_ResultToolResults(t *testing.T) {
}
require.Len(t, toolResults, 1)
require.Equal(t, "call-1", toolResults[0].ToolCallID)
require.Equal(t, "tool1", toolResults[0].ToolName)
assert.Empty(t, cmp.Diff("call-1", toolResults[0].ToolCallID))
assert.Empty(t, cmp.Diff("tool1", toolResults[0].ToolName))
// Verify result content
textResult, ok := toolResults[0].Result.(ToolResultOutputContentText)
require.True(t, ok)
require.Equal(t, "result1", textResult.Text)
assert.Empty(t, cmp.Diff("result1", textResult.Text))
}
// Test multi-step scenario (matches TS "2 steps: initial, tool-result" test)
@ -392,7 +394,7 @@ func TestAgent_Generate_MultipleSteps(t *testing.T) {
"tool1",
"Test tool",
func(ctx context.Context, input TestInput, _ ToolCall) (ToolResponse, error) {
require.Equal(t, "value", input.Value)
assert.Empty(t, cmp.Diff("value", input.Value))
return ToolResponse{Content: "result1", IsError: false}, nil
},
)
@ -447,17 +449,20 @@ func TestAgent_Generate_MultipleSteps(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, result)
require.Len(t, result.Steps, 2)
assert.
// Check total usage sums both steps
require.Equal(t, int64(13), result.TotalUsage.InputTokens) // 10 + 3
require.Equal(t, int64(15), result.TotalUsage.OutputTokens) // 5 + 10
require.Equal(t, int64(28), result.TotalUsage.TotalTokens) // 15 + 13
// Check total usage sums both steps
Empty(t, cmp.Diff(int64(13), result.TotalUsage.InputTokens))
assert. // 10 + 3
Empty(t, cmp.Diff(int64(15), result.TotalUsage.OutputTokens))
assert. // 5 + 10
Empty(t, cmp.Diff(int64(28), result.TotalUsage.TotalTokens)) // 15 + 13
// Final response should be from last step
require.Len(t, result.Response.Content, 1)
textContent, ok := AsContentType[TextContent](result.Response.Content[0])
require.True(t, ok)
require.Equal(t, "Hello, world!", textContent.Text)
assert.Empty(t, cmp.Diff("Hello, world!", textContent.Text))
// result.toolCalls should be empty (from last step)
var toolCalls []ToolCallContent
@ -511,17 +516,19 @@ func TestAgent_Generate_BasicText(t *testing.T) {
require.Len(t, result.Response.Content, 1)
textContent, ok := AsContentType[TextContent](result.Response.Content[0])
require.True(t, ok)
require.Equal(t, "Hello, world!", textContent.Text)
assert.Empty(t, cmp.Diff("Hello, world!", textContent.Text))
assert.
// Check usage
require.Equal(t, int64(3), result.Response.Usage.InputTokens)
require.Equal(t, int64(10), result.Response.Usage.OutputTokens)
require.Equal(t, int64(13), result.Response.Usage.TotalTokens)
// Check usage
Empty(t, cmp.Diff(int64(3), result.Response.Usage.InputTokens))
assert.Empty(t, cmp.Diff(int64(10), result.Response.Usage.OutputTokens))
assert.Empty(t, cmp.Diff(int64(13), result.Response.Usage.TotalTokens))
assert.
// Check total usage
require.Equal(t, int64(3), result.TotalUsage.InputTokens)
require.Equal(t, int64(10), result.TotalUsage.OutputTokens)
require.Equal(t, int64(13), result.TotalUsage.TotalTokens)
// Check total usage
Empty(t, cmp.Diff(int64(3), result.TotalUsage.InputTokens))
assert.Empty(t, cmp.Diff(int64(10), result.TotalUsage.OutputTokens))
assert.Empty(t, cmp.Diff(int64(13), result.TotalUsage.TotalTokens))
}
// Test empty prompt error
@ -547,13 +554,14 @@ func TestAgent_Generate_WithSystemPrompt(t *testing.T) {
model := &mockLanguageModel{
generateFunc: func(ctx context.Context, call Call) (*Response, error) {
// Verify system message is included
require.Len(t, call.Prompt, 2) // system + user
require.Equal(t, MessageRoleSystem, call.Prompt[0].Role)
require.Equal(t, MessageRoleUser, call.Prompt[1].Role)
require.Len(t, call.Prompt, 2)
assert. // system + user
Empty(t, cmp.Diff(MessageRoleSystem, call.Prompt[0].Role))
assert.Empty(t, cmp.Diff(MessageRoleUser, call.Prompt[1].Role))
systemPart, ok := call.Prompt[0].Content[0].(TextPart)
require.True(t, ok)
require.Equal(t, "You are a helpful assistant", systemPart.Text)
assert.Empty(t, cmp.Diff("You are a helpful assistant", systemPart.Text))
return &Response{
Content: []Content{
@ -606,7 +614,7 @@ func TestAgent_Generate_OptionsActiveTools(t *testing.T) {
require.Len(t, call.Tools, 1)
functionTool, ok := call.Tools[0].(FunctionTool)
require.True(t, ok)
require.Equal(t, "tool1", functionTool.Name)
assert.Empty(t, cmp.Diff("tool1", functionTool.Name))
return &Response{
Content: []Content{
@ -644,46 +652,48 @@ func TestResponseContent_Getters(t *testing.T) {
ToolCallContent{ToolCallID: "call1", ToolName: "test_tool", Input: `{"arg": "value"}`},
ToolResultContent{ToolCallID: "call1", ToolName: "test_tool", Result: ToolResultOutputContentText{Text: "result"}},
}
assert.
// Test Text()
require.Equal(t, "Hello world", content.Text())
// Test Text()
Empty(t, cmp.Diff("Hello world", content.Text()))
// Test Reasoning()
reasoning := content.Reasoning()
require.Len(t, reasoning, 1)
require.Equal(t, "Let me think...", reasoning[0].Text)
assert.Empty(t, cmp.Diff("Let me think...", reasoning[0].Text))
assert.
// Test ReasoningText()
require.Equal(t, "Let me think...", content.ReasoningText())
// Test ReasoningText()
Empty(t, cmp.Diff("Let me think...", content.ReasoningText()))
// Test Files()
files := content.Files()
require.Len(t, files, 1)
require.Equal(t, "text/plain", files[0].MediaType)
require.Equal(t, []byte("file data"), files[0].Data)
assert.Empty(t, cmp.Diff("text/plain", files[0].MediaType))
assert.Empty(t, cmp.Diff([]byte("file data"), files[0].Data))
// Test Sources()
sources := content.Sources()
require.Len(t, sources, 1)
require.Equal(t, SourceTypeURL, sources[0].SourceType)
require.Equal(t, "https://example.com", sources[0].URL)
require.Equal(t, "Example", sources[0].Title)
assert.Empty(t, cmp.Diff(SourceTypeURL, sources[0].SourceType))
assert.Empty(t, cmp.Diff("https://example.com", sources[0].URL))
assert.Empty(t, cmp.Diff("Example", sources[0].Title))
// Test ToolCalls()
toolCalls := content.ToolCalls()
require.Len(t, toolCalls, 1)
require.Equal(t, "call1", toolCalls[0].ToolCallID)
require.Equal(t, "test_tool", toolCalls[0].ToolName)
require.Equal(t, `{"arg": "value"}`, toolCalls[0].Input)
assert.Empty(t, cmp.Diff("call1", toolCalls[0].ToolCallID))
assert.Empty(t, cmp.Diff("test_tool", toolCalls[0].ToolName))
assert.Empty(t, cmp.Diff(`{"arg": "value"}`, toolCalls[0].Input))
// Test ToolResults()
toolResults := content.ToolResults()
require.Len(t, toolResults, 1)
require.Equal(t, "call1", toolResults[0].ToolCallID)
require.Equal(t, "test_tool", toolResults[0].ToolName)
assert.Empty(t, cmp.Diff("call1", toolResults[0].ToolCallID))
assert.Empty(t, cmp.Diff("test_tool", toolResults[0].ToolName))
result, ok := AsToolResultOutputType[ToolResultOutputContentText](toolResults[0].Result)
require.True(t, ok)
require.Equal(t, "result", result.Text)
assert.Empty(t, cmp.Diff("result", result.Text))
}
func TestResponseContent_Getters_Empty(t *testing.T) {
@ -691,9 +701,8 @@ func TestResponseContent_Getters_Empty(t *testing.T) {
// Test with empty content
content := ResponseContent{}
require.Equal(t, "", content.Text())
require.Equal(t, "", content.ReasoningText())
assert.Empty(t, cmp.Diff("", content.Text()))
assert.Empty(t, cmp.Diff("", content.ReasoningText()))
require.Empty(t, content.Reasoning())
require.Empty(t, content.Files())
require.Empty(t, content.Sources())
@ -715,17 +724,18 @@ func TestResponseContent_Getters_MultipleItems(t *testing.T) {
// Test multiple reasoning
reasoning := content.Reasoning()
require.Len(t, reasoning, 2)
require.Equal(t, "First thought", reasoning[0].Text)
require.Equal(t, "Second thought", reasoning[1].Text)
assert.Empty(t, cmp.Diff("First thought", reasoning[0].Text))
assert.Empty(t, cmp.Diff("Second thought", reasoning[1].Text))
assert.
// Test concatenated reasoning text
require.Equal(t, "First thoughtSecond thought", content.ReasoningText())
// Test concatenated reasoning text
Empty(t, cmp.Diff("First thoughtSecond thought", content.ReasoningText()))
// Test multiple files
files := content.Files()
require.Len(t, files, 2)
require.Equal(t, "text/plain", files[0].MediaType)
require.Equal(t, "image/png", files[1].MediaType)
assert.Empty(t, cmp.Diff("text/plain", files[0].MediaType))
assert.Empty(t, cmp.Diff("image/png", files[1].MediaType))
}
func TestStopConditions(t *testing.T) {
@ -910,8 +920,9 @@ func TestStopConditions_Integration(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, result)
// Should stop on first condition met (finish reason stop)
require.Equal(t, FinishReasonStop, result.Response.FinishReason)
assert.
// Should stop on first condition met (finish reason stop)
Empty(t, cmp.Diff(FinishReasonStop, result.Response.FinishReason))
})
}
@ -959,7 +970,7 @@ func TestPrepareStep(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, "Modified system prompt for step 0", capturedSystemPrompt)
assert.Empty(t, cmp.Diff("Modified system prompt for step 0", capturedSystemPrompt))
})
t.Run("Tool choice modification", func(t *testing.T) {
@ -997,7 +1008,7 @@ func TestPrepareStep(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, result)
require.NotNil(t, capturedToolChoice)
require.Equal(t, ToolChoiceNone, *capturedToolChoice)
assert.Empty(t, cmp.Diff(ToolChoiceNone, *capturedToolChoice))
})
t.Run("Active tools modification", func(t *testing.T) {
@ -1042,7 +1053,7 @@ func TestPrepareStep(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, result)
require.Len(t, capturedToolNames, 1)
require.Equal(t, "tool2", capturedToolNames[0])
assert.Empty(t, cmp.Diff("tool2", capturedToolNames[0]))
})
t.Run("No tools when DisableAllTools is true", func(t *testing.T) {
@ -1080,7 +1091,7 @@ func TestPrepareStep(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, 0, capturedToolCount) // No tools should be passed
assert.Empty(t, cmp.Diff(0, capturedToolCount)) // No tools should be passed
})
t.Run("All fields modified together", func(t *testing.T) {
@ -1140,11 +1151,11 @@ func TestPrepareStep(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, "Step-specific system", capturedSystemPrompt)
assert.Empty(t, cmp.Diff("Step-specific system", capturedSystemPrompt))
require.NotNil(t, capturedToolChoice)
require.Equal(t, SpecificToolChoice("tool1"), *capturedToolChoice)
assert.Empty(t, cmp.Diff(SpecificToolChoice("tool1"), *capturedToolChoice))
require.Len(t, capturedToolNames, 1)
require.Equal(t, "tool1", capturedToolNames[0])
assert.Empty(t, cmp.Diff("tool1", capturedToolNames[0]))
})
t.Run("Nil fields use parent values", func(t *testing.T) {
@ -1201,11 +1212,11 @@ func TestPrepareStep(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, "Parent system", capturedSystemPrompt)
assert.Empty(t, cmp.Diff("Parent system", capturedSystemPrompt))
require.NotNil(t, capturedToolChoice)
require.Equal(t, ToolChoiceAuto, *capturedToolChoice) // Default
assert.Empty(t, cmp.Diff(ToolChoiceAuto, *capturedToolChoice)) // Default
require.Len(t, capturedToolNames, 1)
require.Equal(t, "tool1", capturedToolNames[0])
assert.Empty(t, cmp.Diff("tool1", capturedToolNames[0]))
})
t.Run("Empty ActiveTools means all tools", func(t *testing.T) {
@ -1399,8 +1410,9 @@ func TestToolCallRepair(t *testing.T) {
// Check that tool call was repaired and is now valid
toolCalls := result.Steps[0].Content.ToolCalls()
require.Len(t, toolCalls, 1)
require.False(t, toolCalls[0].Invalid) // Should be valid after repair
require.Equal(t, `{"value": "repaired"}`, toolCalls[0].Input) // Should have repaired input
require.False(t, toolCalls[0].Invalid)
assert.Empty( // Should be valid after repair
t, cmp.Diff(`{"value": "repaired"}`, toolCalls[0].Input)) // Should have repaired input
})
t.Run("Invalid tool call with failed repair", func(t *testing.T) {
@ -1596,8 +1608,8 @@ func TestAgent_MediaToolResponses(t *testing.T) {
mediaResult, ok := toolResults[0].Result.(ToolResultOutputContentMedia)
require.True(t, ok, "Expected media result")
require.Equal(t, string(imageData), mediaResult.Data)
require.Equal(t, "image/png", mediaResult.MediaType)
assert.Empty(t, cmp.Diff(string(imageData), mediaResult.Data))
assert.Empty(t, cmp.Diff("image/png", mediaResult.MediaType))
})
t.Run("Media tool response (audio)", func(t *testing.T) {
@ -1648,8 +1660,8 @@ func TestAgent_MediaToolResponses(t *testing.T) {
mediaResult, ok := toolResults[0].Result.(ToolResultOutputContentMedia)
require.True(t, ok, "Expected media result")
require.Equal(t, string(audioData), mediaResult.Data)
require.Equal(t, "audio/wav", mediaResult.MediaType)
assert.Empty(t, cmp.Diff(string(audioData), mediaResult.Data))
assert.Empty(t, cmp.Diff("audio/wav", mediaResult.MediaType))
})
t.Run("Media response with text", func(t *testing.T) {
@ -1702,9 +1714,9 @@ func TestAgent_MediaToolResponses(t *testing.T) {
mediaResult, ok := toolResults[0].Result.(ToolResultOutputContentMedia)
require.True(t, ok, "Expected media result")
require.Equal(t, string(imageData), mediaResult.Data)
require.Equal(t, "image/png", mediaResult.MediaType)
require.Equal(t, "Screenshot captured successfully", mediaResult.Text)
assert.Empty(t, cmp.Diff(string(imageData), mediaResult.Data))
assert.Empty(t, cmp.Diff("image/png", mediaResult.MediaType))
assert.Empty(t, cmp.Diff("Screenshot captured successfully", mediaResult.Text))
})
t.Run("Media response preserves metadata", func(t *testing.T) {
@ -1765,7 +1777,7 @@ func TestAgent_MediaToolResponses(t *testing.T) {
var metadata ImageMetadata
err = jsonv2.Unmarshal([]byte(toolResults[0].ClientMetadata), &metadata)
require.NoError(t, err)
require.Equal(t, 800, metadata.Width)
require.Equal(t, 600, metadata.Height)
assert.Empty(t, cmp.Diff(800, metadata.Width))
assert.Empty(t, cmp.Diff(600, metadata.Height))
})
}

View file

@ -5,6 +5,8 @@ import (
"testing"
"charm.land/fantasy"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -41,7 +43,7 @@ func TestToPrompt_DropsEmptyMessages(t *testing.T) {
require.Empty(t, systemBlocks)
require.Len(t, messages, 1, "should only have user message, assistant message should be dropped")
require.Len(t, warnings, 1)
require.Equal(t, fantasy.CallWarningTypeOther, warnings[0].Type)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeOther, warnings[0].Type))
require.Contains(t, warnings[0].Message, "dropping empty assistant message")
require.Contains(t, warnings[0].Message, "neither user-facing content nor tool calls")
})
@ -76,9 +78,9 @@ func TestToPrompt_DropsEmptyMessages(t *testing.T) {
require.Empty(t, systemBlocks)
require.Len(t, messages, 1, "should only have user message, assistant message should be dropped")
require.Len(t, warnings, 2)
require.Equal(t, fantasy.CallWarningTypeOther, warnings[0].Type)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeOther, warnings[0].Type))
require.Contains(t, warnings[0].Message, "sending reasoning content is disabled")
require.Equal(t, fantasy.CallWarningTypeOther, warnings[1].Type)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeOther, warnings[1].Type))
require.Contains(t, warnings[1].Message, "dropping empty assistant message")
})
@ -103,7 +105,7 @@ func TestToPrompt_DropsEmptyMessages(t *testing.T) {
require.Empty(t, systemBlocks)
require.Len(t, messages, 1, "should only have user message")
require.Len(t, warnings, 1)
require.Equal(t, fantasy.CallWarningTypeOther, warnings[0].Type)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeOther, warnings[0].Type))
require.Contains(t, warnings[0].Message, "dropping empty assistant message")
})
@ -188,7 +190,7 @@ func TestToPrompt_DropsEmptyMessages(t *testing.T) {
require.Empty(t, systemBlocks)
require.Len(t, messages, 1, "should only have user message")
require.Len(t, warnings, 1)
require.Equal(t, fantasy.CallWarningTypeOther, warnings[0].Type)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeOther, warnings[0].Type))
require.Contains(t, warnings[0].Message, "dropping empty assistant message")
})
@ -267,7 +269,7 @@ func TestToPrompt_DropsEmptyMessages(t *testing.T) {
require.Empty(t, systemBlocks)
require.Empty(t, messages)
require.Len(t, warnings, 1)
require.Equal(t, fantasy.CallWarningTypeOther, warnings[0].Type)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeOther, warnings[0].Type))
require.Contains(t, warnings[0].Message, "dropping empty user message")
require.Contains(t, warnings[0].Message, "neither user-facing content nor tool results")
})

View file

@ -3,6 +3,7 @@ package azure
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
@ -93,7 +94,7 @@ func TestParseAzureURL(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := parseAzureURL(tt.input)
assert.Equal(t, tt.expected, result)
assert.Empty(t, cmp.Diff(tt.expected, result))
})
}
}

View file

@ -10,7 +10,9 @@ import (
"charm.land/fantasy"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/openai/openai-go/v2/packages/param"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -36,7 +38,7 @@ func TestToOpenAiPrompt_SystemMessages(t *testing.T) {
systemMsg := messages[0].OfSystem
require.NotNil(t, systemMsg)
require.Equal(t, "You are a helpful assistant.", systemMsg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("You are a helpful assistant.", systemMsg.Content.OfString.Value))
})
t.Run("should handle empty system messages", func(t *testing.T) {
@ -76,7 +78,7 @@ func TestToOpenAiPrompt_SystemMessages(t *testing.T) {
systemMsg := messages[0].OfSystem
require.NotNil(t, systemMsg)
require.Equal(t, "You are a helpful assistant.\nBe concise.", systemMsg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("You are a helpful assistant.\nBe concise.", systemMsg.Content.OfString.Value))
})
}
@ -102,7 +104,7 @@ func TestToOpenAiPrompt_UserMessages(t *testing.T) {
userMsg := messages[0].OfUser
require.NotNil(t, userMsg)
require.Equal(t, "Hello", userMsg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("Hello", userMsg.Content.OfString.Value))
})
t.Run("should convert messages with image parts", func(t *testing.T) {
@ -136,13 +138,13 @@ func TestToOpenAiPrompt_UserMessages(t *testing.T) {
// Check text part
textPart := content[0].OfText
require.NotNil(t, textPart)
require.Equal(t, "Hello", textPart.Text)
assert.Empty(t, cmp.Diff("Hello", textPart.Text))
// Check image part
imagePart := content[1].OfImageURL
require.NotNil(t, imagePart)
expectedURL := "data:image/png;base64," + base64.StdEncoding.EncodeToString(imageData)
require.Equal(t, expectedURL, imagePart.ImageURL.URL)
assert.Empty(t, cmp.Diff(expectedURL, imagePart.ImageURL.URL))
})
t.Run("should add image detail when specified through provider options", func(t *testing.T) {
@ -177,7 +179,7 @@ func TestToOpenAiPrompt_UserMessages(t *testing.T) {
imagePart := content[0].OfImageURL
require.NotNil(t, imagePart)
require.Equal(t, "low", imagePart.ImageURL.Detail)
assert.Empty(t, cmp.Diff("low", imagePart.ImageURL.Detail))
})
}
@ -236,8 +238,8 @@ func TestToOpenAiPrompt_FileParts(t *testing.T) {
audioPart := content[0].OfInputAudio
require.NotNil(t, audioPart)
require.Equal(t, base64.StdEncoding.EncodeToString(audioData), audioPart.InputAudio.Data)
require.Equal(t, "wav", audioPart.InputAudio.Format)
assert.Empty(t, cmp.Diff(base64.StdEncoding.EncodeToString(audioData), audioPart.InputAudio.Data))
assert.Empty(t, cmp.Diff("wav", audioPart.InputAudio.Format))
})
t.Run("should add audio content for audio/mpeg file parts", func(t *testing.T) {
@ -265,7 +267,7 @@ func TestToOpenAiPrompt_FileParts(t *testing.T) {
content := userMsg.Content.OfArrayOfContentParts
audioPart := content[0].OfInputAudio
require.NotNil(t, audioPart)
require.Equal(t, "mp3", audioPart.InputAudio.Format)
assert.Empty(t, cmp.Diff("mp3", audioPart.InputAudio.Format))
})
t.Run("should add audio content for audio/mp3 file parts", func(t *testing.T) {
@ -293,7 +295,7 @@ func TestToOpenAiPrompt_FileParts(t *testing.T) {
content := userMsg.Content.OfArrayOfContentParts
audioPart := content[0].OfInputAudio
require.NotNil(t, audioPart)
require.Equal(t, "mp3", audioPart.InputAudio.Format)
assert.Empty(t, cmp.Diff("mp3", audioPart.InputAudio.Format))
})
t.Run("should convert messages with PDF file parts", func(t *testing.T) {
@ -324,10 +326,10 @@ func TestToOpenAiPrompt_FileParts(t *testing.T) {
filePart := content[0].OfFile
require.NotNil(t, filePart)
require.Equal(t, "document.pdf", filePart.File.Filename.Value)
assert.Empty(t, cmp.Diff("document.pdf", filePart.File.Filename.Value))
expectedData := "data:application/pdf;base64," + base64.StdEncoding.EncodeToString(pdfData)
require.Equal(t, expectedData, filePart.File.FileData.Value)
assert.Empty(t, cmp.Diff(expectedData, filePart.File.FileData.Value))
})
t.Run("should convert messages with binary PDF file parts", func(t *testing.T) {
@ -358,7 +360,7 @@ func TestToOpenAiPrompt_FileParts(t *testing.T) {
require.NotNil(t, filePart)
expectedData := "data:application/pdf;base64," + base64.StdEncoding.EncodeToString(pdfData)
require.Equal(t, expectedData, filePart.File.FileData.Value)
assert.Empty(t, cmp.Diff(expectedData, filePart.File.FileData.Value))
})
t.Run("should convert messages with PDF file parts using file_id", func(t *testing.T) {
@ -385,7 +387,7 @@ func TestToOpenAiPrompt_FileParts(t *testing.T) {
content := userMsg.Content.OfArrayOfContentParts
filePart := content[0].OfFile
require.NotNil(t, filePart)
require.Equal(t, "file-pdf-12345", filePart.File.FileID.Value)
assert.Empty(t, cmp.Diff("file-pdf-12345", filePart.File.FileID.Value))
require.True(t, param.IsOmitted(filePart.File.FileData))
require.True(t, param.IsOmitted(filePart.File.Filename))
})
@ -415,7 +417,7 @@ func TestToOpenAiPrompt_FileParts(t *testing.T) {
content := userMsg.Content.OfArrayOfContentParts
filePart := content[0].OfFile
require.NotNil(t, filePart)
require.Equal(t, "part-0.pdf", filePart.File.Filename.Value)
assert.Empty(t, cmp.Diff("part-0.pdf", filePart.File.Filename.Value))
})
}
@ -463,20 +465,20 @@ func TestToOpenAiPrompt_ToolCalls(t *testing.T) {
// Check assistant message with tool call
assistantMsg := messages[0].OfAssistant
require.NotNil(t, assistantMsg)
require.Equal(t, "", assistantMsg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("", assistantMsg.Content.OfString.Value))
require.Len(t, assistantMsg.ToolCalls, 1)
toolCall := assistantMsg.ToolCalls[0].OfFunction
require.NotNil(t, toolCall)
require.Equal(t, "quux", toolCall.ID)
require.Equal(t, "thwomp", toolCall.Function.Name)
require.Equal(t, string(inputJSON), toolCall.Function.Arguments)
assert.Empty(t, cmp.Diff("quux", toolCall.ID))
assert.Empty(t, cmp.Diff("thwomp", toolCall.Function.Name))
assert.Empty(t, cmp.Diff(string(inputJSON), toolCall.Function.Arguments))
// Check tool message
toolMsg := messages[1].OfTool
require.NotNil(t, toolMsg)
require.Equal(t, string(outputJSON), toolMsg.Content.OfString.Value)
require.Equal(t, "quux", toolMsg.ToolCallID)
assert.Empty(t, cmp.Diff(string(outputJSON), toolMsg.Content.OfString.Value))
assert.Empty(t, cmp.Diff("quux", toolMsg.ToolCallID))
})
t.Run("should handle different tool output types", func(t *testing.T) {
@ -510,14 +512,14 @@ func TestToOpenAiPrompt_ToolCalls(t *testing.T) {
// Check first tool message (text)
textToolMsg := messages[0].OfTool
require.NotNil(t, textToolMsg)
require.Equal(t, "Hello world", textToolMsg.Content.OfString.Value)
require.Equal(t, "text-tool", textToolMsg.ToolCallID)
assert.Empty(t, cmp.Diff("Hello world", textToolMsg.Content.OfString.Value))
assert.Empty(t, cmp.Diff("text-tool", textToolMsg.ToolCallID))
// Check second tool message (error)
errorToolMsg := messages[1].OfTool
require.NotNil(t, errorToolMsg)
require.Equal(t, "Something went wrong", errorToolMsg.Content.OfString.Value)
require.Equal(t, "error-tool", errorToolMsg.ToolCallID)
assert.Empty(t, cmp.Diff("Something went wrong", errorToolMsg.Content.OfString.Value))
assert.Empty(t, cmp.Diff("error-tool", errorToolMsg.ToolCallID))
})
}
@ -543,7 +545,7 @@ func TestToOpenAiPrompt_AssistantMessages(t *testing.T) {
assistantMsg := messages[0].OfAssistant
require.NotNil(t, assistantMsg)
require.Equal(t, "Hello, how can I help you?", assistantMsg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("Hello, how can I help you?", assistantMsg.Content.OfString.Value))
})
t.Run("should handle assistant messages with mixed content", func(t *testing.T) {
@ -573,13 +575,13 @@ func TestToOpenAiPrompt_AssistantMessages(t *testing.T) {
assistantMsg := messages[0].OfAssistant
require.NotNil(t, assistantMsg)
require.Equal(t, "Let me search for that.", assistantMsg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("Let me search for that.", assistantMsg.Content.OfString.Value))
require.Len(t, assistantMsg.ToolCalls, 1)
toolCall := assistantMsg.ToolCalls[0].OfFunction
require.Equal(t, "call-123", toolCall.ID)
require.Equal(t, "search", toolCall.Function.Name)
require.Equal(t, string(inputJSON), toolCall.Function.Arguments)
assert.Empty(t, cmp.Diff("call-123", toolCall.ID))
assert.Empty(t, cmp.Diff("search", toolCall.Function.Name))
assert.Empty(t, cmp.Diff(string(inputJSON), toolCall.Function.Arguments))
})
}
@ -825,7 +827,7 @@ func TestDoGenerate(t *testing.T) {
textContent, ok := result.Content[0].(fantasy.TextContent)
require.True(t, ok)
require.Equal(t, "Hello, World!", textContent.Text)
assert.Empty(t, cmp.Diff("Hello, World!", textContent.Text))
})
t.Run("should extract usage", func(t *testing.T) {
@ -854,9 +856,9 @@ func TestDoGenerate(t *testing.T) {
})
require.NoError(t, err)
require.Equal(t, int64(20), result.Usage.InputTokens)
require.Equal(t, int64(5), result.Usage.OutputTokens)
require.Equal(t, int64(25), result.Usage.TotalTokens)
assert.Empty(t, cmp.Diff(int64(20), result.Usage.InputTokens))
assert.Empty(t, cmp.Diff(int64(5), result.Usage.OutputTokens))
assert.Empty(t, cmp.Diff(int64(25), result.Usage.TotalTokens))
})
t.Run("should send request body", func(t *testing.T) {
@ -882,17 +884,17 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "POST", call.method)
require.Equal(t, "/chat/completions", call.path)
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
assert.Empty(t, cmp.Diff("POST", call.method))
assert.Empty(t, cmp.Diff("/chat/completions", call.path))
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
messages, ok := call.body["messages"].([]any)
require.True(t, ok)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should support partial usage", func(t *testing.T) {
@ -920,9 +922,9 @@ func TestDoGenerate(t *testing.T) {
})
require.NoError(t, err)
require.Equal(t, int64(20), result.Usage.InputTokens)
require.Equal(t, int64(0), result.Usage.OutputTokens)
require.Equal(t, int64(20), result.Usage.TotalTokens)
assert.Empty(t, cmp.Diff(int64(20), result.Usage.InputTokens))
assert.Empty(t, cmp.Diff(int64(0), result.Usage.OutputTokens))
assert.Empty(t, cmp.Diff(int64(20), result.Usage.TotalTokens))
})
t.Run("should extract logprobs", func(t *testing.T) {
@ -982,7 +984,7 @@ func TestDoGenerate(t *testing.T) {
})
require.NoError(t, err)
require.Equal(t, fantasy.FinishReasonStop, result.FinishReason)
assert.Empty(t, cmp.Diff(fantasy.FinishReasonStop, result.FinishReason))
})
t.Run("should support unknown finish reason", func(t *testing.T) {
@ -1007,7 +1009,7 @@ func TestDoGenerate(t *testing.T) {
})
require.NoError(t, err)
require.Equal(t, fantasy.FinishReasonUnknown, result.FinishReason)
assert.Empty(t, cmp.Diff(fantasy.FinishReasonUnknown, result.FinishReason))
})
t.Run("should pass the model and the messages", func(t *testing.T) {
@ -1035,14 +1037,14 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should pass settings", func(t *testing.T) {
@ -1075,15 +1077,15 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
logitBias := call.body["logit_bias"].(map[string]any)
require.Equal(t, float64(-100), logitBias["50256"])
require.Equal(t, false, call.body["parallel_tool_calls"])
require.Equal(t, "test-user-id", call.body["user"])
assert.Empty(t, cmp.Diff(float64(-100), logitBias["50256"]))
assert.Empty(t, cmp.Diff(false, call.body["parallel_tool_calls"]))
assert.Empty(t, cmp.Diff("test-user-id", call.body["user"]))
})
t.Run("should pass reasoningEffort setting", func(t *testing.T) {
@ -1116,15 +1118,15 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "o1-mini", call.body["model"])
require.Equal(t, "low", call.body["reasoning_effort"])
assert.Empty(t, cmp.Diff("o1-mini", call.body["model"]))
assert.Empty(t, cmp.Diff("low", call.body["reasoning_effort"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should pass textVerbosity setting", func(t *testing.T) {
@ -1155,15 +1157,15 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-4o", call.body["model"])
require.Equal(t, "low", call.body["verbosity"])
assert.Empty(t, cmp.Diff("gpt-4o", call.body["model"]))
assert.Empty(t, cmp.Diff("low", call.body["verbosity"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should pass tools and toolChoice", func(t *testing.T) {
@ -1208,7 +1210,7 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
@ -1217,17 +1219,17 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, tools, 1)
tool := tools[0].(map[string]any)
require.Equal(t, "function", tool["type"])
assert.Empty(t, cmp.Diff("function", tool["type"]))
function := tool["function"].(map[string]any)
require.Equal(t, "test-tool", function["name"])
require.Equal(t, false, function["strict"])
assert.Empty(t, cmp.Diff("test-tool", function["name"]))
assert.Empty(t, cmp.Diff(false, function["strict"]))
toolChoice := call.body["tool_choice"].(map[string]any)
require.Equal(t, "function", toolChoice["type"])
assert.Empty(t, cmp.Diff("function", toolChoice["type"]))
toolChoiceFunction := toolChoice["function"].(map[string]any)
require.Equal(t, "test-tool", toolChoiceFunction["name"])
assert.Empty(t, cmp.Diff("test-tool", toolChoiceFunction["name"]))
})
t.Run("should parse tool results", func(t *testing.T) {
@ -1282,9 +1284,9 @@ func TestDoGenerate(t *testing.T) {
toolCall, ok := result.Content[0].(fantasy.ToolCallContent)
require.True(t, ok)
require.Equal(t, "call_O17Uplv4lJvD6DVdIvFFeRMw", toolCall.ToolCallID)
require.Equal(t, "test-tool", toolCall.ToolName)
require.Equal(t, `{"value":"Spark"}`, toolCall.Input)
assert.Empty(t, cmp.Diff("call_O17Uplv4lJvD6DVdIvFFeRMw", toolCall.ToolCallID))
assert.Empty(t, cmp.Diff("test-tool", toolCall.ToolName))
assert.Empty(t, cmp.Diff(`{"value":"Spark"}`, toolCall.Input))
})
t.Run("should handle ToolChoiceRequired", func(t *testing.T) {
@ -1329,21 +1331,21 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
// Verify tool is present
tools := call.body["tools"].([]any)
require.Len(t, tools, 1)
tool := tools[0].(map[string]any)
require.Equal(t, "function", tool["type"])
assert.Empty(t, cmp.Diff("function", tool["type"]))
function := tool["function"].(map[string]any)
require.Equal(t, "test-tool", function["name"])
assert.Empty(t, cmp.Diff("test-tool", function["name"]))
// Verify tool_choice is set to "required" (not a function name)
toolChoice := call.body["tool_choice"]
require.Equal(t, "required", toolChoice)
assert.Empty(t, cmp.Diff("required", toolChoice))
})
t.Run("should parse annotations/citations", func(t *testing.T) {
@ -1383,13 +1385,13 @@ func TestDoGenerate(t *testing.T) {
textContent, ok := result.Content[0].(fantasy.TextContent)
require.True(t, ok)
require.Equal(t, "Based on the search results [doc1], I found information.", textContent.Text)
assert.Empty(t, cmp.Diff("Based on the search results [doc1], I found information.", textContent.Text))
sourceContent, ok := result.Content[1].(fantasy.SourceContent)
require.True(t, ok)
require.Equal(t, fantasy.SourceTypeURL, sourceContent.SourceType)
require.Equal(t, "https://example.com/doc1.pdf", sourceContent.URL)
require.Equal(t, "Document 1", sourceContent.Title)
assert.Empty(t, cmp.Diff(fantasy.SourceTypeURL, sourceContent.SourceType))
assert.Empty(t, cmp.Diff("https://example.com/doc1.pdf", sourceContent.URL))
assert.Empty(t, cmp.Diff("Document 1", sourceContent.Title))
require.NotEmpty(t, sourceContent.ID)
})
@ -1422,10 +1424,10 @@ func TestDoGenerate(t *testing.T) {
})
require.NoError(t, err)
require.Equal(t, int64(1152), result.Usage.CacheReadTokens)
require.Equal(t, int64(15), result.Usage.InputTokens)
require.Equal(t, int64(20), result.Usage.OutputTokens)
require.Equal(t, int64(35), result.Usage.TotalTokens)
assert.Empty(t, cmp.Diff(int64(1152), result.Usage.CacheReadTokens))
assert.Empty(t, cmp.Diff(int64(15), result.Usage.InputTokens))
assert.Empty(t, cmp.Diff(int64(20), result.Usage.OutputTokens))
assert.Empty(t, cmp.Diff(int64(35), result.Usage.TotalTokens))
})
t.Run("should return accepted_prediction_tokens and rejected_prediction_tokens", func(t *testing.T) {
@ -1463,8 +1465,8 @@ func TestDoGenerate(t *testing.T) {
openaiMeta, ok := result.ProviderMetadata["openai"].(*ProviderMetadata)
require.True(t, ok)
require.Equal(t, int64(123), openaiMeta.AcceptedPredictionTokens)
require.Equal(t, int64(456), openaiMeta.RejectedPredictionTokens)
assert.Empty(t, cmp.Diff(int64(123), openaiMeta.AcceptedPredictionTokens))
assert.Empty(t, cmp.Diff(int64(456), openaiMeta.RejectedPredictionTokens))
})
t.Run("should clear out temperature, top_p, frequency_penalty, presence_penalty for reasoning models", func(t *testing.T) {
@ -1494,14 +1496,14 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "o1-preview", call.body["model"])
assert.Empty(t, cmp.Diff("o1-preview", call.body["model"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
// These should not be present
require.Nil(t, call.body["temperature"])
@ -1511,8 +1513,8 @@ func TestDoGenerate(t *testing.T) {
// Should have warnings
require.Len(t, result.Warnings, 4)
require.Equal(t, fantasy.CallWarningTypeUnsupportedSetting, result.Warnings[0].Type)
require.Equal(t, "temperature", result.Warnings[0].Setting)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeUnsupportedSetting, result.Warnings[0].Type))
assert.Empty(t, cmp.Diff("temperature", result.Warnings[0].Setting))
require.Contains(t, result.Warnings[0].Details, "temperature is not supported for reasoning models")
})
@ -1540,16 +1542,16 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "o1-preview", call.body["model"])
require.Equal(t, float64(1000), call.body["max_completion_tokens"])
assert.Empty(t, cmp.Diff("o1-preview", call.body["model"]))
assert.Empty(t, cmp.Diff(float64(1000), call.body["max_completion_tokens"]))
require.Nil(t, call.body["max_tokens"])
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should return reasoning tokens", func(t *testing.T) {
@ -1581,10 +1583,10 @@ func TestDoGenerate(t *testing.T) {
})
require.NoError(t, err)
require.Equal(t, int64(15), result.Usage.InputTokens)
require.Equal(t, int64(20), result.Usage.OutputTokens)
require.Equal(t, int64(35), result.Usage.TotalTokens)
require.Equal(t, int64(10), result.Usage.ReasoningTokens)
assert.Empty(t, cmp.Diff(int64(15), result.Usage.InputTokens))
assert.Empty(t, cmp.Diff(int64(20), result.Usage.OutputTokens))
assert.Empty(t, cmp.Diff(int64(35), result.Usage.TotalTokens))
assert.Empty(t, cmp.Diff(int64(10), result.Usage.ReasoningTokens))
})
t.Run("should send max_completion_tokens extension setting", func(t *testing.T) {
@ -1615,15 +1617,15 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "o1-preview", call.body["model"])
require.Equal(t, float64(255), call.body["max_completion_tokens"])
assert.Empty(t, cmp.Diff("o1-preview", call.body["model"]))
assert.Empty(t, cmp.Diff(float64(255), call.body["max_completion_tokens"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should send prediction extension setting", func(t *testing.T) {
@ -1657,18 +1659,18 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
prediction := call.body["prediction"].(map[string]any)
require.Equal(t, "content", prediction["type"])
require.Equal(t, "Hello, World!", prediction["content"])
assert.Empty(t, cmp.Diff("content", prediction["type"]))
assert.Empty(t, cmp.Diff("Hello, World!", prediction["content"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should send store extension setting", func(t *testing.T) {
@ -1699,15 +1701,15 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
require.Equal(t, true, call.body["store"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
assert.Empty(t, cmp.Diff(true, call.body["store"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should send metadata extension values", func(t *testing.T) {
@ -1740,17 +1742,17 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
metadata := call.body["metadata"].(map[string]any)
require.Equal(t, "value", metadata["custom"])
assert.Empty(t, cmp.Diff("value", metadata["custom"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should send promptCacheKey extension value", func(t *testing.T) {
@ -1781,15 +1783,15 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
require.Equal(t, "test-cache-key-123", call.body["prompt_cache_key"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
assert.Empty(t, cmp.Diff("test-cache-key-123", call.body["prompt_cache_key"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should send safety_identifier extension value", func(t *testing.T) {
@ -1820,15 +1822,15 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
require.Equal(t, "test-safety-identifier-123", call.body["safety_identifier"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
assert.Empty(t, cmp.Diff("test-safety-identifier-123", call.body["safety_identifier"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should remove temperature setting for search preview models", func(t *testing.T) {
@ -1855,12 +1857,12 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-4o-search-preview", call.body["model"])
assert.Empty(t, cmp.Diff("gpt-4o-search-preview", call.body["model"]))
require.Nil(t, call.body["temperature"])
require.Len(t, result.Warnings, 1)
require.Equal(t, fantasy.CallWarningTypeUnsupportedSetting, result.Warnings[0].Type)
require.Equal(t, "temperature", result.Warnings[0].Setting)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeUnsupportedSetting, result.Warnings[0].Type))
assert.Empty(t, cmp.Diff("temperature", result.Warnings[0].Setting))
require.Contains(t, result.Warnings[0].Details, "search preview models")
})
@ -1892,15 +1894,15 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "o3-mini", call.body["model"])
require.Equal(t, "flex", call.body["service_tier"])
assert.Empty(t, cmp.Diff("o3-mini", call.body["model"]))
assert.Empty(t, cmp.Diff("flex", call.body["service_tier"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should show warning when using flex processing with unsupported model", func(t *testing.T) {
@ -1932,8 +1934,8 @@ func TestDoGenerate(t *testing.T) {
require.Nil(t, call.body["service_tier"])
require.Len(t, result.Warnings, 1)
require.Equal(t, fantasy.CallWarningTypeUnsupportedSetting, result.Warnings[0].Type)
require.Equal(t, "ServiceTier", result.Warnings[0].Setting)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeUnsupportedSetting, result.Warnings[0].Type))
assert.Empty(t, cmp.Diff("ServiceTier", result.Warnings[0].Setting))
require.Contains(t, result.Warnings[0].Details, "flex processing is only available")
})
@ -1963,15 +1965,15 @@ func TestDoGenerate(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-4o-mini", call.body["model"])
require.Equal(t, "priority", call.body["service_tier"])
assert.Empty(t, cmp.Diff("gpt-4o-mini", call.body["model"]))
assert.Empty(t, cmp.Diff("priority", call.body["service_tier"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should show warning when using priority processing with unsupported model", func(t *testing.T) {
@ -2003,8 +2005,8 @@ func TestDoGenerate(t *testing.T) {
require.Nil(t, call.body["service_tier"])
require.Len(t, result.Warnings, 1)
require.Equal(t, fantasy.CallWarningTypeUnsupportedSetting, result.Warnings[0].Type)
require.Equal(t, "ServiceTier", result.Warnings[0].Setting)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeUnsupportedSetting, result.Warnings[0].Type))
assert.Empty(t, cmp.Diff("ServiceTier", result.Warnings[0].Setting))
require.Contains(t, result.Warnings[0].Details, "priority processing is only available")
})
}
@ -2329,14 +2331,14 @@ func TestDoStream(t *testing.T) {
require.NotEqual(t, -1, textStart)
require.NotEqual(t, -1, textEnd)
require.NotEqual(t, -1, finish)
require.Equal(t, []string{"Hello", ", ", "World!"}, deltas)
assert.Empty(t, cmp.Diff([]string{"Hello", ", ", "World!"}, deltas))
// Check finish part
finishPart := parts[finish]
require.Equal(t, fantasy.FinishReasonStop, finishPart.FinishReason)
require.Equal(t, int64(17), finishPart.Usage.InputTokens)
require.Equal(t, int64(227), finishPart.Usage.OutputTokens)
require.Equal(t, int64(244), finishPart.Usage.TotalTokens)
assert.Empty(t, cmp.Diff(fantasy.FinishReasonStop, finishPart.FinishReason))
assert.Empty(t, cmp.Diff(int64(17), finishPart.Usage.InputTokens))
assert.Empty(t, cmp.Diff(int64(227), finishPart.Usage.OutputTokens))
assert.Empty(t, cmp.Diff(int64(244), finishPart.Usage.TotalTokens))
})
t.Run("should stream tool deltas", func(t *testing.T) {
@ -2387,17 +2389,17 @@ func TestDoStream(t *testing.T) {
switch part.Type {
case fantasy.StreamPartTypeToolInputStart:
toolInputStart = i
require.Equal(t, "call_O17Uplv4lJvD6DVdIvFFeRMw", part.ID)
require.Equal(t, "test-tool", part.ToolCallName)
assert.Empty(t, cmp.Diff("call_O17Uplv4lJvD6DVdIvFFeRMw", part.ID))
assert.Empty(t, cmp.Diff("test-tool", part.ToolCallName))
case fantasy.StreamPartTypeToolInputDelta:
toolDeltas = append(toolDeltas, part.Delta)
case fantasy.StreamPartTypeToolInputEnd:
toolInputEnd = i
case fantasy.StreamPartTypeToolCall:
toolCall = i
require.Equal(t, "call_O17Uplv4lJvD6DVdIvFFeRMw", part.ID)
require.Equal(t, "test-tool", part.ToolCallName)
require.Equal(t, `{"value":"Sparkle Day"}`, part.ToolCallInput)
assert.Empty(t, cmp.Diff("call_O17Uplv4lJvD6DVdIvFFeRMw", part.ID))
assert.Empty(t, cmp.Diff("test-tool", part.ToolCallName))
assert.Empty(t, cmp.Diff(`{"value":"Sparkle Day"}`, part.ToolCallInput))
}
}
@ -2410,7 +2412,7 @@ func TestDoStream(t *testing.T) {
for _, delta := range toolDeltas {
fullInput.WriteString(delta)
}
require.Equal(t, `{"value":"Sparkle Day"}`, fullInput.String())
assert.Empty(t, cmp.Diff(`{"value":"Sparkle Day"}`, fullInput.String()))
})
t.Run("should stream annotations/citations", func(t *testing.T) {
@ -2460,9 +2462,9 @@ func TestDoStream(t *testing.T) {
}
require.NotNil(t, sourcePart)
require.Equal(t, fantasy.SourceTypeURL, sourcePart.SourceType)
require.Equal(t, "https://example.com/doc1.pdf", sourcePart.URL)
require.Equal(t, "Document 1", sourcePart.Title)
assert.Empty(t, cmp.Diff(fantasy.SourceTypeURL, sourcePart.SourceType))
assert.Empty(t, cmp.Diff("https://example.com/doc1.pdf", sourcePart.URL))
assert.Empty(t, cmp.Diff("Document 1", sourcePart.Title))
require.NotEmpty(t, sourcePart.ID)
})
@ -2531,20 +2533,20 @@ func TestDoStream(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "POST", call.method)
require.Equal(t, "/chat/completions", call.path)
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
require.Equal(t, true, call.body["stream"])
assert.Empty(t, cmp.Diff("POST", call.method))
assert.Empty(t, cmp.Diff("/chat/completions", call.path))
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
assert.Empty(t, cmp.Diff(true, call.body["stream"]))
streamOptions := call.body["stream_options"].(map[string]any)
require.Equal(t, true, streamOptions["include_usage"])
assert.Empty(t, cmp.Diff(true, streamOptions["include_usage"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should return cached tokens in providerMetadata", func(t *testing.T) {
@ -2591,10 +2593,10 @@ func TestDoStream(t *testing.T) {
}
require.NotNil(t, finishPart)
require.Equal(t, int64(1152), finishPart.Usage.CacheReadTokens)
require.Equal(t, int64(15), finishPart.Usage.InputTokens)
require.Equal(t, int64(20), finishPart.Usage.OutputTokens)
require.Equal(t, int64(35), finishPart.Usage.TotalTokens)
assert.Empty(t, cmp.Diff(int64(1152), finishPart.Usage.CacheReadTokens))
assert.Empty(t, cmp.Diff(int64(15), finishPart.Usage.InputTokens))
assert.Empty(t, cmp.Diff(int64(20), finishPart.Usage.OutputTokens))
assert.Empty(t, cmp.Diff(int64(35), finishPart.Usage.TotalTokens))
})
t.Run("should return accepted_prediction_tokens and rejected_prediction_tokens", func(t *testing.T) {
@ -2646,8 +2648,8 @@ func TestDoStream(t *testing.T) {
openaiMeta, ok := finishPart.ProviderMetadata["openai"].(*ProviderMetadata)
require.True(t, ok)
require.Equal(t, int64(123), openaiMeta.AcceptedPredictionTokens)
require.Equal(t, int64(456), openaiMeta.RejectedPredictionTokens)
assert.Empty(t, cmp.Diff(int64(123), openaiMeta.AcceptedPredictionTokens))
assert.Empty(t, cmp.Diff(int64(456), openaiMeta.RejectedPredictionTokens))
})
t.Run("should send store extension setting", func(t *testing.T) {
@ -2678,19 +2680,19 @@ func TestDoStream(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
require.Equal(t, true, call.body["stream"])
require.Equal(t, true, call.body["store"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
assert.Empty(t, cmp.Diff(true, call.body["stream"]))
assert.Empty(t, cmp.Diff(true, call.body["store"]))
streamOptions := call.body["stream_options"].(map[string]any)
require.Equal(t, true, streamOptions["include_usage"])
assert.Empty(t, cmp.Diff(true, streamOptions["include_usage"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should send metadata extension values", func(t *testing.T) {
@ -2723,21 +2725,21 @@ func TestDoStream(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-3.5-turbo", call.body["model"])
require.Equal(t, true, call.body["stream"])
assert.Empty(t, cmp.Diff("gpt-3.5-turbo", call.body["model"]))
assert.Empty(t, cmp.Diff(true, call.body["stream"]))
metadata := call.body["metadata"].(map[string]any)
require.Equal(t, "value", metadata["custom"])
assert.Empty(t, cmp.Diff("value", metadata["custom"]))
streamOptions := call.body["stream_options"].(map[string]any)
require.Equal(t, true, streamOptions["include_usage"])
assert.Empty(t, cmp.Diff(true, streamOptions["include_usage"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should send serviceTier flex processing setting in streaming", func(t *testing.T) {
@ -2768,19 +2770,19 @@ func TestDoStream(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "o3-mini", call.body["model"])
require.Equal(t, "flex", call.body["service_tier"])
require.Equal(t, true, call.body["stream"])
assert.Empty(t, cmp.Diff("o3-mini", call.body["model"]))
assert.Empty(t, cmp.Diff("flex", call.body["service_tier"]))
assert.Empty(t, cmp.Diff(true, call.body["stream"]))
streamOptions := call.body["stream_options"].(map[string]any)
require.Equal(t, true, streamOptions["include_usage"])
assert.Empty(t, cmp.Diff(true, streamOptions["include_usage"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should send serviceTier priority processing setting in streaming", func(t *testing.T) {
@ -2811,19 +2813,19 @@ func TestDoStream(t *testing.T) {
require.Len(t, server.calls, 1)
call := server.calls[0]
require.Equal(t, "gpt-4o-mini", call.body["model"])
require.Equal(t, "priority", call.body["service_tier"])
require.Equal(t, true, call.body["stream"])
assert.Empty(t, cmp.Diff("gpt-4o-mini", call.body["model"]))
assert.Empty(t, cmp.Diff("priority", call.body["service_tier"]))
assert.Empty(t, cmp.Diff(true, call.body["stream"]))
streamOptions := call.body["stream_options"].(map[string]any)
require.Equal(t, true, streamOptions["include_usage"])
assert.Empty(t, cmp.Diff(true, streamOptions["include_usage"]))
messages := call.body["messages"].([]any)
require.Len(t, messages, 1)
message := messages[0].(map[string]any)
require.Equal(t, "user", message["role"])
require.Equal(t, "Hello", message["content"])
assert.Empty(t, cmp.Diff("user", message["role"]))
assert.Empty(t, cmp.Diff("Hello", message["content"]))
})
t.Run("should stream text delta for reasoning models", func(t *testing.T) {
@ -2860,9 +2862,10 @@ func TestDoStream(t *testing.T) {
textDeltas = append(textDeltas, part.Delta)
}
}
assert.
// Should contain the text content (without empty delta)
require.Equal(t, []string{"Hello, World!"}, textDeltas)
// Should contain the text content (without empty delta)
Empty(t, cmp.Diff([]string{"Hello, World!"}, textDeltas))
})
t.Run("should send reasoning tokens", func(t *testing.T) {
@ -2910,10 +2913,10 @@ func TestDoStream(t *testing.T) {
}
require.NotNil(t, finishPart)
require.Equal(t, int64(15), finishPart.Usage.InputTokens)
require.Equal(t, int64(20), finishPart.Usage.OutputTokens)
require.Equal(t, int64(35), finishPart.Usage.TotalTokens)
require.Equal(t, int64(10), finishPart.Usage.ReasoningTokens)
assert.Empty(t, cmp.Diff(int64(15), finishPart.Usage.InputTokens))
assert.Empty(t, cmp.Diff(int64(20), finishPart.Usage.OutputTokens))
assert.Empty(t, cmp.Diff(int64(35), finishPart.Usage.TotalTokens))
assert.Empty(t, cmp.Diff(int64(10), finishPart.Usage.ReasoningTokens))
})
}
@ -2940,7 +2943,7 @@ func TestDefaultToPrompt_DropsEmptyMessages(t *testing.T) {
require.Len(t, messages, 1, "should only have user message")
require.Len(t, warnings, 1)
require.Equal(t, fantasy.CallWarningTypeOther, warnings[0].Type)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeOther, warnings[0].Type))
require.Contains(t, warnings[0].Message, "dropping empty assistant message")
})
@ -3105,7 +3108,7 @@ func TestResponsesToPrompt_DropsEmptyMessages(t *testing.T) {
require.Len(t, input, 1, "should only have user message")
require.Len(t, warnings, 1)
require.Equal(t, fantasy.CallWarningTypeOther, warnings[0].Type)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeOther, warnings[0].Type))
require.Contains(t, warnings[0].Message, "dropping empty assistant message")
})

View file

@ -5,6 +5,8 @@ import (
"testing"
"charm.land/fantasy"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -44,22 +46,22 @@ func TestToPromptFunc_ReasoningContent(t *testing.T) {
// First message (user) - no reasoning
msg1 := messages[0].OfUser
require.NotNil(t, msg1)
require.Equal(t, "What is 2+2?", msg1.Content.OfString.Value)
assert.Empty(t, cmp.Diff("What is 2+2?", msg1.Content.OfString.Value))
// Second message (assistant) - with reasoning
msg2 := messages[1].OfAssistant
require.NotNil(t, msg2)
require.Equal(t, "The answer is 4.", msg2.Content.OfString.Value)
assert.Empty(t, cmp.Diff("The answer is 4.", msg2.Content.OfString.Value))
// Check reasoning_content in extra fields
extraFields := msg2.ExtraFields()
reasoningContent, hasReasoning := extraFields["reasoning_content"]
require.True(t, hasReasoning)
require.Equal(t, "Let me think... 2+2 equals 4.", reasoningContent)
assert.Empty(t, cmp.Diff("Let me think... 2+2 equals 4.", reasoningContent))
// Third message (user) - no reasoning
msg3 := messages[2].OfUser
require.NotNil(t, msg3)
require.Equal(t, "What about 3+3?", msg3.Content.OfString.Value)
assert.Empty(t, cmp.Diff("What about 3+3?", msg3.Content.OfString.Value))
})
t.Run("should handle assistant messages with only reasoning content", func(t *testing.T) {
@ -89,7 +91,7 @@ func TestToPromptFunc_ReasoningContent(t *testing.T) {
// User message - unchanged
msg := messages[0].OfUser
require.NotNil(t, msg)
require.Equal(t, "Hello", msg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("Hello", msg.Content.OfString.Value))
})
t.Run("should not add reasoning_content to messages without reasoning", func(t *testing.T) {
@ -118,7 +120,7 @@ func TestToPromptFunc_ReasoningContent(t *testing.T) {
// Assistant message without reasoning
msg := messages[1].OfAssistant
require.NotNil(t, msg)
require.Equal(t, "Hi there!", msg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("Hi there!", msg.Content.OfString.Value))
extraFields := msg.ExtraFields()
_, hasReasoning := extraFields["reasoning_content"]
require.False(t, hasReasoning)
@ -150,12 +152,12 @@ func TestToPromptFunc_ReasoningContent(t *testing.T) {
// System message - unchanged
systemMsg := messages[0].OfSystem
require.NotNil(t, systemMsg)
require.Equal(t, "You are helpful.", systemMsg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("You are helpful.", systemMsg.Content.OfString.Value))
// User message - unchanged
userMsg := messages[1].OfUser
require.NotNil(t, userMsg)
require.Equal(t, "Hello", userMsg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("Hello", userMsg.Content.OfString.Value))
})
t.Run("should use last assistant TextPart only", func(t *testing.T) {
@ -186,7 +188,7 @@ func TestToPromptFunc_ReasoningContent(t *testing.T) {
// Assistant message should use only the last TextPart (matching openai behavior)
assistantMsg := messages[1].OfAssistant
require.NotNil(t, assistantMsg)
require.Equal(t, "Third part.", assistantMsg.Content.OfString.Value)
assert.Empty(t, cmp.Diff("Third part.", assistantMsg.Content.OfString.Value))
})
t.Run("should include user messages with only unsupported attachments", func(t *testing.T) {
@ -226,11 +228,11 @@ func TestToPromptFunc_ReasoningContent(t *testing.T) {
msg1 := messages[0].OfUser
require.NotNil(t, msg1)
require.Equal(t, "Hello", msg1.Content.OfString.Value)
assert.Empty(t, cmp.Diff("Hello", msg1.Content.OfString.Value))
msg2 := messages[1].OfUser
require.NotNil(t, msg2)
require.Equal(t, "After unsupported", msg2.Content.OfString.Value)
assert.Empty(t, cmp.Diff("After unsupported", msg2.Content.OfString.Value))
})
t.Run("should detect PDF file IDs using strings.HasPrefix", func(t *testing.T) {
@ -264,7 +266,7 @@ func TestToPromptFunc_ReasoningContent(t *testing.T) {
// Second content part should be file with file_id
filePart := content[1].OfFile
require.NotNil(t, filePart)
require.Equal(t, "file-abc123xyz", filePart.File.FileID.Value)
assert.Empty(t, cmp.Diff("file-abc123xyz", filePart.File.FileID.Value))
})
}
@ -291,7 +293,7 @@ func TestToPromptFunc_DropsEmptyMessages(t *testing.T) {
require.Len(t, messages, 1, "should only have user message")
require.Len(t, warnings, 1)
require.Equal(t, fantasy.CallWarningTypeOther, warnings[0].Type)
assert.Empty(t, cmp.Diff(fantasy.CallWarningTypeOther, warnings[0].Type))
require.Contains(t, warnings[0].Message, "dropping empty assistant message")
})

View file

@ -9,6 +9,8 @@ import (
"charm.land/fantasy"
"charm.land/fantasy/providers/anthropic"
"charm.land/x/vcr"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -145,7 +147,7 @@ func testAnthropicThinking(t *testing.T, result *fantasy.AgentResult) {
}
require.Greater(t, reasoningContentCount, 0)
require.Greater(t, signaturesCount, 0)
require.Equal(t, reasoningContentCount, signaturesCount)
assert.Empty(t, cmp.Diff(reasoningContentCount, signaturesCount))
}
func anthropicBuilder(model string) builderFunc {

View file

@ -1,7 +1,7 @@
package providertests
import (
"cmp"
stdcmp "cmp"
"net/http"
"os"
"testing"
@ -10,6 +10,8 @@ import (
"charm.land/fantasy/providers/azure"
"charm.land/fantasy/providers/openai"
"charm.land/x/vcr"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -29,8 +31,8 @@ func TestAzureResponsesCommon(t *testing.T) {
func azureReasoningBuilder(model string) builderFunc {
return func(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
provider, err := azure.New(
azure.WithBaseURL(cmp.Or(os.Getenv("FANTASY_AZURE_BASE_URL"), defaultBaseURL)),
azure.WithAPIKey(cmp.Or(os.Getenv("FANTASY_AZURE_API_KEY"), "(missing)")),
azure.WithBaseURL(stdcmp.Or(os.Getenv("FANTASY_AZURE_BASE_URL"), defaultBaseURL)),
azure.WithAPIKey(stdcmp.Or(os.Getenv("FANTASY_AZURE_API_KEY"), "(missing)")),
azure.WithHTTPClient(&http.Client{Transport: r}),
azure.WithUseResponsesAPI(),
)
@ -96,5 +98,5 @@ func testAzureResponsesThinkingWithSummaryThinking(t *testing.T, result *fantasy
}
require.Greater(t, reasoningContentCount, 0)
require.Greater(t, encryptedData, 0)
require.Equal(t, reasoningContentCount, encryptedData)
assert.Empty(t, cmp.Diff(reasoningContentCount, encryptedData))
}

View file

@ -9,7 +9,9 @@ import (
"charm.land/fantasy"
"charm.land/x/vcr"
"github.com/google/go-cmp/cmp"
"github.com/joho/godotenv"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -118,7 +120,7 @@ func testTool(t *testing.T, pair builderPair) {
require.False(t, tc.Invalid)
}
require.Len(t, toolCalls, 1)
require.Equal(t, toolCalls[0].ToolName, "weather")
assert.Empty(t, cmp.Diff(toolCalls[0].ToolName, "weather"))
want1 := "Florence"
want2 := "40"

View file

@ -8,6 +8,8 @@ import (
"charm.land/fantasy"
"charm.land/fantasy/providers/openai"
"charm.land/x/vcr"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -95,5 +97,5 @@ func testOpenAIResponsesThinkingWithSummaryThinking(t *testing.T, result *fantas
}
require.Greater(t, reasoningContentCount, 0)
require.Greater(t, encryptedData, 0)
require.Equal(t, reasoningContentCount, encryptedData)
assert.Empty(t, cmp.Diff(reasoningContentCount, encryptedData))
}

View file

@ -9,6 +9,8 @@ import (
"charm.land/fantasy/providers/anthropic"
"charm.land/fantasy/providers/openrouter"
"charm.land/x/vcr"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -99,7 +101,7 @@ func testOpenrouterThinkingWithSignature(t *testing.T, result *fantasy.AgentResu
}
require.Greater(t, reasoningContentCount, 0)
require.Greater(t, signaturesCount, 0)
require.Equal(t, reasoningContentCount, signaturesCount)
assert.Empty(t, cmp.Diff(reasoningContentCount, signaturesCount))
// we also add the anthropic metadata so test that
testAnthropicThinking(t, result)
}

View file

@ -4,6 +4,7 @@ import (
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"charm.land/fantasy"
"charm.land/fantasy/providers/anthropic"
@ -11,6 +12,7 @@ import (
"charm.land/fantasy/providers/openai"
"charm.land/fantasy/providers/openaicompat"
"charm.land/fantasy/providers/openrouter"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -36,11 +38,11 @@ func TestProviderRegistry_Serialization_OpenAIOptions(t *testing.T) {
po, ok := raw.ProviderOptions[openai.Name]
require.True(t, ok)
require.Equal(t, openai.TypeProviderOptions, po["type"]) // no magic strings
assert.Empty(t, cmp.Diff(openai.TypeProviderOptions, po["type"])) // no magic strings
// ensure inner data has the field we set
inner, ok := po["data"].(map[string]any)
require.True(t, ok)
require.Equal(t, "tester", inner["user"])
assert.Empty(t, cmp.Diff("tester", inner["user"]))
var decoded fantasy.Message
require.NoError(t, jsonv2.Unmarshal(data, &decoded))
@ -50,7 +52,7 @@ func TestProviderRegistry_Serialization_OpenAIOptions(t *testing.T) {
opt, ok := got.(*openai.ProviderOptions)
require.True(t, ok)
require.NotNil(t, opt.User)
require.Equal(t, "tester", *opt.User)
assert.Empty(t, cmp.Diff("tester", *opt.User))
}
func TestProviderRegistry_Serialization_OpenAIResponses(t *testing.T) {
@ -81,11 +83,11 @@ func TestProviderRegistry_Serialization_OpenAIResponses(t *testing.T) {
require.NoError(t, jsonv2.Unmarshal(data, &raw))
po := raw.ProviderOptions[openai.Name]
require.Equal(t, openai.TypeResponsesProviderOptions, po["type"]) // no magic strings
assert.Empty(t, cmp.Diff(openai.TypeResponsesProviderOptions, po["type"])) // no magic strings
inner, ok := po["data"].(map[string]any)
require.True(t, ok)
require.Equal(t, "cache-key-1", inner["prompt_cache_key"])
require.Equal(t, true, inner["parallel_tool_calls"])
assert.Empty(t, cmp.Diff("cache-key-1", inner["prompt_cache_key"]))
assert.Empty(t, cmp.Diff(true, inner["parallel_tool_calls"]))
// Unmarshal back and assert concrete type
var decoded fantasy.Message
@ -94,9 +96,9 @@ func TestProviderRegistry_Serialization_OpenAIResponses(t *testing.T) {
reqOpts, ok := got.(*openai.ResponsesProviderOptions)
require.True(t, ok)
require.NotNil(t, reqOpts.PromptCacheKey)
require.Equal(t, "cache-key-1", *reqOpts.PromptCacheKey)
assert.Empty(t, cmp.Diff("cache-key-1", *reqOpts.PromptCacheKey))
require.NotNil(t, reqOpts.ParallelToolCalls)
require.Equal(t, true, *reqOpts.ParallelToolCalls)
assert.Empty(t, cmp.Diff(true, *reqOpts.ParallelToolCalls))
}
func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *testing.T) {
@ -132,10 +134,10 @@ func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *test
require.True(t, ok)
om, ok := pm[openai.Name].(map[string]any)
require.True(t, ok)
require.Equal(t, openai.TypeResponsesReasoningMetadata, om["type"]) // no magic strings
assert.Empty(t, cmp.Diff(openai.TypeResponsesReasoningMetadata, om["type"])) // no magic strings
inner, ok := om["data"].(map[string]any)
require.True(t, ok)
require.Equal(t, "item-123", inner["item_id"])
assert.Empty(t, cmp.Diff("item-123", inner["item_id"]))
// Unmarshal back
var decoded fantasy.Response
@ -145,8 +147,8 @@ func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *test
require.True(t, ok)
meta, ok := val.(*openai.ResponsesReasoningMetadata)
require.True(t, ok)
require.Equal(t, "item-123", meta.ItemID)
require.Equal(t, []string{"part1", "part2"}, meta.Summary)
assert.Empty(t, cmp.Diff("item-123", meta.ItemID))
assert.Empty(t, cmp.Diff([]string{"part1", "part2"}, meta.Summary))
}
func TestProviderRegistry_Serialization_AnthropicOptions(t *testing.T) {
@ -175,7 +177,7 @@ func TestProviderRegistry_Serialization_AnthropicOptions(t *testing.T) {
opt, ok := got.(*anthropic.ProviderOptions)
require.True(t, ok)
require.NotNil(t, opt.SendReasoning)
require.Equal(t, true, *opt.SendReasoning)
assert.Empty(t, cmp.Diff(true, *opt.SendReasoning))
}
func TestProviderRegistry_Serialization_GoogleOptions(t *testing.T) {
@ -203,8 +205,8 @@ func TestProviderRegistry_Serialization_GoogleOptions(t *testing.T) {
require.True(t, ok)
opt, ok := got.(*google.ProviderOptions)
require.True(t, ok)
require.Equal(t, "cached-123", opt.CachedContent)
require.Equal(t, "BLOCK_ONLY_HIGH", opt.Threshold)
assert.Empty(t, cmp.Diff("cached-123", opt.CachedContent))
assert.Empty(t, cmp.Diff("BLOCK_ONLY_HIGH", opt.Threshold))
}
func TestProviderRegistry_Serialization_OpenRouterOptions(t *testing.T) {
@ -234,9 +236,9 @@ func TestProviderRegistry_Serialization_OpenRouterOptions(t *testing.T) {
opt, ok := got.(*openrouter.ProviderOptions)
require.True(t, ok)
require.NotNil(t, opt.IncludeUsage)
require.Equal(t, true, *opt.IncludeUsage)
assert.Empty(t, cmp.Diff(true, *opt.IncludeUsage))
require.NotNil(t, opt.User)
require.Equal(t, "test-user", *opt.User)
assert.Empty(t, cmp.Diff("test-user", *opt.User))
}
func TestProviderRegistry_Serialization_OpenAICompatOptions(t *testing.T) {
@ -266,9 +268,9 @@ func TestProviderRegistry_Serialization_OpenAICompatOptions(t *testing.T) {
opt, ok := got.(*openaicompat.ProviderOptions)
require.True(t, ok)
require.NotNil(t, opt.User)
require.Equal(t, "test-user", *opt.User)
assert.Empty(t, cmp.Diff("test-user", *opt.User))
require.NotNil(t, opt.ReasoningEffort)
require.Equal(t, openai.ReasoningEffortHigh, *opt.ReasoningEffort)
assert.Empty(t, cmp.Diff(openai.ReasoningEffortHigh, *opt.ReasoningEffort))
}
func TestProviderRegistry_MultiProvider(t *testing.T) {
@ -301,14 +303,14 @@ func TestProviderRegistry_MultiProvider(t *testing.T) {
require.True(t, ok)
openaiData, ok := openaiOpt.(*openai.ProviderOptions)
require.True(t, ok)
require.Equal(t, "user1", *openaiData.User)
assert.Empty(t, cmp.Diff("user1", *openaiData.User))
// Check Anthropic options
anthropicOpt, ok := decoded.ProviderOptions[anthropic.Name]
require.True(t, ok)
anthropicData, ok := anthropicOpt.(*anthropic.ProviderOptions)
require.True(t, ok)
require.Equal(t, true, *anthropicData.SendReasoning)
assert.Empty(t, cmp.Diff(true, *anthropicData.SendReasoning))
}
func TestProviderRegistry_ErrorHandling(t *testing.T) {

View file

@ -9,6 +9,8 @@ import (
"charm.land/fantasy/providers/anthropic"
"charm.land/fantasy/providers/vercel"
"charm.land/x/vcr"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -94,7 +96,7 @@ func testVercelThinkingWithSignature(t *testing.T, result *fantasy.AgentResult)
}
require.Greater(t, reasoningContentCount, 0)
require.Greater(t, signaturesCount, 0)
require.Equal(t, reasoningContentCount, signaturesCount)
assert.Empty(t, cmp.Diff(reasoningContentCount, signaturesCount))
// we also add the anthropic metadata so test that
testAnthropicThinking(t, result)
}

View file

@ -5,6 +5,7 @@ import (
"sync"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -56,9 +57,9 @@ func TestFSM_Start(t *testing.T) {
transitions := obs.Snapshot()
require.Len(t, transitions, 1)
assert.Equal(t, ReActStateInit, transitions[0].From)
assert.Equal(t, ReActStatePrepareStep, transitions[0].To)
assert.Equal(t, ReActTriggerStart, transitions[0].Trigger)
assert.Empty(t, cmp.Diff(ReActStateInit, transitions[0].From))
assert.Empty(t, cmp.Diff(ReActStatePrepareStep, transitions[0].To))
assert.Empty(t, cmp.Diff(ReActTriggerStart, transitions[0].Trigger))
}
// TestFSM_FullHappyPath drives one complete step through all states and
@ -76,8 +77,8 @@ func TestFSM_FullHappyPath(t *testing.T) {
transitions := obs.Snapshot()
// Expected: Init->PrepareStep, PrepareStep->LLM, LLM->Validate, Validate->Execute, Execute->Append, Append->Stop, Stop->Done
require.Len(t, transitions, 7)
assert.Equal(t, ReActStateInit, transitions[0].From)
assert.Equal(t, ReActStateDone, transitions[6].To)
assert.Empty(t, cmp.Diff(ReActStateInit, transitions[0].From))
assert.Empty(t, cmp.Diff(ReActStateDone, transitions[6].To))
}
// TestFSM_Continue verifies that the loop can re-enter PrepareStep after a
@ -96,10 +97,10 @@ func TestFSM_Continue(t *testing.T) {
transitions := obs.Snapshot()
// Two full loops: each has 5 states + Start + Continue + Finished = 13
assert.Equal(t, 13, len(transitions))
assert.Empty(t, cmp.Diff(13, len(transitions)))
// Second loop re-enters PrepareStep
assert.Equal(t, ReActStatePrepareStep, transitions[6].To)
assert.Empty(t, cmp.Diff(ReActStatePrepareStep, transitions[6].To))
}
// TestFSM_StopConditionMet verifies the alternative Done path.
@ -114,7 +115,7 @@ func TestFSM_StopConditionMet(t *testing.T) {
f.Fire(ctx, ReActTriggerStopConditionMet)
last := obs.Snapshot()
assert.Equal(t, ReActStateDone, last[len(last)-1].To)
assert.Empty(t, cmp.Diff(ReActStateDone, last[len(last)-1].To))
}
// TestFSM_ErrorTransition verifies the error state is reachable from any state.
@ -130,7 +131,7 @@ func TestFSM_ErrorTransition(t *testing.T) {
transitions := obs.Snapshot()
last := transitions[len(transitions)-1]
assert.Equal(t, ReActStateError, last.To)
assert.Empty(t, cmp.Diff(ReActStateError, last.To))
}
// TestFSM_RecoveredContinue verifies the error → PrepareStep recovery path.
@ -146,7 +147,7 @@ func TestFSM_RecoveredContinue(t *testing.T) {
transitions := obs.Snapshot()
last := transitions[len(transitions)-1]
assert.Equal(t, ReActStatePrepareStep, last.To)
assert.Empty(t, cmp.Diff(ReActStatePrepareStep, last.To))
}
// TestFSM_UnhandledTriggerIsPermissive verifies that firing an invalid trigger
@ -197,8 +198,8 @@ func TestFSM_StepIndex(t *testing.T) {
transitions := obs.Snapshot()
require.Len(t, transitions, 2)
assert.Equal(t, 0, transitions[0].StepIndex)
assert.Equal(t, 1, transitions[1].StepIndex)
assert.Empty(t, cmp.Diff(0, transitions[0].StepIndex))
assert.Empty(t, cmp.Diff(1, transitions[1].StepIndex))
}
// TestFSM_NilObserverSafe verifies no panic when no observer is attached.

View file

@ -4,6 +4,8 @@ import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -19,8 +21,7 @@ func TestEnumSupport(t *testing.T) {
}
schema := Generate(reflect.TypeFor[WeatherInput]())
require.Equal(t, "object", schema.Type)
assert.Empty(t, cmp.Diff("object", schema.Type))
// Check units field has enum values
unitsSchema := schema.Properties["units"]
@ -28,7 +29,7 @@ func TestEnumSupport(t *testing.T) {
require.Len(t, unitsSchema.Enum, 3)
expectedUnits := []string{"celsius", "fahrenheit", "kelvin"}
for i, expected := range expectedUnits {
require.Equal(t, expected, unitsSchema.Enum[i])
assert.Empty(t, cmp.Diff(expected, unitsSchema.Enum[i]))
}
// Check required fields (format should not be required due to omitempty)
@ -69,20 +70,20 @@ func TestSchemaToParameters(t *testing.T) {
// Check name parameter
nameParam, ok := params["name"].(map[string]any)
require.True(t, ok, "Expected name parameter to exist")
require.Equal(t, "string", nameParam["type"])
require.Equal(t, "The name field", nameParam["description"])
assert.Empty(t, cmp.Diff("string", nameParam["type"]))
assert.Empty(t, cmp.Diff("The name field", nameParam["description"]))
// Check age parameter with min/max
ageParam, ok := params["age"].(map[string]any)
require.True(t, ok, "Expected age parameter to exist")
require.Equal(t, "integer", ageParam["type"])
require.Equal(t, 0.0, ageParam["minimum"])
require.Equal(t, 120.0, ageParam["maximum"])
assert.Empty(t, cmp.Diff("integer", ageParam["type"]))
assert.Empty(t, cmp.Diff(0.0, ageParam["minimum"]))
assert.Empty(t, cmp.Diff(120.0, ageParam["maximum"]))
// Check priority parameter with enum
priorityParam, ok := params["priority"].(map[string]any)
require.True(t, ok, "Expected priority parameter to exist")
require.Equal(t, "string", priorityParam["type"])
assert.Empty(t, cmp.Diff("string", priorityParam["type"]))
enumValues, ok := priorityParam["enum"].([]any)
require.True(t, ok)
require.Len(t, enumValues, 3)
@ -137,7 +138,7 @@ func TestGenerateSchemaBasicTypes(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
schema := Generate(reflect.TypeOf(tt.input))
require.Equal(t, tt.expected.Type, schema.Type)
assert.Empty(t, cmp.Diff(tt.expected.Type, schema.Type))
})
}
}
@ -180,9 +181,9 @@ func TestGenerateSchemaArrayTypes(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
schema := Generate(reflect.TypeOf(tt.input))
require.Equal(t, tt.expected.Type, schema.Type)
assert.Empty(t, cmp.Diff(tt.expected.Type, schema.Type))
require.NotNil(t, schema.Items, "Expected items schema to exist")
require.Equal(t, tt.expected.Items.Type, schema.Items.Type)
assert.Empty(t, cmp.Diff(tt.expected.Items.Type, schema.Items.Type))
})
}
}
@ -216,7 +217,7 @@ func TestGenerateSchemaMapTypes(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
schema := Generate(reflect.TypeOf(tt.input))
require.Equal(t, tt.expected, schema.Type)
assert.Empty(t, cmp.Diff(tt.expected, schema.Type))
})
}
}
@ -253,10 +254,10 @@ func TestGenerateSchemaStructTypes(t *testing.T) {
name: "simple struct",
input: SimpleStruct{},
validate: func(t *testing.T, schema Schema) {
require.Equal(t, "object", schema.Type)
assert.Empty(t, cmp.Diff("object", schema.Type))
require.Len(t, schema.Properties, 2)
require.NotNil(t, schema.Properties["name"], "Expected name property to exist")
require.Equal(t, "The name field", schema.Properties["name"].Description)
assert.Empty(t, cmp.Diff("The name field", schema.Properties["name"].Description))
require.Len(t, schema.Required, 2)
},
},
@ -265,7 +266,7 @@ func TestGenerateSchemaStructTypes(t *testing.T) {
input: StructWithOmitEmpty{},
validate: func(t *testing.T, schema Schema) {
require.Len(t, schema.Required, 1)
require.Equal(t, "required", schema.Required[0])
assert.Empty(t, cmp.Diff("required", schema.Required[0]))
},
},
{
@ -305,14 +306,13 @@ func TestGenerateSchemaPointerTypes(t *testing.T) {
}
schema := Generate(reflect.TypeFor[StructWithPointers]())
require.Equal(t, "object", schema.Type)
assert.Empty(t, cmp.Diff("object", schema.Type))
require.NotNil(t, schema.Properties["name"], "Expected name property to exist")
require.Equal(t, "string", schema.Properties["name"].Type)
assert.Empty(t, cmp.Diff("string", schema.Properties["name"].Type))
require.NotNil(t, schema.Properties["age"], "Expected age property to exist")
require.Equal(t, "integer", schema.Properties["age"].Type)
assert.Empty(t, cmp.Diff("integer", schema.Properties["age"].Type))
}
func TestGenerateSchemaNestedStructs(t *testing.T) {
@ -329,13 +329,12 @@ func TestGenerateSchemaNestedStructs(t *testing.T) {
}
schema := Generate(reflect.TypeFor[Person]())
require.Equal(t, "object", schema.Type)
assert.Empty(t, cmp.Diff("object", schema.Type))
require.NotNil(t, schema.Properties["address"], "Expected address property to exist")
addressSchema := schema.Properties["address"]
require.Equal(t, "object", addressSchema.Type)
assert.Empty(t, cmp.Diff("object", addressSchema.Type))
require.NotNil(t, addressSchema.Properties["street"], "Expected street property in address to exist")
require.NotNil(t, addressSchema.Properties["city"], "Expected city property in address to exist")
@ -350,8 +349,7 @@ func TestGenerateSchemaRecursiveStructs(t *testing.T) {
}
schema := Generate(reflect.TypeFor[Node]())
require.Equal(t, "object", schema.Type)
assert.Empty(t, cmp.Diff("object", schema.Type))
require.NotNil(t, schema.Properties["value"], "Expected value property to exist")
@ -359,7 +357,7 @@ func TestGenerateSchemaRecursiveStructs(t *testing.T) {
// The recursive reference should be handled gracefully
nextSchema := schema.Properties["next"]
require.Equal(t, "object", nextSchema.Type)
assert.Empty(t, cmp.Diff("object", nextSchema.Type))
}
func TestGenerateSchemaWithEnumTags(t *testing.T) {
@ -379,7 +377,7 @@ func TestGenerateSchemaWithEnumTags(t *testing.T) {
require.Len(t, levelSchema.Enum, 4)
expectedLevels := []string{"debug", "info", "warn", "error"}
for i, expected := range expectedLevels {
require.Equal(t, expected, levelSchema.Enum[i])
assert.Empty(t, cmp.Diff(expected, levelSchema.Enum[i]))
}
// Check format field
@ -407,24 +405,24 @@ func TestGenerateSchemaComplexTypes(t *testing.T) {
// Check string slice
stringSliceSchema := schema.Properties["string_slice"]
require.NotNil(t, stringSliceSchema, "Expected string_slice property to exist")
require.Equal(t, "array", stringSliceSchema.Type)
require.Equal(t, "string", stringSliceSchema.Items.Type)
assert.Empty(t, cmp.Diff("array", stringSliceSchema.Type))
assert.Empty(t, cmp.Diff("string", stringSliceSchema.Items.Type))
// Check int map
intMapSchema := schema.Properties["int_map"]
require.NotNil(t, intMapSchema, "Expected int_map property to exist")
require.Equal(t, "object", intMapSchema.Type)
assert.Empty(t, cmp.Diff("object", intMapSchema.Type))
// Check nested slice
nestedSliceSchema := schema.Properties["nested_slice"]
require.NotNil(t, nestedSliceSchema, "Expected nested_slice property to exist")
require.Equal(t, "array", nestedSliceSchema.Type)
require.Equal(t, "object", nestedSliceSchema.Items.Type)
assert.Empty(t, cmp.Diff("array", nestedSliceSchema.Type))
assert.Empty(t, cmp.Diff("object", nestedSliceSchema.Items.Type))
// Check interface
interfaceSchema := schema.Properties["interface"]
require.NotNil(t, interfaceSchema, "Expected interface property to exist")
require.Equal(t, "object", interfaceSchema.Type)
assert.Empty(t, cmp.Diff("object", interfaceSchema.Type))
}
func TestToSnakeCase(t *testing.T) {
@ -449,7 +447,7 @@ func TestToSnakeCase(t *testing.T) {
t.Run(tt.input, func(t *testing.T) {
t.Parallel()
result := toSnakeCase(tt.input)
require.Equal(t, tt.expected, result, "toSnakeCase(%s)", tt.input)
assert.Empty(t, cmp.Diff(tt.expected, result), "toSnakeCase(%s)", tt.input)
})
}
}
@ -530,7 +528,7 @@ func TestSchemaToParametersEdgeCases(t *testing.T) {
resultParam := result[key].(map[string]any)
expectedParam := expectedValue.(map[string]any)
for propKey, propValue := range expectedParam {
require.Equal(t, propValue, resultParam[propKey], "Expected %s.%s", key, propKey)
assert.Empty(t, cmp.Diff(propValue, resultParam[propKey]), "Expected %s.%s", key, propKey)
}
}
})
@ -564,7 +562,7 @@ func TestNormalize_TypeArray(t *testing.T) {
require.Contains(t, variant, "items")
}
}
require.Equal(t, "Config value", val["description"])
assert.Empty(t, cmp.Diff("Config value", val["description"]))
}
func TestNormalize_SingleStringType(t *testing.T) {
@ -580,7 +578,7 @@ func TestNormalize_SingleStringType(t *testing.T) {
Normalize(node)
val := node["properties"].(map[string]any)["name"].(map[string]any)
require.Equal(t, "string", val["type"])
assert.Empty(t, cmp.Diff("string", val["type"]))
}
func TestNormalize_BareArrayGetsItems(t *testing.T) {
@ -596,7 +594,7 @@ func TestNormalize_BareArrayGetsItems(t *testing.T) {
Normalize(node)
val := node["properties"].(map[string]any)["tags"].(map[string]any)
require.Equal(t, "array", val["type"])
assert.Empty(t, cmp.Diff("array", val["type"]))
require.Contains(t, val, "items")
}
@ -617,7 +615,7 @@ func TestNormalize_SingleElementTypeArray(t *testing.T) {
anyOf, ok := val["anyOf"].([]any)
require.True(t, ok)
require.Len(t, anyOf, 1)
require.Equal(t, "string", anyOf[0].(map[string]any)["type"])
assert.Empty(t, cmp.Diff("string", anyOf[0].(map[string]any)["type"]))
}
func TestNormalize_NestedProperties(t *testing.T) {

View file

@ -8,6 +8,8 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -119,10 +121,11 @@ func TestDAGToolRuntime_DependenciesWaitAndInputIsResolved(t *testing.T) {
require.NoError(t, err)
require.Len(t, res, 2)
assert.
// B should have received val=1 and returned "1".
require.Equal(t, "callB", res[1].ToolCallID)
require.Equal(t, "1", res[1].Result.(ToolResultOutputContentText).Text)
// B should have received val=1 and returned "1".
Empty(t, cmp.Diff("callB", res[1].ToolCallID))
assert.Empty(t, cmp.Diff("1", res[1].Result.(ToolResultOutputContentText).Text))
}
func TestDAGToolRuntime_CycleDetected(t *testing.T) {
@ -178,7 +181,7 @@ func TestDAGToolRuntime_OnToolResultSerialized(t *testing.T) {
orderMu.Lock()
defer orderMu.Unlock()
require.Equal(t, []string{"a", "b"}, order)
assert.Empty(t, cmp.Diff([]string{"a", "b"}, order))
}
func TestDAGToolRuntime_MetricsAndLogHooks(t *testing.T) {

View file

@ -7,6 +7,8 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -49,16 +51,15 @@ func TestParallelToolRuntime_OrderAndCallbackDeterminism(t *testing.T) {
results, err := runtime.Execute(t.Context(), []AgentTool{tool}, toolCalls, cb)
require.NoError(t, err)
require.Len(t, results, 3)
require.Equal(t, "c1", results[0].ToolCallID)
require.Equal(t, "a", results[0].Result.(ToolResultOutputContentText).Text)
require.Equal(t, "c2", results[1].ToolCallID)
require.Equal(t, "b", results[1].Result.(ToolResultOutputContentText).Text)
require.Equal(t, "c3", results[2].ToolCallID)
require.Equal(t, "c", results[2].Result.(ToolResultOutputContentText).Text)
assert.Empty(t, cmp.Diff("c1", results[0].ToolCallID))
assert.Empty(t, cmp.Diff("a", results[0].Result.(ToolResultOutputContentText).Text))
assert.Empty(t, cmp.Diff("c2", results[1].ToolCallID))
assert.Empty(t, cmp.Diff("b", results[1].Result.(ToolResultOutputContentText).Text))
assert.Empty(t, cmp.Diff("c3", results[2].ToolCallID))
assert.Empty(t, cmp.Diff("c", results[2].Result.(ToolResultOutputContentText).Text))
cbMu.Lock()
require.Equal(t, []string{"c1", "c2", "c3"}, cbOrder)
assert.Empty(t, cmp.Diff([]string{"c1", "c2", "c3"}, cbOrder))
cbMu.Unlock()
}
@ -97,7 +98,7 @@ func TestParallelToolRuntime_BarrierForNonParallelTools(t *testing.T) {
require.Len(t, results, 4)
mu.Lock()
require.Equal(t, []string{"p1", "p2", "s1", "p3"}, order)
assert.Empty(t, cmp.Diff([]string{"p1", "p2", "s1", "p3"}, order))
mu.Unlock()
}

View file

@ -6,6 +6,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -30,9 +31,9 @@ func TestTypedToolFuncExample(t *testing.T) {
// Check the tool info
info := tool.Info()
require.Equal(t, "calculator", info.Name)
assert.Empty(t, cmp.Diff("calculator", info.Name))
require.Len(t, info.Required, 1)
require.Equal(t, "expression", info.Required[0])
assert.Empty(t, cmp.Diff("expression", info.Required[0]))
// Test execution
call := ToolCall{
@ -43,7 +44,7 @@ func TestTypedToolFuncExample(t *testing.T) {
result, err := tool.Run(t.Context(), call)
require.NoError(t, err)
require.Equal(t, "4", result.Content)
assert.Empty(t, cmp.Diff("4", result.Content))
require.False(t, result.IsError)
}

View file

@ -3,6 +3,7 @@ package agent_test
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -24,7 +25,7 @@ func TestDelegateKV_PutAndGet(t *testing.T) {
got, err := kv.Get(ctx, "key1")
require.NoError(t, err)
assert.Equal(t, []byte("hello world"), got)
assert.Empty(t, cmp.Diff([]byte("hello world"), got))
}
func TestDelegateKV_GetMissingKey(t *testing.T) {
@ -47,7 +48,7 @@ func TestDelegateKV_PutOverwrite(t *testing.T) {
got, err := kv.Get(ctx, "k")
require.NoError(t, err)
assert.Equal(t, []byte("v2"), got, "second put should overwrite the first")
assert.Empty(t, cmp.Diff([]byte("v2"), got), "second put should overwrite the first")
}
func TestDelegateKV_BinaryValues(t *testing.T) {
@ -61,7 +62,7 @@ func TestDelegateKV_BinaryValues(t *testing.T) {
got, err := kv.Get(ctx, "binary-key")
require.NoError(t, err)
assert.Equal(t, data, got, "binary round-trip must be lossless")
assert.Empty(t, cmp.Diff(data, got), "binary round-trip must be lossless")
}
func TestDelegateKV_Scan(t *testing.T) {
@ -101,7 +102,7 @@ func TestDelegateKV_ScanSorted(t *testing.T) {
got, err := kv.Scan(ctx, "z/")
require.NoError(t, err)
assert.Equal(t, []string{"z/a", "z/b", "z/c"}, got, "Scan must return keys sorted")
assert.Empty(t, cmp.Diff([]string{"z/a", "z/b", "z/c"}, got), "Scan must return keys sorted")
}
func TestDelegateKV_AgentIsolation(t *testing.T) {
@ -116,11 +117,11 @@ func TestDelegateKV_AgentIsolation(t *testing.T) {
v1, err := kv1.Get(ctx, "shared-key")
require.NoError(t, err)
assert.Equal(t, []byte("from-A"), v1)
assert.Empty(t, cmp.Diff([]byte("from-A"), v1))
v2, err := kv2.Get(ctx, "shared-key")
require.NoError(t, err)
assert.Equal(t, []byte("from-B"), v2)
assert.Empty(t, cmp.Diff([]byte("from-B"), v2))
}
func TestDelegateKV_EmptyKey_PutErrors(t *testing.T) {

View file

@ -6,6 +6,7 @@ import (
"testing"
"charm.land/fantasy"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -85,7 +86,7 @@ func TestOffloading_SmallResult_KeptInline(t *testing.T) {
require.NoError(t, err)
require.Len(t, results, 1)
assert.Equal(t, "echo:call-a", results[0].Result.(fantasy.ToolResultOutputContentText).Text)
assert.Empty(t, cmp.Diff("echo:call-a", results[0].Result.(fantasy.ToolResultOutputContentText).Text))
// KV should still have the full result stored
keys, err := kv.Scan(ctx, "tool_results/")
@ -146,7 +147,7 @@ func TestOffloading_LargeResult_Truncated(t *testing.T) {
require.NoError(t, err)
full.Write(part)
}
assert.Equal(t, longText, full.String(), "reassembled chunks must equal original text")
assert.Empty(t, cmp.Diff(longText, full.String()), "reassembled chunks must equal original text")
}
// TestOffloading_DBMetadata_Inserted verifies that the DB record is created.
@ -290,7 +291,7 @@ func TestChunkString(t *testing.T) {
}
if tt.chunkSize > 0 && len(tt.input) > effectiveThreshold {
assert.Equal(t, tt.wantLen, chunkCount)
assert.Empty(t, cmp.Diff(tt.wantLen, chunkCount))
}
})
}

View file

@ -204,12 +204,12 @@ func TestCheckpointStore_CreateAndList(t *testing.T) {
require.NoError(t, err)
assert.False(t, cp.ID.IsZero())
assert.Equal(t, "after-step-0", cp.Name)
assert.Empty(t, cmp.Diff("after-step-0", cp.Name))
cps, err := cs.ListCheckpoints(ctx, convID)
require.NoError(t, err)
assert.Len(t, cps, 1)
assert.Equal(t, cp.ID, cps[0].ID)
assert.Empty(t, cmp.Diff(cp.ID, cps[0].ID))
}
func TestCheckpointStore_GetByName(t *testing.T) {
@ -232,7 +232,7 @@ func TestCheckpointStore_GetByName(t *testing.T) {
cp, err := cs.GetCheckpoint(ctx, convID, "snap-1")
require.NoError(t, err)
assert.Equal(t, "snap-1", cp.Name)
assert.Empty(t, cmp.Diff("snap-1", cp.Name))
}
func TestCheckpointStore_EmptyName(t *testing.T) {

View file

@ -7,6 +7,7 @@ import (
jsonv2 "github.com/go-json-experiment/json"
"charm.land/fantasy"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -149,12 +150,12 @@ func TestToolResultSearch_QueryFilters(t *testing.T) {
out := invokeSearch(t, tool, input)
total, _ := out["total"].(float64)
assert.Equal(t, tt.wantTotal, total)
assert.Empty(t, cmp.Diff(tt.wantTotal, total))
if tt.wantTCID != "" {
items := out["items"].([]any)
item := items[0].(map[string]any)
assert.Equal(t, tt.wantTCID, item["tool_call_id"])
assert.Empty(t, cmp.Diff(tt.wantTCID, item["tool_call_id"]))
}
})
}
@ -229,7 +230,7 @@ func TestToolResultSearch_LineView(t *testing.T) {
// The view_range reflects the actual clamped range (1 JSON line available).
viewRange := item["view_range"].(map[string]any)
assert.Equal(t, float64(1), viewRange["start_line"], "clamped to 1 since full.json is a single-line JSON blob")
assert.Empty(t, cmp.Diff(float64(1), viewRange["start_line"]), "clamped to 1 since full.json is a single-line JSON blob")
}
// TestToolResultSearch_ChunkView verifies chunk-range retrieval from KV.
@ -282,5 +283,5 @@ func TestToolResultSearch_ChunkView(t *testing.T) {
view := item["view"].(string)
// First two chunks: "abcdefghijabcdefghij" + "abcdefghijabcdefghij" = 40 chars
assert.Equal(t, strings.Repeat("abcdefghij", 4), view, "chunk 0+1 should be first 40 chars")
assert.Empty(t, cmp.Diff(strings.Repeat("abcdefghij", 4), view), "chunk 0+1 should be first 40 chars")
}

View file

@ -87,8 +87,8 @@ func TestToolExecPayloadPreservation(t *testing.T) {
payload, ok := decoded.Payload.(ToolExec)
require.True(t, ok, "payload should be ToolExec")
assert.Equal(t, "shell", payload.ToolName)
assert.Equal(t, `{"cmd":"ls -la"}`, payload.ArgsJSON)
assert.Empty(t, cmp.Diff("shell", payload.ToolName))
assert.Empty(t, cmp.Diff(`{"cmd":"ls -la"}`, payload.ArgsJSON))
}
func TestGrepPayloadPreservation(t *testing.T) {
@ -102,8 +102,8 @@ func TestGrepPayloadPreservation(t *testing.T) {
payload, ok := decoded.Payload.(Grep)
require.True(t, ok)
assert.Equal(t, "error.*fatal", payload.Pattern)
assert.Equal(t, uint32(25), payload.MaxMatches)
assert.Empty(t, cmp.Diff("error.*fatal", payload.Pattern))
assert.Empty(t, cmp.Diff(uint32(25), payload.MaxMatches))
assert.True(t, payload.CaseInsensitive)
}
@ -127,20 +127,20 @@ func TestDAGPlanPayloadPreservation(t *testing.T) {
dagPlan, ok := decoded.Payload.(DAGPlan)
require.True(t, ok)
assert.Len(t, dagPlan.Nodes, 2)
assert.Equal(t, "a", dagPlan.Nodes[0].ID)
assert.Equal(t, CmdToolSearch, dagPlan.Nodes[0].Type)
assert.Equal(t, []string{"a"}, dagPlan.Nodes[1].DependsOn)
assert.Equal(t, uint8(2), dagPlan.MaxParallel)
assert.Equal(t, "combine results", dagPlan.JoinerQuery)
assert.Empty(t, cmp.Diff("a", dagPlan.Nodes[0].ID))
assert.Empty(t, cmp.Diff(CmdToolSearch, dagPlan.Nodes[0].Type))
assert.Empty(t, cmp.Diff([]string{"a"}, dagPlan.Nodes[1].DependsOn))
assert.Empty(t, cmp.Diff(uint8(2), dagPlan.MaxParallel))
assert.Empty(t, cmp.Diff("combine results", dagPlan.JoinerQuery))
ts, ok := dagPlan.Nodes[0].Payload.(ToolSearch)
require.True(t, ok, "ToolSearch payload should be retyped after unmarshal")
assert.Equal(t, "files", ts.Query)
assert.Equal(t, uint8(5), ts.MaxResults)
assert.Empty(t, cmp.Diff("files", ts.Query))
assert.Empty(t, cmp.Diff(uint8(5), ts.MaxResults))
te, ok := dagPlan.Nodes[1].Payload.(ToolExec)
require.True(t, ok, "ToolExec payload should be retyped after unmarshal")
assert.Equal(t, "read", te.ToolName)
assert.Empty(t, cmp.Diff("read", te.ToolName))
assert.Contains(t, te.ArgsJSON, "#nodea")
}
@ -214,9 +214,9 @@ func TestRequestJSON_Roundtrip(t *testing.T) {
decoded, err := UnmarshalRequestJSON(data)
require.NoError(t, err)
assert.Equal(t, orig.ID, decoded.ID)
assert.Empty(t, cmp.Diff(orig.ID, decoded.ID))
p := decoded.Payload.(ToolExec)
assert.Equal(t, "shell", p.ToolName)
assert.Empty(t, cmp.Diff("shell", p.ToolName))
}
func TestResponseJSON_Roundtrip(t *testing.T) {
@ -227,6 +227,6 @@ func TestResponseJSON_Roundtrip(t *testing.T) {
decoded, err := UnmarshalResponseJSON(data)
require.NoError(t, err)
assert.Equal(t, orig.ID, decoded.ID)
assert.Equal(t, "ok", decoded.Result)
assert.Empty(t, cmp.Diff(orig.ID, decoded.ID))
assert.Empty(t, cmp.Diff("ok", decoded.Result))
}

View file

@ -11,6 +11,7 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg/itr/dag"
"github.com/ZanzyTHEbar/dragonscale/pkg/security/securebus"
"github.com/ZanzyTHEbar/dragonscale/pkg/tools"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -95,8 +96,8 @@ func TestExecutor_ParallelNodes(t *testing.T) {
result, err := executor.Execute(t.Context(), "test-sess", plan)
require.NoError(t, err)
assert.Equal(t, "a-result", result.NodeResults["n1"])
assert.Equal(t, "b-result", result.NodeResults["n2"])
assert.Empty(t, cmp.Diff("a-result", result.NodeResults["n1"]))
assert.Empty(t, cmp.Diff("b-result", result.NodeResults["n2"]))
}
func TestExecutor_CycleDetection(t *testing.T) {
@ -148,7 +149,7 @@ func TestExecutor_WithJoiner(t *testing.T) {
result, err := executor.Execute(t.Context(), "test-sess", plan)
require.NoError(t, err)
assert.Contains(t, result.FinalAnswer, "synthesized:")
assert.Equal(t, uint32(50), result.TotalTokens)
assert.Empty(t, cmp.Diff(uint32(50), result.TotalTokens))
}
func TestExecutor_EmptyPlan(t *testing.T) {
@ -192,21 +193,21 @@ func TestRouter_SimpleQuerySelectsReAct(t *testing.T) {
t.Parallel()
cfg := dag.DefaultRouterConfig()
mode := dag.Route(dag.ModeAuto, "What is the weather?", cfg)
assert.Equal(t, dag.ModeReAct, mode)
assert.Empty(t, cmp.Diff(dag.ModeReAct, mode))
}
func TestRouter_ComplexQuerySelectsDAG(t *testing.T) {
t.Parallel()
cfg := dag.DefaultRouterConfig()
mode := dag.Route(dag.ModeAuto, "Search for the latest news about AI, read the top 3 articles, and compare their viewpoints to create a summary report with aggregate statistics", cfg)
assert.Equal(t, dag.ModeDAG, mode)
assert.Empty(t, cmp.Diff(dag.ModeDAG, mode))
}
func TestRouter_ExplicitModeOverridesAuto(t *testing.T) {
t.Parallel()
cfg := dag.DefaultRouterConfig()
mode := dag.Route(dag.ModeReAct, "Do many complex parallel things simultaneously", cfg)
assert.Equal(t, dag.ModeReAct, mode)
assert.Empty(t, cmp.Diff(dag.ModeReAct, mode))
}
func TestPlanner_ValidatePlan(t *testing.T) {

View file

@ -7,6 +7,7 @@ import (
jsonv2 "github.com/go-json-experiment/json"
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -14,34 +15,34 @@ import (
func TestExtractJSON_PlainJSON(t *testing.T) {
t.Parallel()
input := `{"nodes": [{"id": "n1"}]}`
assert.Equal(t, input, extractJSON(input))
assert.Empty(t, cmp.Diff(input, extractJSON(input)))
}
func TestExtractJSON_MarkdownFenced(t *testing.T) {
t.Parallel()
input := "Here is the plan:\n```json\n{\"nodes\": [{\"id\": \"n1\"}]}\n```\nDone."
assert.Equal(t, `{"nodes": [{"id": "n1"}]}`, extractJSON(input))
assert.Empty(t, cmp.Diff(`{"nodes": [{"id": "n1"}]}`, extractJSON(input)))
}
func TestExtractJSON_GenericFenced(t *testing.T) {
t.Parallel()
input := "```\n{\"nodes\": []}\n```"
assert.Equal(t, `{"nodes": []}`, extractJSON(input))
assert.Empty(t, cmp.Diff(`{"nodes": []}`, extractJSON(input)))
}
func TestExtractJSON_LeadingText(t *testing.T) {
t.Parallel()
input := "The plan is: {\"nodes\":[]}"
assert.Equal(t, `{"nodes":[]}`, extractJSON(input))
assert.Empty(t, cmp.Diff(`{"nodes":[]}`, extractJSON(input)))
}
func TestFindIndex(t *testing.T) {
t.Parallel()
assert.Equal(t, 0, findIndex("abc", "a"))
assert.Equal(t, 2, findIndex("abc", "c"))
assert.Equal(t, -1, findIndex("abc", "z"))
assert.Equal(t, -1, findIndex("", "a"))
assert.Equal(t, -1, findIndex("ab", "abc"))
assert.Empty(t, cmp.Diff(0, findIndex("abc", "a")))
assert.Empty(t, cmp.Diff(2, findIndex("abc", "c")))
assert.Empty(t, cmp.Diff(-1, findIndex("abc", "z")))
assert.Empty(t, cmp.Diff(-1, findIndex("", "a")))
assert.Empty(t, cmp.Diff(-1, findIndex("ab", "abc")))
}
func TestValidatePlan_Valid(t *testing.T) {
@ -135,13 +136,13 @@ func TestParsePlanResponse_ValidJSON(t *testing.T) {
plan, err := parsePlanResponse(input)
require.NoError(t, err)
require.Len(t, plan.Nodes, 1)
assert.Equal(t, "n1", plan.Nodes[0].ID)
assert.Equal(t, itr.CmdToolExec, plan.Nodes[0].Type)
assert.Equal(t, "summarize", plan.JoinerQuery)
assert.Empty(t, cmp.Diff("n1", plan.Nodes[0].ID))
assert.Empty(t, cmp.Diff(itr.CmdToolExec, plan.Nodes[0].Type))
assert.Empty(t, cmp.Diff("summarize", plan.JoinerQuery))
te, ok := plan.Nodes[0].Payload.(itr.ToolExec)
require.True(t, ok)
assert.Equal(t, "read_file", te.ToolName)
assert.Empty(t, cmp.Diff("read_file", te.ToolName))
}
func TestParsePlanResponse_WithMarkdownFence(t *testing.T) {
@ -154,7 +155,7 @@ func TestParsePlanResponse_WithMarkdownFence(t *testing.T) {
ts, ok := plan.Nodes[0].Payload.(itr.ToolSearch)
require.True(t, ok)
assert.Equal(t, "files", ts.Query)
assert.Empty(t, cmp.Diff("files", ts.Query))
}
func TestParsePlanResponse_InvalidJSON(t *testing.T) {
@ -183,10 +184,10 @@ func TestPlannerPlanE2E(t *testing.T) {
planner := NewPlanner(mockLLM, nil, DefaultPlannerConfig())
plan, tokens, err := planner.Plan(t.Context(), "search and read", nil)
require.NoError(t, err)
assert.Equal(t, uint32(100), tokens)
assert.Empty(t, cmp.Diff(uint32(100), tokens))
require.Len(t, plan.Nodes, 2)
assert.Equal(t, "search", plan.Nodes[0].ID)
assert.Equal(t, uint8(8), plan.MaxParallel)
assert.Empty(t, cmp.Diff("search", plan.Nodes[0].ID))
assert.Empty(t, cmp.Diff(uint8(8), plan.MaxParallel))
}
func TestPlannerPlanLLMError(t *testing.T) {
@ -198,5 +199,5 @@ func TestPlannerPlanLLMError(t *testing.T) {
planner := NewPlanner(mockLLM, nil, DefaultPlannerConfig())
_, tokens, err := planner.Plan(t.Context(), "anything", nil)
assert.Error(t, err)
assert.Equal(t, uint32(50), tokens)
assert.Empty(t, cmp.Diff(uint32(50), tokens))
}

View file

@ -3,6 +3,7 @@ package dag
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
@ -16,5 +17,5 @@ func TestNeedsReplan(t *testing.T) {
func TestReplanSentinelIsConsistent(t *testing.T) {
t.Parallel()
assert.Equal(t, "[NEEDS_MORE_STEPS]", replanSentinel)
assert.Empty(t, cmp.Diff("[NEEDS_MORE_STEPS]", replanSentinel))
}

View file

@ -3,6 +3,7 @@ package dag
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -18,9 +19,9 @@ func TestTopologicalOrderLinear(t *testing.T) {
waves, err := topologicalOrder(states)
require.NoError(t, err)
require.Len(t, waves, 3)
assert.Equal(t, []string{"a"}, waves[0])
assert.Equal(t, []string{"b"}, waves[1])
assert.Equal(t, []string{"c"}, waves[2])
assert.Empty(t, cmp.Diff([]string{"a"}, waves[0]))
assert.Empty(t, cmp.Diff([]string{"b"}, waves[1]))
assert.Empty(t, cmp.Diff([]string{"c"}, waves[2]))
}
func TestTopologicalOrderParallel(t *testing.T) {
@ -38,7 +39,7 @@ func TestTopologicalOrderParallel(t *testing.T) {
assert.Len(t, waves[0], 2)
assert.Contains(t, waves[0], "a")
assert.Contains(t, waves[0], "b")
assert.Equal(t, []string{"c"}, waves[1])
assert.Empty(t, cmp.Diff([]string{"c"}, waves[1]))
}
func TestTopologicalOrderCycleDetection(t *testing.T) {
@ -63,7 +64,7 @@ func TestTopologicalOrderSingleNode(t *testing.T) {
waves, err := topologicalOrder(states)
require.NoError(t, err)
require.Len(t, waves, 1)
assert.Equal(t, []string{"only"}, waves[0])
assert.Empty(t, cmp.Diff([]string{"only"}, waves[0]))
}
func TestResolveRefs(t *testing.T) {
@ -84,7 +85,7 @@ func TestResolveRefsNoMatch(t *testing.T) {
states := map[string]*nodeState{}
input := `{"path":"#nodemissing"}`
result := resolveRefs(input, states)
assert.Equal(t, input, result)
assert.Empty(t, cmp.Diff(input, result))
}
func TestResolveToolExecArgsNoRefs(t *testing.T) {
@ -92,7 +93,7 @@ func TestResolveToolExecArgsNoRefs(t *testing.T) {
states := map[string]*nodeState{}
input := `{"path":"/tmp/plain.txt"}`
result := resolveToolExecArgs(input, states)
assert.Equal(t, input, result)
assert.Empty(t, cmp.Diff(input, result))
}
func TestEscapeForJSON(t *testing.T) {
@ -108,7 +109,7 @@ func TestEscapeForJSON(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
assert.Equal(t, tt.expected, escapeForJSON(tt.input))
assert.Empty(t, cmp.Diff(tt.expected, escapeForJSON(tt.input)))
})
}
}
@ -125,5 +126,5 @@ func TestNodeStateSetAndGetResult(t *testing.T) {
result, err := ns.getResult()
assert.NoError(t, err)
assert.Equal(t, "result-data", result)
assert.Empty(t, cmp.Diff("result-data", result))
}

View file

@ -4,6 +4,7 @@ import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
@ -11,21 +12,21 @@ func TestRouteExplicitModes(t *testing.T) {
t.Parallel()
cfg := DefaultRouterConfig()
assert.Equal(t, ModeReAct, Route(ModeReAct, "anything", cfg))
assert.Equal(t, ModeDAG, Route(ModeDAG, "anything", cfg))
assert.Empty(t, cmp.Diff(ModeReAct, Route(ModeReAct, "anything", cfg)))
assert.Empty(t, cmp.Diff(ModeDAG, Route(ModeDAG, "anything", cfg)))
}
func TestRouteAutoSimpleQuery(t *testing.T) {
t.Parallel()
cfg := DefaultRouterConfig()
assert.Equal(t, ModeReAct, Route(ModeAuto, "what is the weather?", cfg))
assert.Empty(t, cmp.Diff(ModeReAct, Route(ModeAuto, "what is the weather?", cfg)))
}
func TestRouteAutoComplexQuery(t *testing.T) {
t.Parallel()
cfg := DefaultRouterConfig()
longQuery := strings.Repeat("word ", 35)
assert.Equal(t, ModeDAG, Route(ModeAuto, longQuery, cfg))
assert.Empty(t, cmp.Diff(ModeDAG, Route(ModeAuto, longQuery, cfg)))
}
func TestRouteAutoParallelKeywords(t *testing.T) {
@ -40,7 +41,7 @@ func TestRouteAutoParallelKeywords(t *testing.T) {
}
for _, q := range keywords {
t.Run(q, func(t *testing.T) {
assert.Equal(t, ModeDAG, Route(ModeAuto, q, cfg))
assert.Empty(t, cmp.Diff(ModeDAG, Route(ModeAuto, q, cfg)))
})
}
}
@ -49,19 +50,19 @@ func TestRouteAutoToolSignals(t *testing.T) {
t.Parallel()
cfg := DefaultRouterConfig()
q := "search the codebase, read the file, then execute the command"
assert.Equal(t, ModeDAG, Route(ModeAuto, q, cfg))
assert.Empty(t, cmp.Diff(ModeDAG, Route(ModeAuto, q, cfg)))
}
func TestToolLoopModeString(t *testing.T) {
t.Parallel()
assert.Equal(t, "react", ModeReAct.String())
assert.Equal(t, "dag", ModeDAG.String())
assert.Equal(t, "auto", ModeAuto.String())
assert.Equal(t, "unknown", ToolLoopMode(99).String())
assert.Empty(t, cmp.Diff("react", ModeReAct.String()))
assert.Empty(t, cmp.Diff("dag", ModeDAG.String()))
assert.Empty(t, cmp.Diff("auto", ModeAuto.String()))
assert.Empty(t, cmp.Diff("unknown", ToolLoopMode(99).String()))
}
func TestClassifyQueryDefault(t *testing.T) {
t.Parallel()
cfg := DefaultRouterConfig()
assert.Equal(t, ModeReAct, classifyQuery("hello", cfg))
assert.Empty(t, cmp.Diff(ModeReAct, classifyQuery("hello", cfg)))
}

View file

@ -4,6 +4,7 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -62,7 +63,7 @@ func TestExecuteMinimalModule(t *testing.T) {
result, err := rt.Execute(ctx, minimalWASM, "")
require.NoError(t, err)
assert.Equal(t, uint32(0), result.ExitCode)
assert.Empty(t, cmp.Diff(uint32(0), result.ExitCode))
assert.Empty(t, result.Stderr)
assert.True(t, result.Duration > 0)
}
@ -80,7 +81,7 @@ func TestExecuteTimeout(t *testing.T) {
// Minimal module is fast enough to succeed even with 1ms timeout.
// This test validates that the timeout machinery doesn't break normal execution.
if err == nil {
assert.Equal(t, uint32(0), result.ExitCode)
assert.Empty(t, cmp.Diff(uint32(0), result.ExitCode))
}
}
@ -123,8 +124,8 @@ func TestLimitedBuffer(t *testing.T) {
lb := &limitedBuffer{max: 5}
n, err := lb.Write([]byte("hello world"))
assert.NoError(t, err)
assert.Equal(t, 11, n)
assert.Equal(t, "hello", lb.String())
assert.Empty(t, cmp.Diff(11, n))
assert.Empty(t, cmp.Diff("hello", lb.String()))
}
func TestLimitedBufferExactFit(t *testing.T) {
@ -132,11 +133,11 @@ func TestLimitedBufferExactFit(t *testing.T) {
lb := &limitedBuffer{max: 5}
n, err := lb.Write([]byte("hello"))
assert.NoError(t, err)
assert.Equal(t, 5, n)
assert.Equal(t, "hello", lb.String())
assert.Empty(t, cmp.Diff(5, n))
assert.Empty(t, cmp.Diff("hello", lb.String()))
n, err = lb.Write([]byte("more"))
assert.NoError(t, err)
assert.Equal(t, 4, n)
assert.Equal(t, "hello", lb.String())
assert.Empty(t, cmp.Diff(4, n))
assert.Empty(t, cmp.Diff("hello", lb.String()))
}

View file

@ -5,6 +5,7 @@ import (
"testing"
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -25,7 +26,7 @@ func TestTransportNonCodeExecForwarded(t *testing.T) {
resp, err := transport.Send(t.Context(), req)
require.NoError(t, err)
assert.True(t, forwarded)
assert.Equal(t, "forwarded", resp.Result)
assert.Empty(t, cmp.Diff("forwarded", resp.Result))
assert.False(t, resp.IsError)
}

View file

@ -6,6 +6,7 @@ import (
"time"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -50,15 +51,15 @@ func TestBackfillMissingSessionDAGs_CreatesSnapshotsAndPersistsStatus(t *testing
status, err := dag.BackfillMissingSessionDAGs(ctx, d, d.Queries(), agentID, dag.DefaultBackfillOptions())
require.NoError(t, err)
require.NotNil(t, status)
assert.Equal(t, 1, status.SnapshotsCreated)
assert.Equal(t, 0, status.Failures)
assert.Empty(t, cmp.Diff(1, status.SnapshotsCreated))
assert.Empty(t, cmp.Diff(0, status.Failures))
row, err := d.Queries().GetLatestDAGSnapshotBySession(ctx, memsqlc.GetLatestDAGSnapshotBySessionParams{
AgentID: agentID,
SessionKey: sessionKey,
})
require.NoError(t, err)
assert.Equal(t, int64(12), row.MsgCount)
assert.Empty(t, cmp.Diff(int64(12), row.MsgCount))
kv, err := d.ListKVByPrefix(ctx, agentID, "migration:dag_backfill", 10)
require.NoError(t, err)
@ -69,7 +70,7 @@ func TestBackfillMissingSessionDAGs_CreatesSnapshotsAndPersistsStatus(t *testing
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &stored))
break
}
assert.Equal(t, status.SnapshotsCreated, stored.SnapshotsCreated)
assert.Empty(t, cmp.Diff(status.SnapshotsCreated, stored.SnapshotsCreated))
assert.False(t, stored.CompletedAt.IsZero())
status2, err := dag.BackfillMissingSessionDAGs(ctx, d, d.Queries(), agentID, dag.DefaultBackfillOptions())

View file

@ -45,8 +45,8 @@ func TestCompressor_SmallInput(t *testing.T) {
require.Len(t, d.Nodes, 1)
chunk := d.NodesAtLevel(LevelChunk)
require.Len(t, chunk, 1)
assert.Equal(t, 0, chunk[0].StartIdx)
assert.Equal(t, 2, chunk[0].EndIdx)
assert.Empty(t, cmp.Diff(0, chunk[0].StartIdx))
assert.Empty(t, cmp.Diff(2, chunk[0].EndIdx))
assert.Contains(t, chunk[0].Summary, "user:")
assert.Contains(t, chunk[0].Summary, "assistant:")
}
@ -63,12 +63,12 @@ func TestCompressor_ChunkSplitting(t *testing.T) {
chunks := d.NodesAtLevel(LevelChunk)
require.Len(t, chunks, 3)
assert.Equal(t, 0, chunks[0].StartIdx)
assert.Equal(t, 4, chunks[0].EndIdx)
assert.Equal(t, 4, chunks[1].StartIdx)
assert.Equal(t, 8, chunks[1].EndIdx)
assert.Equal(t, 8, chunks[2].StartIdx)
assert.Equal(t, 12, chunks[2].EndIdx)
assert.Empty(t, cmp.Diff(0, chunks[0].StartIdx))
assert.Empty(t, cmp.Diff(4, chunks[0].EndIdx))
assert.Empty(t, cmp.Diff(4, chunks[1].StartIdx))
assert.Empty(t, cmp.Diff(8, chunks[1].EndIdx))
assert.Empty(t, cmp.Diff(8, chunks[2].StartIdx))
assert.Empty(t, cmp.Diff(12, chunks[2].EndIdx))
}
func TestCompressor_SectionBuilding(t *testing.T) {
@ -89,8 +89,8 @@ func TestCompressor_SectionBuilding(t *testing.T) {
assert.Len(t, sections, 3)
// First section covers chunks 0-1 (msgs 0-7)
assert.Equal(t, 0, sections[0].StartIdx)
assert.Equal(t, 8, sections[0].EndIdx)
assert.Empty(t, cmp.Diff(0, sections[0].StartIdx))
assert.Empty(t, cmp.Diff(8, sections[0].EndIdx))
assert.Len(t, sections[0].Children, 2)
}
@ -107,12 +107,12 @@ func TestCompressor_SessionSummary(t *testing.T) {
sessions := d.NodesAtLevel(LevelSession)
require.Len(t, sessions, 1)
assert.Equal(t, 0, sessions[0].StartIdx)
assert.Equal(t, 24, sessions[0].EndIdx)
assert.Empty(t, cmp.Diff(0, sessions[0].StartIdx))
assert.Empty(t, cmp.Diff(24, sessions[0].EndIdx))
assert.Len(t, sessions[0].Children, 3)
require.Len(t, d.Roots, 1)
assert.Equal(t, sessions[0].ID, d.Roots[0])
assert.Empty(t, cmp.Diff(sessions[0].ID, d.Roots[0]))
}
func TestExtractSentences(t *testing.T) {
@ -135,7 +135,7 @@ func TestExtractSentences(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := extractSentences(tt.text, tt.n)
assert.Equal(t, tt.want, got)
assert.Empty(t, cmp.Diff(tt.want, got))
})
}
}
@ -209,8 +209,7 @@ func TestComputeBudget(t *testing.T) {
func TestBudget_Remaining(t *testing.T) {
t.Parallel()
b := Budget{Total: 10000}
assert.Equal(t, 7000, b.Remaining(1000, 500, 500, 500, 500, 0))
assert.Equal(t, 0, b.Remaining(5000, 3000, 1000, 1000, 1000, 0))
assert.Empty(t, cmp.Diff(7000, b.Remaining(1000, 500, 500, 500, 500, 0)))
}
func TestSelectDAGLevel(t *testing.T) {
@ -226,21 +225,21 @@ func TestSelectDAGLevel(t *testing.T) {
chunkTokens := d.TotalTokens(LevelChunk)
// Large budget -> most detailed (chunk)
assert.Equal(t, LevelChunk, SelectDAGLevel(d, chunkTokens+1000))
assert.Empty(t, cmp.Diff(LevelChunk, SelectDAGLevel(d, chunkTokens+1000)))
// Very small budget -> session level
assert.Equal(t, LevelSession, SelectDAGLevel(d, 10))
assert.Empty(t, cmp.Diff(LevelSession, SelectDAGLevel(d, 10)))
// Nil DAG
assert.Equal(t, LevelRaw, SelectDAGLevel(nil, 1000))
assert.Empty(t, cmp.Diff(LevelRaw, SelectDAGLevel(nil, 1000)))
}
func TestTailMessageCount(t *testing.T) {
t.Parallel()
assert.Equal(t, 4, TailMessageCount(100)) // Minimum
assert.Equal(t, 20, TailMessageCount(1000)) // 1000/50
assert.Equal(t, 4, TailMessageCount(0)) // Zero budget
assert.Equal(t, 4, TailMessageCount(-1)) // Negative
assert.Empty(t, cmp.Diff(4, TailMessageCount(100))) // Minimum
assert.Empty(t, cmp.Diff(20, TailMessageCount(1000))) // 1000/50
assert.Empty(t, cmp.Diff(4, TailMessageCount(0))) // Zero budget
assert.Empty(t, cmp.Diff(4, TailMessageCount(-1))) // Negative
}
func TestRenderDAGForBudget(t *testing.T) {

View file

@ -7,6 +7,7 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -60,7 +61,7 @@ func TestLibSQLDelegate_InsertAuditEntry(t *testing.T) {
count, err := d.CountAuditEntries(ctx, tt.entry.AgentID)
require.NoError(t, err)
assert.Equal(t, 1, count)
assert.Empty(t, cmp.Diff(1, count))
})
}
}
@ -171,9 +172,15 @@ func TestLibSQLDelegate_ListAuditEntriesByAction(t *testing.T) {
require.NoError(t, err)
assert.Len(t, entries, tt.wantLen)
if tt.wantAction != "" {
for _, e := range entries {
assert.Equal(t, tt.wantAction, e.Action)
actions := make([]string, len(entries))
for i, entry := range entries {
actions[i] = entry.Action
}
wantActions := make([]string, len(entries))
for i := range wantActions {
wantActions[i] = tt.wantAction
}
assert.Empty(t, cmp.Diff(wantActions, actions))
}
})
}
@ -234,14 +241,14 @@ func TestLibSQLDelegate_PruneOldAuditEntries(t *testing.T) {
count, err := d.CountAuditEntries(ctx, "a1")
require.NoError(t, err)
assert.Equal(t, 2, count)
assert.Empty(t, cmp.Diff(2, count))
// Prune entries created before "now + 1 minute" (should remove all)
require.NoError(t, d.PruneOldAuditEntries(ctx, "a1", time.Now().Add(time.Minute)))
count, err = d.CountAuditEntries(ctx, "a1")
require.NoError(t, err)
assert.Equal(t, 0, count)
assert.Empty(t, cmp.Diff(0, count))
}
func TestLibSQLDelegate_CountAuditEntriesByAction(t *testing.T) {
@ -281,7 +288,7 @@ func TestLibSQLDelegate_CountAuditEntriesByAction(t *testing.T) {
}
count, err := d.CountAuditEntriesByAction(ctx, tt.agentID, tt.action)
require.NoError(t, err)
assert.Equal(t, tt.want, count)
assert.Empty(t, cmp.Diff(tt.want, count))
})
}
}

View file

@ -5,6 +5,8 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg/memory/dag"
memsqlc "github.com/ZanzyTHEbar/dragonscale/pkg/memory/sqlc"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -38,9 +40,9 @@ func TestLibSQLDelegate_PersistDAG(t *testing.T) {
SessionKey: "session1",
})
require.NoError(t, err)
require.Equal(t, "agent1", row.AgentID)
require.Equal(t, "session1", row.SessionKey)
require.Equal(t, int64(16), row.MsgCount)
assert.Empty(t, cmp.Diff("agent1", row.AgentID))
assert.Empty(t, cmp.Diff("session1", row.SessionKey))
assert.Empty(t, cmp.Diff(int64(16), row.MsgCount))
nodes, err := d.Queries().ListDAGNodesBySnapshotID(ctx, memsqlc.ListDAGNodesBySnapshotIDParams{
SnapshotID: row.ID,

View file

@ -6,6 +6,8 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -79,9 +81,12 @@ func TestLibSQLDelegate_GetDocument(t *testing.T) {
return
}
require.NotNil(t, doc)
assert.Equal(t, tt.wantContent, doc.Content)
assert.Equal(t, tt.agentID, doc.AgentID)
assert.Equal(t, tt.docName, doc.Name)
assert.Empty(t, cmp.Diff(&memory.AgentDocument{
AgentID: tt.agentID,
Name: tt.docName,
Content: tt.wantContent,
IsActive: true,
}, doc, cmpopts.IgnoreFields(memory.AgentDocument{}, "ID", "Category", "Version", "CreatedAt", "UpdatedAt")))
})
}
}
@ -148,7 +153,7 @@ func TestLibSQLDelegate_UpsertDocument(t *testing.T) {
got, err := d.GetDocument(ctx, tt.agentID, tt.docName)
require.NoError(t, err)
require.NotNil(t, got)
assert.Equal(t, tt.wantContent, got.Content)
assert.Empty(t, cmp.Diff(tt.wantContent, got.Content))
})
}
}
@ -202,7 +207,7 @@ func TestLibSQLDelegate_DeleteDocument(t *testing.T) {
other, err := d.GetDocument(ctx, "a2", "shared")
require.NoError(t, err)
require.NotNil(t, other, "other agent's document must survive")
assert.Equal(t, "a2-doc", other.Content)
assert.Empty(t, cmp.Diff("a2-doc", other.Content))
}
})
}
@ -268,7 +273,7 @@ func TestLibSQLDelegate_ListDocumentsByCategory(t *testing.T) {
require.NoError(t, err)
assert.Len(t, docs, tt.wantLen)
if tt.wantName != "" && len(docs) > 0 {
assert.Equal(t, tt.wantName, docs[0].Name)
assert.Empty(t, cmp.Diff(tt.wantName, docs[0].Name))
}
})
}

View file

@ -8,10 +8,21 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg"
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type integrationRecallItemRoleContent struct {
Role string
Content string
}
type auditEntryActionTarget struct {
Action string
Target string
}
func TestCronKVBackend_Roundtrip(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
@ -52,12 +63,18 @@ func TestCronKVBackend_Roundtrip(t *testing.T) {
var loaded cronStore
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &loaded))
assert.Equal(t, 1, loaded.Version)
assert.Len(t, loaded.Jobs, 2)
assert.Equal(t, "daily report", loaded.Jobs[0].Name)
assert.True(t, loaded.Jobs[0].Enabled)
assert.Equal(t, "weekly backup", loaded.Jobs[1].Name)
assert.False(t, loaded.Jobs[1].Enabled)
wantStore := cronStore{
Version: 1,
Jobs: []struct {
ID string `json:"id"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
}{
{ID: "job-1", Name: "daily report", Enabled: true},
{ID: "job-2", Name: "weekly backup", Enabled: false},
},
}
assert.Empty(t, cmp.Diff(wantStore, loaded))
}
func TestCronKVBackend_UpdatePreservesShape(t *testing.T) {
@ -75,7 +92,7 @@ func TestCronKVBackend_UpdatePreservesShape(t *testing.T) {
raw, err := d.GetKV(ctx, agentID, kvKey)
require.NoError(t, err)
assert.Equal(t, v2, raw)
assert.Empty(t, cmp.Diff(v2, raw))
}
func TestCronKVBackend_PrefixScan(t *testing.T) {
@ -111,15 +128,26 @@ func TestEndToEnd_SessionAndAuditFlow(t *testing.T) {
// 2. Verify session message count
count, err := d.CountSessionMessages(ctx, agentID, sessionKey)
require.NoError(t, err)
assert.Equal(t, int64(4), count)
assert.Empty(t, cmp.Diff(int64(4), count))
// 3. Verify message ordering (ASC)
msgs, err := d.ListSessionMessages(ctx, agentID, sessionKey, "", 50)
require.NoError(t, err)
require.Len(t, msgs, 4)
assert.Equal(t, "user", msgs[0].Role)
assert.Equal(t, "What is the weather?", msgs[0].Content)
assert.Equal(t, "assistant", msgs[3].Role)
wantMsgs := []integrationRecallItemRoleContent{
{Role: "user", Content: "What is the weather?"},
{Role: "assistant", Content: "Let me check..."},
{Role: "tool", Content: `{"temp":72,"unit":"F"}`},
{Role: "assistant", Content: "It's 72F."},
}
gotMsgs := make([]integrationRecallItemRoleContent, len(msgs))
for i, msg := range msgs {
gotMsgs[i] = integrationRecallItemRoleContent{
Role: msg.Role,
Content: msg.Content,
}
}
assert.Empty(t, cmp.Diff(wantMsgs, gotMsgs))
// 4. Insert audit entries for the tool call
auditEntry := &memory.AuditEntry{
@ -138,8 +166,11 @@ func TestEndToEnd_SessionAndAuditFlow(t *testing.T) {
auditEntries, err := d.ListAuditEntriesBySession(ctx, agentID, sessionKey, 10)
require.NoError(t, err)
require.Len(t, auditEntries, 1)
assert.Equal(t, "tool_call", auditEntries[0].Action)
assert.Equal(t, "weather_api", auditEntries[0].Target)
gotAuditEntry := auditEntryActionTarget{
Action: auditEntries[0].Action,
Target: auditEntries[0].Target,
}
assert.Empty(t, cmp.Diff(auditEntryActionTarget{Action: "tool_call", Target: "weather_api"}, gotAuditEntry))
// 6. Store KV state (e.g. focus checkpoint)
require.NoError(t, d.UpsertKV(ctx, agentID, "focus:"+sessionKey, `{"topic":"weather query","checkpoint_index":2}`))
@ -161,7 +192,7 @@ func TestEndToEnd_SessionAndAuditFlow(t *testing.T) {
loadedDoc, err := d.GetDocument(ctx, agentID, "AGENT.md")
require.NoError(t, err)
require.NotNil(t, loadedDoc)
assert.Equal(t, "# Agent Identity\nI am dragonscale.", loadedDoc.Content)
assert.Empty(t, cmp.Diff("# Agent Identity\nI am dragonscale.", loadedDoc.Content))
// 8. Verify cross-table isolation: different session sees nothing
otherMsgs, err := d.ListSessionMessages(ctx, agentID, "sess-other", "", 50)
@ -186,7 +217,7 @@ func TestEndToEnd_WorkingContextAndRecallRoundtrip(t *testing.T) {
wc, err := d.GetWorkingContext(ctx, agentID, sessionKey)
require.NoError(t, err)
require.NotNil(t, wc)
assert.Equal(t, "Initial system prompt state", wc.Content)
assert.Empty(t, cmp.Diff("Initial system prompt state", wc.Content))
// 2. Insert recall items
item := &memory.RecallItem{
@ -206,14 +237,14 @@ func TestEndToEnd_WorkingContextAndRecallRoundtrip(t *testing.T) {
// 3. Count recall items
recallCount, err := d.CountRecallItems(ctx, agentID, sessionKey)
require.NoError(t, err)
assert.Equal(t, 1, recallCount)
assert.Empty(t, cmp.Diff(1, recallCount))
// 4. Update working context
require.NoError(t, d.UpsertWorkingContext(ctx, agentID, sessionKey, "Updated with preference awareness"))
wc, err = d.GetWorkingContext(ctx, agentID, sessionKey)
require.NoError(t, err)
assert.Equal(t, "Updated with preference awareness", wc.Content)
assert.Empty(t, cmp.Diff("Updated with preference awareness", wc.Content))
// 5. Insert a summary
summary := &memory.MemorySummary{
@ -229,5 +260,5 @@ func TestEndToEnd_WorkingContextAndRecallRoundtrip(t *testing.T) {
summaries, err := d.ListSummaries(ctx, agentID, sessionKey, 10)
require.NoError(t, err)
require.Len(t, summaries, 1)
assert.Equal(t, "User discussed preferences. Key info captured.", summaries[0].Content)
assert.Empty(t, cmp.Diff("User discussed preferences. Key info captured.", summaries[0].Content))
}

View file

@ -4,6 +4,7 @@ import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -84,7 +85,7 @@ func TestLibSQLDelegate_GetKV(t *testing.T) {
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantValue, got)
assert.Empty(t, cmp.Diff(tt.wantValue, got))
})
}
}
@ -155,7 +156,7 @@ func TestLibSQLDelegate_UpsertKV(t *testing.T) {
}
got, err := d.GetKV(ctx, tt.agentID, tt.key)
require.NoError(t, err)
assert.Equal(t, tt.want, got)
assert.Empty(t, cmp.Diff(tt.want, got))
})
}
}
@ -214,7 +215,7 @@ func TestLibSQLDelegate_DeleteKV(t *testing.T) {
if tt.name == "delete only affects target agent" {
other, err := d.GetKV(ctx, "a2", "shared")
require.NoError(t, err)
assert.Equal(t, "a2-val", other, "other agent's key must survive")
assert.Empty(t, cmp.Diff("a2-val", other), "other agent's key must survive")
}
})
}
@ -306,7 +307,7 @@ func TestLibSQLDelegate_ListKVByPrefix(t *testing.T) {
return
}
assert.Equal(t, tt.want, got)
assert.Empty(t, cmp.Diff(tt.want, got))
})
}
}

View file

@ -7,6 +7,7 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -77,11 +78,16 @@ func TestLibSQLDelegate_InsertSessionMessage(t *testing.T) {
count, err := d.CountSessionMessages(ctx, tt.agentID, tt.sessionKey)
require.NoError(t, err)
assert.Equal(t, int64(1), count)
assert.Empty(t, cmp.Diff(int64(1), count))
})
}
}
type recallItemRoleContent struct {
Role string
Content string
}
func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
t.Parallel()
tests := []struct {
@ -92,7 +98,8 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
role string
limit int
wantLen int
wantRole string
wantRoles []string
wantMsgs []recallItemRoleContent
}{
{
name: "empty session returns empty",
@ -114,6 +121,11 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
role: "",
limit: 50,
wantLen: 3,
wantMsgs: []recallItemRoleContent{
{Role: "user", Content: "msg1"},
{Role: "assistant", Content: "msg2"},
{Role: "tool", Content: "msg3"},
},
},
{
name: "filter by role=user",
@ -127,7 +139,11 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
role: "user",
limit: 50,
wantLen: 2,
wantRole: "user",
wantRoles: []string{"user", "user"},
wantMsgs: []recallItemRoleContent{
{Role: "user", Content: "u1"},
{Role: "user", Content: "u2"},
},
},
{
name: "session isolation",
@ -140,6 +156,9 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
role: "",
limit: 50,
wantLen: 1,
wantMsgs: []recallItemRoleContent{
{Role: "user", Content: "msgA"},
},
},
{
name: "agent isolation",
@ -152,6 +171,9 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
role: "",
limit: 50,
wantLen: 1,
wantMsgs: []recallItemRoleContent{
{Role: "user", Content: "from-a1"},
},
},
{
name: "respects limit",
@ -178,6 +200,12 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
role: "",
limit: 50,
wantLen: 3,
wantRoles: []string{"user", "assistant", "user"},
wantMsgs: []recallItemRoleContent{
{Role: "user", Content: "first"},
{Role: "assistant", Content: "second"},
{Role: "user", Content: "third"},
},
},
}
@ -192,16 +220,22 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
require.NoError(t, err)
assert.Len(t, msgs, tt.wantLen)
if tt.wantRole != "" {
for _, m := range msgs {
assert.Equal(t, tt.wantRole, m.Role)
if tt.wantRoles != nil {
gotRoles := make([]string, len(msgs))
for i, got := range msgs {
gotRoles[i] = got.Role
}
assert.Empty(t, cmp.Diff(tt.wantRoles, gotRoles))
}
if tt.name == "ordered by created_at ASC" && len(msgs) == 3 {
assert.Equal(t, "first", msgs[0].Content)
assert.Equal(t, "second", msgs[1].Content)
assert.Equal(t, "third", msgs[2].Content)
if tt.wantMsgs != nil {
gotMsgs := make([]recallItemRoleContent, len(msgs))
for i, got := range msgs {
gotMsgs[i] = recallItemRoleContent{
Role: got.Role,
Content: got.Content,
}
}
assert.Empty(t, cmp.Diff(tt.wantMsgs, gotMsgs))
}
})
}
@ -266,7 +300,7 @@ func TestLibSQLDelegate_CountSessionMessages(t *testing.T) {
}
count, err := d.CountSessionMessages(ctx, tt.agentID, tt.sessionKey)
require.NoError(t, err)
assert.Equal(t, tt.want, count)
assert.Empty(t, cmp.Diff(tt.want, count))
})
}
}

View file

@ -9,6 +9,7 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory/delegate"
memstore "github.com/ZanzyTHEbar/dragonscale/pkg/memory/store"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -103,8 +104,8 @@ func TestIntegration_BlobPK_RoundTrip(t *testing.T) {
fetched, err := store.GetRecall(ctx, item.ID)
require.NoError(t, err)
require.NotNil(t, fetched)
assert.Equal(t, item.ID, fetched.ID, "BLOB PK round-trip must preserve ID")
assert.Equal(t, "BLOB PK integration test item", fetched.Content)
assert.Empty(t, cmp.Diff(item.ID, fetched.ID), "BLOB PK round-trip must preserve ID")
assert.Empty(t, cmp.Diff("BLOB PK integration test item", fetched.Content))
}
func TestIntegration_ArchivalChunking_EndToEnd(t *testing.T) {
@ -129,7 +130,7 @@ func TestIntegration_ArchivalChunking_EndToEnd(t *testing.T) {
for _, chunk := range chunks {
assert.False(t, chunk.ID.IsZero(), "chunk ID should not be zero")
assert.Equal(t, recallID, chunk.RecallID, "chunk must reference parent recall item")
assert.Empty(t, cmp.Diff(recallID, chunk.RecallID), "chunk must reference parent recall item")
}
}
@ -226,12 +227,12 @@ func TestIntegration_WorkingContext_Persistence(t *testing.T) {
wc, err := del.GetWorkingContext(ctx, testAgent, testSession)
require.NoError(t, err)
require.NotNil(t, wc)
assert.Equal(t, "initial state", wc.Content)
assert.Empty(t, cmp.Diff("initial state", wc.Content))
require.NoError(t, del.UpsertWorkingContext(ctx, testAgent, testSession, "updated state"))
wc, err = del.GetWorkingContext(ctx, testAgent, testSession)
require.NoError(t, err)
assert.Equal(t, "updated state", wc.Content)
assert.Empty(t, cmp.Diff("updated state", wc.Content))
}
func TestIntegration_Summary_CRUD(t *testing.T) {
@ -252,8 +253,8 @@ func TestIntegration_Summary_CRUD(t *testing.T) {
fetched, err := del.ListSummaries(ctx, testAgent, testSession, 1)
require.NoError(t, err)
require.Len(t, fetched, 1)
assert.Equal(t, summary.ID, fetched[0].ID)
assert.Equal(t, "Summarized conversation about testing", fetched[0].Content)
assert.Empty(t, cmp.Diff(summary.ID, fetched[0].ID))
assert.Empty(t, cmp.Diff("Summarized conversation about testing", fetched[0].Content))
}
func TestIntegration_IDUniqueness_AcrossEntities(t *testing.T) {
@ -300,7 +301,7 @@ func TestIntegration_ContextPressure(t *testing.T) {
pressure, err := store.ContextUsage(ctx, testAgent, testSession)
require.NoError(t, err)
require.NotNil(t, pressure)
assert.Equal(t, 0, pressure.RecallItemCount)
assert.Empty(t, cmp.Diff(0, pressure.RecallItemCount))
for i := 0; i < 3; i++ {
item := &memory.RecallItem{
@ -315,5 +316,5 @@ func TestIntegration_ContextPressure(t *testing.T) {
pressure, err = store.ContextUsage(ctx, testAgent, testSession)
require.NoError(t, err)
assert.Equal(t, 3, pressure.RecallItemCount)
assert.Empty(t, cmp.Diff(3, pressure.RecallItemCount))
}

View file

@ -5,6 +5,7 @@ import (
"time"
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
@ -17,7 +18,7 @@ func TestActiveContextProjection_TotalTokens(t *testing.T) {
{Tokens: 35},
},
}
assert.Equal(t, 235, p.TotalTokens())
assert.Empty(t, cmp.Diff(235, p.TotalTokens()))
}
func TestActiveContextProjection_HasLosslessRefs(t *testing.T) {

View file

@ -8,6 +8,7 @@ import (
"time"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/ZanzyTHEbar/dragonscale/pkg"
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
@ -114,71 +115,156 @@ func writeSessionFile(t *testing.T, dir, name string, sess SessionFile) {
}
}
func TestMigrateFileSessions_Basic(t *testing.T) {
func TestMigrateFileSessions(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()
writeSessionFile(t, sessDir, "sess1.json", SessionFile{
Key: "session-1",
Messages: []SessionMsg{
{Role: "user", Content: "Hello"},
{Role: "assistant", Content: "Hi there!"},
type tc struct {
name string
setup func(t *testing.T, dir string, del *mockDelegate)
sessionsDir string
want *MigrateSessionsResult
extra func(t *testing.T, dir string, result *MigrateSessionsResult, del *mockDelegate)
}
tests := []tc{
{
name: "basic",
setup: func(t *testing.T, dir string, _ *mockDelegate) {
writeSessionFile(t, dir, "sess1.json", SessionFile{
Key: "session-1",
Messages: []SessionMsg{
{Role: "user", Content: "Hello"},
{Role: "assistant", Content: "Hi there!"},
},
Created: time.Now().Add(-time.Hour),
Updated: time.Now(),
})
writeSessionFile(t, dir, "sess2.json", SessionFile{
Key: "session-2",
Summary: "Talked about Go programming",
Messages: []SessionMsg{
{Role: "user", Content: "Tell me about Go"},
},
})
},
want: &MigrateSessionsResult{SessionsFound: 2, SessionsMigrated: 2, ItemsCreated: 3, Errors: 0},
extra: func(t *testing.T, _ string, _ *MigrateSessionsResult, del *mockDelegate) {
if len(del.recallItems) != 3 {
t.Fatalf("expected 3 recall items, got %d", len(del.recallItems))
}
first := del.recallItems[0]
if first.Role != "user" || first.Content != "Hello" || first.SessionKey != "session-1" || first.Tags != "migrated" {
t.Errorf("unexpected first recall item: %+v", first)
}
wc, err := del.GetWorkingContext(t.Context(), pkg.NAME, "session-2")
if err != nil {
t.Fatalf("GetWorkingContext: %v", err)
}
if wc == nil || wc.Content != "Talked about Go programming" {
t.Errorf("expected summary as working context, got %v", wc)
}
},
},
Created: time.Now().Add(-time.Hour),
Updated: time.Now(),
})
writeSessionFile(t, sessDir, "sess2.json", SessionFile{
Key: "session-2",
Summary: "Talked about Go programming",
Messages: []SessionMsg{
{Role: "user", Content: "Tell me about Go"},
{
name: "empty dir",
setup: func(_ *testing.T, _ string, _ *mockDelegate) {},
want: &MigrateSessionsResult{
SessionsFound: 0,
SessionsMigrated: 0,
ItemsCreated: 0,
Errors: 0,
},
},
{
name: "skip empty messages",
setup: func(t *testing.T, dir string, _ *mockDelegate) {
writeSessionFile(t, dir, "sess.json", SessionFile{
Key: "s1",
Messages: []SessionMsg{
{Role: "user", Content: "real content"},
{Role: "assistant", Content: ""},
{Role: "user", Content: " "},
},
})
},
want: &MigrateSessionsResult{SessionsFound: 1, SessionsMigrated: 1, ItemsCreated: 1, Errors: 0},
},
{
name: "fallback key from filename",
setup: func(t *testing.T, dir string, _ *mockDelegate) {
writeSessionFile(t, dir, "custom-key.json", SessionFile{
Key: "",
Messages: []SessionMsg{
{Role: "user", Content: "test"},
},
})
},
want: &MigrateSessionsResult{SessionsFound: 1, SessionsMigrated: 1, ItemsCreated: 1, Errors: 0},
extra: func(t *testing.T, _ string, result *MigrateSessionsResult, del *mockDelegate) {
if got := del.recallItems[0].SessionKey; got != "custom-key" {
t.Errorf("expected 'custom-key' from filename, got %q", got)
}
if result.ItemsCreated != 1 {
t.Fatalf("expected 1 item, got %d", result.ItemsCreated)
}
},
},
{
name: "malformed json",
setup: func(t *testing.T, dir string, _ *mockDelegate) {
if err := os.WriteFile(filepath.Join(dir, "bad.json"), []byte("{broken"), 0644); err != nil {
t.Fatalf("write malformed json: %v", err)
}
writeSessionFile(t, dir, "good.json", SessionFile{
Key: "g1",
Messages: []SessionMsg{
{Role: "user", Content: "ok"},
},
})
},
want: &MigrateSessionsResult{SessionsFound: 2, SessionsMigrated: 1, ItemsCreated: 1, Errors: 1},
},
{
name: "nonexistent dir",
sessionsDir: "/nonexistent/path",
want: nil,
},
})
result, err := MigrateFileSessions(t.Context(), del, pkg.NAME, sessDir)
if err != nil {
t.Fatalf("MigrateFileSessions: %v", err)
}
if result.SessionsFound != 2 {
t.Errorf("expected 2 sessions found, got %d", result.SessionsFound)
}
if result.SessionsMigrated != 2 {
t.Errorf("expected 2 sessions migrated, got %d", result.SessionsMigrated)
}
if result.ItemsCreated != 3 {
t.Errorf("expected 3 items, got %d", result.ItemsCreated)
}
if result.Errors != 0 {
t.Errorf("expected 0 errors, got %d", result.Errors)
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
del := newMockDelegate()
sessDir := t.TempDir()
if tt.sessionsDir != "" {
sessDir = tt.sessionsDir
}
if tt.setup != nil {
tt.setup(t, sessDir, del)
}
// Check recall items were created with correct data
if len(del.recallItems) != 3 {
t.Fatalf("expected 3 recall items, got %d", len(del.recallItems))
}
if del.recallItems[0].Role != "user" {
t.Errorf("expected 'user' role, got %q", del.recallItems[0].Role)
}
if del.recallItems[0].Content != "Hello" {
t.Errorf("expected 'Hello', got %q", del.recallItems[0].Content)
}
if del.recallItems[0].SessionKey != "session-1" {
t.Errorf("expected 'session-1', got %q", del.recallItems[0].SessionKey)
}
if del.recallItems[0].Tags != "migrated" {
t.Errorf("expected 'migrated' tag, got %q", del.recallItems[0].Tags)
}
// Check summary was stored as working context
wc, err := del.GetWorkingContext(t.Context(), pkg.NAME, "session-2")
if err != nil {
t.Fatalf("GetWorkingContext: %v", err)
}
if wc == nil || wc.Content != "Talked about Go programming" {
t.Errorf("expected summary as working context, got %v", wc)
got, err := MigrateFileSessions(t.Context(), del, pkg.NAME, sessDir)
if err != nil {
t.Fatalf("MigrateFileSessions: %v", err)
}
if tt.want == nil {
if got != nil {
t.Errorf("expected nil result, got %#v", got)
}
return
}
if got == nil {
t.Fatal("expected migration result, got nil")
}
if diff := cmp.Diff(*tt.want, *got); diff != "" {
t.Errorf("migration result mismatch (-want +got):\n%s", diff)
}
if tt.extra != nil {
tt.extra(t, sessDir, got, del)
}
})
}
}
@ -194,7 +280,6 @@ func TestMigrateFileSessions_Idempotent(t *testing.T) {
},
})
// First run
result1, err := MigrateFileSessions(t.Context(), del, pkg.NAME, sessDir)
if err != nil {
t.Fatalf("first migration: %v", err)
@ -203,7 +288,6 @@ func TestMigrateFileSessions_Idempotent(t *testing.T) {
t.Fatalf("expected 1, got %d", result1.SessionsMigrated)
}
// Second run should be a no-op (marker file exists)
result2, err := MigrateFileSessions(t.Context(), del, pkg.NAME, sessDir)
if err != nil {
t.Fatalf("second migration: %v", err)
@ -212,114 +296,7 @@ func TestMigrateFileSessions_Idempotent(t *testing.T) {
t.Error("expected nil result for already-migrated directory")
}
// Still only 1 item
if len(del.recallItems) != 1 {
t.Errorf("expected 1 recall item (no duplicates), got %d", len(del.recallItems))
}
}
func TestMigrateFileSessions_EmptyDir(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()
result, err := MigrateFileSessions(t.Context(), del, pkg.NAME, sessDir)
if err != nil {
t.Fatalf("MigrateFileSessions: %v", err)
}
if result.SessionsFound != 0 {
t.Errorf("expected 0 sessions, got %d", result.SessionsFound)
}
}
func TestMigrateFileSessions_NonexistentDir(t *testing.T) {
t.Parallel()
del := newMockDelegate()
result, err := MigrateFileSessions(t.Context(), del, pkg.NAME, "/nonexistent/path")
if err != nil {
t.Fatalf("MigrateFileSessions: %v", err)
}
if result != nil {
t.Error("expected nil result for nonexistent dir")
}
}
func TestMigrateFileSessions_SkipsEmptyMessages(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()
writeSessionFile(t, sessDir, "sess.json", SessionFile{
Key: "s1",
Messages: []SessionMsg{
{Role: "user", Content: "real content"},
{Role: "assistant", Content: ""},
{Role: "user", Content: " "},
},
})
result, err := MigrateFileSessions(t.Context(), del, pkg.NAME, sessDir)
if err != nil {
t.Fatalf("MigrateFileSessions: %v", err)
}
if result.ItemsCreated != 1 {
t.Errorf("expected 1 item (empty msgs skipped), got %d", result.ItemsCreated)
}
}
func TestMigrateFileSessions_FallbackKey(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()
// Session with empty key — should use filename
writeSessionFile(t, sessDir, "custom-key.json", SessionFile{
Key: "",
Messages: []SessionMsg{
{Role: "user", Content: "test"},
},
})
result, err := MigrateFileSessions(t.Context(), del, pkg.NAME, sessDir)
if err != nil {
t.Fatalf("MigrateFileSessions: %v", err)
}
if result.ItemsCreated != 1 {
t.Fatalf("expected 1 item, got %d", result.ItemsCreated)
}
if del.recallItems[0].SessionKey != "custom-key" {
t.Errorf("expected 'custom-key' from filename, got %q", del.recallItems[0].SessionKey)
}
}
func TestMigrateFileSessions_MalformedJSON(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()
// Write a malformed JSON file
os.WriteFile(filepath.Join(sessDir, "bad.json"), []byte("{broken"), 0644)
// Also a valid one
writeSessionFile(t, sessDir, "good.json", SessionFile{
Key: "g1",
Messages: []SessionMsg{
{Role: "user", Content: "ok"},
},
})
result, err := MigrateFileSessions(t.Context(), del, pkg.NAME, sessDir)
if err != nil {
t.Fatalf("MigrateFileSessions: %v", err)
}
if result.SessionsFound != 2 {
t.Errorf("expected 2 found, got %d", result.SessionsFound)
}
if result.SessionsMigrated != 1 {
t.Errorf("expected 1 migrated, got %d", result.SessionsMigrated)
}
if result.Errors != 1 {
t.Errorf("expected 1 error, got %d", result.Errors)
}
}

View file

@ -53,17 +53,17 @@ func TestRelativeDate(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := relativeDate(tc.ref, now)
assert.Equal(t, tc.want, got)
assert.Empty(t, cmp.Diff(tc.want, got))
})
}
}
func TestPriorityEmoji(t *testing.T) {
t.Parallel()
assert.Equal(t, "🔴", PriorityCritical.Emoji())
assert.Equal(t, "🟡", PriorityNotable.Emoji())
assert.Equal(t, "🔵", PriorityInformational.Emoji())
assert.Equal(t, "🔵", Priority("unknown").Emoji())
assert.Empty(t, cmp.Diff("🔴", PriorityCritical.Emoji()))
assert.Empty(t, cmp.Diff("🟡", PriorityNotable.Emoji()))
assert.Empty(t, cmp.Diff("🔵", PriorityInformational.Emoji()))
assert.Empty(t, cmp.Diff("🔵", Priority("unknown").Emoji()))
}
func TestFormatBlock(t *testing.T) {
@ -84,8 +84,8 @@ func TestFormatBlock(t *testing.T) {
func TestFormatBlock_Empty(t *testing.T) {
t.Parallel()
assert.Equal(t, "", FormatBlock(nil))
assert.Equal(t, "", FormatBlock([]Observation{}))
assert.Empty(t, cmp.Diff("", FormatBlock(nil)))
assert.Empty(t, cmp.Diff("", FormatBlock([]Observation{})))
}
func TestMarshalUnmarshalRoundTrip(t *testing.T) {
@ -185,7 +185,7 @@ func TestObserver_Observe(t *testing.T) {
obs, err := o.Observe(t.Context(), msgs, nil)
require.NoError(t, err)
assert.Len(t, obs, 2)
assert.Equal(t, PriorityCritical, obs[0].Priority)
assert.Empty(t, cmp.Diff(PriorityCritical, obs[0].Priority))
}
func TestReflector_ShouldReflect(t *testing.T) {
@ -224,8 +224,8 @@ func TestReflector_Reflect(t *testing.T) {
kept, err := r.Reflect(t.Context(), obs)
require.NoError(t, err)
assert.Len(t, kept, 2)
assert.Equal(t, "Critical fact", kept[0].Content)
assert.Equal(t, "Notable thing", kept[1].Content)
assert.Empty(t, cmp.Diff("Critical fact", kept[0].Content))
assert.Empty(t, cmp.Diff("Notable thing", kept[1].Content))
}
func TestReflector_ReflectFallbackKeepsCritical(t *testing.T) {
@ -245,16 +245,16 @@ func TestReflector_ReflectFallbackKeepsCritical(t *testing.T) {
kept, err := r.Reflect(t.Context(), obs)
require.NoError(t, err)
assert.Len(t, kept, 1)
assert.Equal(t, "Must keep", kept[0].Content)
assert.Empty(t, cmp.Diff("Must keep", kept[0].Content))
}
func TestParsePriority(t *testing.T) {
t.Parallel()
assert.Equal(t, PriorityCritical, parsePriority("critical"))
assert.Equal(t, PriorityCritical, parsePriority("CRITICAL"))
assert.Equal(t, PriorityNotable, parsePriority("notable"))
assert.Equal(t, PriorityInformational, parsePriority("informational"))
assert.Equal(t, PriorityInformational, parsePriority("unknown"))
assert.Empty(t, cmp.Diff(PriorityCritical, parsePriority("critical")))
assert.Empty(t, cmp.Diff(PriorityCritical, parsePriority("CRITICAL")))
assert.Empty(t, cmp.Diff(PriorityNotable, parsePriority("notable")))
assert.Empty(t, cmp.Diff(PriorityInformational, parsePriority("informational")))
assert.Empty(t, cmp.Diff(PriorityInformational, parsePriority("unknown")))
}
func TestParseKeptIndices(t *testing.T) {

View file

@ -4,6 +4,7 @@ import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -21,7 +22,7 @@ func TestMarkdownChunker_BasicSplit(t *testing.T) {
assert.Greater(t, len(chunks), 1, "long content should produce multiple chunks")
for i, c := range chunks {
assert.Equal(t, i, c.Index)
assert.Empty(t, cmp.Diff(i, c.Index))
assert.NotEmpty(t, c.Text)
}
}
@ -33,7 +34,7 @@ func TestMarkdownChunker_SmallContent(t *testing.T) {
chunks, err := chunker.Chunk("Short text.")
require.NoError(t, err)
assert.Len(t, chunks, 1)
assert.Equal(t, "Short text.", chunks[0].Text)
assert.Empty(t, cmp.Diff("Short text.", chunks[0].Text))
}
func TestMarkdownChunker_PreservesMarkdownStructure(t *testing.T) {
@ -94,8 +95,8 @@ func TestMarkdownChunker_EmptyContent(t *testing.T) {
func TestMarkdownChunker_DefaultConfig(t *testing.T) {
t.Parallel()
cfg := DefaultMarkdownChunkerConfig()
assert.Equal(t, 1600, cfg.ChunkSize)
assert.Equal(t, 320, cfg.ChunkOverlap)
assert.Empty(t, cmp.Diff(1600, cfg.ChunkSize))
assert.Empty(t, cmp.Diff(320, cfg.ChunkOverlap))
assert.True(t, cfg.CodeBlocks)
assert.True(t, cfg.Headings)
}

View file

@ -13,6 +13,7 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
memdag "github.com/ZanzyTHEbar/dragonscale/pkg/memory/dag"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory/delegate"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -111,7 +112,7 @@ func TestWorkingContext_SetAndGet(t *testing.T) {
// Get back
content, err = store.GetWorkingContext(ctx, "agent-1", "session-1")
require.NoError(t, err)
assert.Equal(t, "You are a helpful assistant.", content)
assert.Empty(t, cmp.Diff("You are a helpful assistant.", content))
// Update
err = store.SetWorkingContext(ctx, "agent-1", "session-1", "Updated context.")
@ -119,7 +120,7 @@ func TestWorkingContext_SetAndGet(t *testing.T) {
content, err = store.GetWorkingContext(ctx, "agent-1", "session-1")
require.NoError(t, err)
assert.Equal(t, "Updated context.", content)
assert.Empty(t, cmp.Diff("Updated context.", content))
}
func TestWorkingContext_IsolatedBySessions(t *testing.T) {
@ -134,11 +135,11 @@ func TestWorkingContext_IsolatedBySessions(t *testing.T) {
a, err := store.GetWorkingContext(ctx, "agent-1", "session-a")
require.NoError(t, err)
assert.Equal(t, "Context A", a)
assert.Empty(t, cmp.Diff("Context A", a))
b, err := store.GetWorkingContext(ctx, "agent-1", "session-b")
require.NoError(t, err)
assert.Equal(t, "Context B", b)
assert.Empty(t, cmp.Diff("Context B", b))
}
func TestRecall_CRUD(t *testing.T) {
@ -165,8 +166,8 @@ func TestRecall_CRUD(t *testing.T) {
got, err := store.GetRecall(ctx, item.ID)
require.NoError(t, err)
require.NotNil(t, got)
assert.Equal(t, "The user asked about Go generics.", got.Content)
assert.Equal(t, memory.SectorEpisodic, got.Sector)
assert.Empty(t, cmp.Diff("The user asked about Go generics.", got.Content))
assert.Empty(t, cmp.Diff(memory.SectorEpisodic, got.Sector))
assert.InDelta(t, 0.8, got.Importance, 0.001)
// Update
@ -178,7 +179,7 @@ func TestRecall_CRUD(t *testing.T) {
updated, err := store.GetRecall(ctx, item.ID)
require.NoError(t, err)
assert.InDelta(t, 0.95, updated.Importance, 0.001)
assert.Equal(t, "Updated: user asked about Go generics in depth.", updated.Content)
assert.Empty(t, cmp.Diff("Updated: user asked about Go generics in depth.", updated.Content))
// Delete
err = store.DeleteRecall(ctx, item.ID)
@ -323,7 +324,7 @@ func TestSearch_ShadowModeUsesBaselineAndTracksParity(t *testing.T) {
var metrics retrievalShadowMetrics
require.NoError(t, json.Unmarshal([]byte(metricsRaw), &metrics))
assert.Equal(t, 1, metrics.TotalQueries)
assert.Empty(t, cmp.Diff(1, metrics.TotalQueries))
}
func TestSearch_DoesNotPromoteWithoutAugmentedSignals(t *testing.T) {
@ -366,7 +367,7 @@ func TestSearch_DoesNotPromoteWithoutAugmentedSignals(t *testing.T) {
require.NoError(t, err)
var state retrievalPolicyState
require.NoError(t, json.Unmarshal([]byte(stateRaw), &state))
assert.Equal(t, retrievalModeShadow, state.Mode)
assert.Empty(t, cmp.Diff(retrievalModeShadow, state.Mode))
}
func TestSearch_PromoteOnlyOnGateWin(t *testing.T) {
@ -412,7 +413,7 @@ func TestSearch_PromoteOnlyOnGateWin(t *testing.T) {
var state retrievalPolicyState
require.NoError(t, json.Unmarshal([]byte(stateRaw), &state))
assert.Equal(t, retrievalModePromoted, state.Mode)
assert.Empty(t, cmp.Diff(retrievalModePromoted, state.Mode))
}
func TestSearch_FastRollbackPreservesBaselinePath(t *testing.T) {
@ -488,7 +489,7 @@ func TestSearch_FastRollbackPreservesBaselinePath(t *testing.T) {
require.NoError(t, err)
var state retrievalPolicyState
require.NoError(t, json.Unmarshal([]byte(stateRaw), &state))
assert.Equal(t, retrievalModeRollback, state.Mode)
assert.Empty(t, cmp.Diff(retrievalModeRollback, state.Mode))
// Subsequent calls should return baseline-only path again.
results, err := store.Search(ctx, "grocery", memory.SearchOptions{
@ -538,7 +539,7 @@ func TestUpdateRetrievalPolicy_PersistFailuresDoNotBlockTransitions(t *testing.T
}
next := store.updateRetrievalPolicy(ctx, state, gates, metrics, parity, true, baseline)
assert.Equal(t, retrievalModePromoted, next.Mode)
assert.Empty(t, cmp.Diff(retrievalModePromoted, next.Mode))
}
func TestSearch_ConcurrentRetrievalPolicyUpdates(t *testing.T) {
@ -596,9 +597,9 @@ func TestContextUsage(t *testing.T) {
// Empty system — should be normal pressure
pressure, err := store.ContextUsage(ctx, "agent-1", "session-1")
require.NoError(t, err)
assert.Equal(t, memory.PressureNormal, pressure.PressureLevel)
assert.Equal(t, 0, pressure.WorkingContextTokens)
assert.Equal(t, 0, pressure.RecallItemCount)
assert.Empty(t, cmp.Diff(memory.PressureNormal, pressure.PressureLevel))
assert.Empty(t, cmp.Diff(0, pressure.WorkingContextTokens))
assert.Empty(t, cmp.Diff(0, pressure.RecallItemCount))
// Add working context
err = store.SetWorkingContext(ctx, "agent-1", "session-1", strings.Repeat("x", 4000))
@ -642,7 +643,7 @@ func TestContextUsage_RecallTokenEstimateUsesContent(t *testing.T) {
expectedRecallTokens := estimateTokens(contentA) + estimateTokens(contentB)
assert.GreaterOrEqual(t, pressure.EstimatedTotalTokens, expectedRecallTokens)
assert.Equal(t, 2, pressure.RecallItemCount)
assert.Empty(t, cmp.Diff(2, pressure.RecallItemCount))
}
func TestContextUsage_PressureLevels(t *testing.T) {
@ -782,7 +783,7 @@ func TestRRF_MergesTwoSets(t *testing.T) {
merged := ReciprocalRankFusion([][]memory.SearchResult{set1, set2}, []float64{1.0, 1.0}, 60)
require.GreaterOrEqual(t, len(merged), 2)
// idB appears in both sets, should have highest fused score
assert.Equal(t, idB, merged[0].ID)
assert.Empty(t, cmp.Diff(idB, merged[0].ID))
}
func TestRecencyDecay(t *testing.T) {
@ -819,5 +820,5 @@ func TestApplyRecencyDecay_ReordersByAge(t *testing.T) {
})
// idNew should now rank higher because idOld got heavily decayed
assert.Equal(t, idNew, results[0].ID)
assert.Empty(t, cmp.Diff(idNew, results[0].ID))
}

View file

@ -5,6 +5,7 @@ import (
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -119,8 +120,8 @@ func TestMemoryTool_Status(t *testing.T) {
resp := executeAndParse(t, tool, `{"action":"status"}`)
assert.True(t, resp.Success)
require.NotNil(t, resp.Status)
assert.Equal(t, "normal", resp.Status.PressureLevel)
assert.Equal(t, 0, resp.Status.RecallItemCount)
assert.Empty(t, cmp.Diff("normal", resp.Status.PressureLevel))
assert.Empty(t, cmp.Diff(0, resp.Status.RecallItemCount))
}
func TestMemoryTool_InvalidAction(t *testing.T) {

View file

@ -6,6 +6,7 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory/delegate"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -37,7 +38,7 @@ func TestQueueManager_NormalPressure(t *testing.T) {
decision, err := qm.Evaluate(ctx, "agent-1", "session-1")
require.NoError(t, err)
assert.Equal(t, QueueActionNone, decision.Action)
assert.Empty(t, cmp.Diff(QueueActionNone, decision.Action))
assert.Contains(t, decision.Message, "healthy")
}
@ -52,7 +53,7 @@ func TestQueueManager_WarnPressure(t *testing.T) {
decision, err := qm.Evaluate(ctx, "agent-1", "s1")
require.NoError(t, err)
assert.Equal(t, QueueActionWarn, decision.Action)
assert.Empty(t, cmp.Diff(QueueActionWarn, decision.Action))
assert.Contains(t, decision.Message, "selective")
}
@ -67,7 +68,7 @@ func TestQueueManager_OffloadPressure(t *testing.T) {
decision, err := qm.Evaluate(ctx, "agent-1", "s1")
require.NoError(t, err)
assert.Equal(t, QueueActionOffload, decision.Action)
assert.Empty(t, cmp.Diff(QueueActionOffload, decision.Action))
assert.Contains(t, decision.Message, "offloading")
}
@ -82,7 +83,7 @@ func TestQueueManager_FlushPressure(t *testing.T) {
decision, err := qm.Evaluate(ctx, "agent-1", "s1")
require.NoError(t, err)
assert.Equal(t, QueueActionFlush, decision.Action)
assert.Empty(t, cmp.Diff(QueueActionFlush, decision.Action))
assert.Contains(t, decision.Message, "flush")
}
@ -118,7 +119,7 @@ func TestQueueManager_EvictEmpty(t *testing.T) {
evicted, summary, err := qm.EvictOldest(ctx, "agent-1", "empty-session")
require.NoError(t, err)
assert.Equal(t, 0, evicted)
assert.Empty(t, cmp.Diff(0, evicted))
assert.Empty(t, summary)
}
@ -128,5 +129,5 @@ func TestDefaultQueueManagerConfig(t *testing.T) {
assert.InDelta(t, 0.70, cfg.WarnThreshold, 0.001)
assert.InDelta(t, 0.80, cfg.OffloadThreshold, 0.001)
assert.InDelta(t, 0.85, cfg.FlushThreshold, 0.001)
assert.Equal(t, 10, cfg.MaxEvictBatch)
assert.Empty(t, cmp.Diff(10, cfg.MaxEvictBatch))
}

View file

@ -4,6 +4,7 @@ import (
"testing"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -73,7 +74,7 @@ func TestHeuristicScorer_SectorClassification(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
result, err := scorer.Score(ctx, tt.content, "user", "")
require.NoError(t, err)
assert.Equal(t, tt.sector, result.Sector)
assert.Empty(t, cmp.Diff(tt.sector, result.Sector))
})
}
}
@ -85,7 +86,7 @@ func TestParseScoringResponse_ValidJSON(t *testing.T) {
require.NoError(t, err)
assert.InDelta(t, 0.85, result.Importance, 0.001)
assert.InDelta(t, 0.6, result.Salience, 0.001)
assert.Equal(t, memory.SectorSemantic, result.Sector)
assert.Empty(t, cmp.Diff(memory.SectorSemantic, result.Sector))
}
func TestParseScoringResponse_WithCodeFences(t *testing.T) {
@ -94,7 +95,7 @@ func TestParseScoringResponse_WithCodeFences(t *testing.T) {
result, err := parseScoringResponse(input)
require.NoError(t, err)
assert.InDelta(t, 0.9, result.Importance, 0.001)
assert.Equal(t, memory.SectorProcedural, result.Sector)
assert.Empty(t, cmp.Diff(memory.SectorProcedural, result.Sector))
}
func TestParseScoringResponse_ClampsValues(t *testing.T) {
@ -111,7 +112,7 @@ func TestParseScoringResponse_UnknownSector(t *testing.T) {
input := `{"importance": 0.5, "salience": 0.5, "sector": "unknown_sector"}`
result, err := parseScoringResponse(input)
require.NoError(t, err)
assert.Equal(t, memory.SectorEpisodic, result.Sector, "unknown sector should default to episodic")
assert.Empty(t, cmp.Diff(memory.SectorEpisodic, result.Sector), "unknown sector should default to episodic")
}
func TestNormalizeSector(t *testing.T) {
@ -129,7 +130,7 @@ func TestNormalizeSector(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
assert.Equal(t, tt.expected, normalizeSector(tt.input))
assert.Empty(t, cmp.Diff(tt.expected, normalizeSector(tt.input)))
})
}
}

View file

@ -7,6 +7,7 @@ import (
"testing"
"github.com/ZanzyTHEbar/dragonscale/pkg/rlm"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -110,8 +111,8 @@ func TestFanOut_AllPartitions_Processed(t *testing.T) {
assert.Len(t, results, 4)
for i, r := range results {
assert.Equal(t, i, r.PartitionIdx)
assert.Equal(t, "ans-"+partitions[i], r.Answer)
assert.Empty(t, cmp.Diff(i, r.PartitionIdx))
assert.Empty(t, cmp.Diff("ans-"+partitions[i], r.Answer))
}
}
@ -126,7 +127,7 @@ func TestFanOut_UnboundedConcurrency(t *testing.T) {
return rlm.PartitionResult{PartitionIdx: idx, Answer: part, Tokens: 2}
})
assert.Len(t, results, 20)
assert.Equal(t, uint32(40), rlm.TotalTokens(results))
assert.Empty(t, cmp.Diff(uint32(40), rlm.TotalTokens(results)))
}
func TestFanOut_Empty(t *testing.T) {
@ -147,7 +148,7 @@ func TestMergeResults_Deduplication(t *testing.T) {
{Answer: ""}, // empty — should be skipped
}
merged := rlm.MergeResults(results)
assert.Equal(t, "alpha\nbeta", merged)
assert.Empty(t, cmp.Diff("alpha\nbeta", merged))
}
func TestMergeResults_WithErrors(t *testing.T) {
@ -157,7 +158,7 @@ func TestMergeResults_WithErrors(t *testing.T) {
{Err: fmt.Errorf("failed"), Answer: "should be skipped"},
}
merged := rlm.MergeResults(results)
assert.Equal(t, "good", merged)
assert.Empty(t, cmp.Diff("good", merged))
}
func TestStrategyPlanner_SmallContext_OpFinal(t *testing.T) {
@ -167,7 +168,7 @@ func TestStrategyPlanner_SmallContext_OpFinal(t *testing.T) {
planner := rlm.NewStrategyPlanner(cfg)
op := planner.PlanNext(500, "any query", 0)
assert.Equal(t, rlm.OpFinal, op.Type)
assert.Empty(t, cmp.Diff(rlm.OpFinal, op.Type))
}
func TestStrategyPlanner_MaxDepth_OpFinal(t *testing.T) {
@ -177,7 +178,7 @@ func TestStrategyPlanner_MaxDepth_OpFinal(t *testing.T) {
planner := rlm.NewStrategyPlanner(cfg)
op := planner.PlanNext(100000, "any query", 3) // depth == MaxDepth
assert.Equal(t, rlm.OpFinal, op.Type)
assert.Empty(t, cmp.Diff(rlm.OpFinal, op.Type))
}
func TestStrategyPlanner_KeywordQuery_OpGrep(t *testing.T) {
@ -186,7 +187,7 @@ func TestStrategyPlanner_KeywordQuery_OpGrep(t *testing.T) {
// "find" prefix should trigger grep.
op := planner.PlanNext(100000, "find myFunction in code", 0)
assert.Equal(t, rlm.OpGrep, op.Type)
assert.Empty(t, cmp.Diff(rlm.OpGrep, op.Type))
assert.NotEmpty(t, op.GrepQuery)
}
@ -195,6 +196,6 @@ func TestStrategyPlanner_LargeContext_OpPartition(t *testing.T) {
planner := rlm.NewStrategyPlanner(rlm.DefaultStrategyConfig())
op := planner.PlanNext(100000, "summarise everything", 0)
assert.Equal(t, rlm.OpPartition, op.Type)
assert.Empty(t, cmp.Diff(rlm.OpPartition, op.Type))
assert.Greater(t, op.PartitionK, 0)
}

View file

@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -32,9 +33,9 @@ func TestFanOutUnbounded(t *testing.T) {
require.Len(t, results, 3)
for i, r := range results {
assert.Equal(t, i, r.PartitionIdx)
assert.Empty(t, cmp.Diff(i, r.PartitionIdx))
assert.Contains(t, r.Answer, fmt.Sprintf("part-%d", i))
assert.Equal(t, uint32(10), r.Tokens)
assert.Empty(t, cmp.Diff(uint32(10), r.Tokens))
}
}
@ -86,8 +87,8 @@ func TestFanOutPreservesOrder(t *testing.T) {
require.Len(t, results, 4)
for i, r := range results {
assert.Equal(t, i, r.PartitionIdx)
assert.Equal(t, partitions[i], r.Answer)
assert.Empty(t, cmp.Diff(i, r.PartitionIdx))
assert.Empty(t, cmp.Diff(partitions[i], r.Answer))
}
}
@ -102,7 +103,7 @@ func TestMergeResultsDeduplication(t *testing.T) {
}
merged := MergeResults(results)
assert.Equal(t, "answer one\nanswer two", merged)
assert.Empty(t, cmp.Diff("answer one\nanswer two", merged))
}
func TestMergeResultsAllErrors(t *testing.T) {
@ -130,10 +131,10 @@ func TestTotalTokens(t *testing.T) {
{Tokens: 250},
{Tokens: 50},
}
assert.Equal(t, uint32(400), TotalTokens(results))
assert.Empty(t, cmp.Diff(uint32(400), TotalTokens(results)))
}
func TestTotalTokensEmpty(t *testing.T) {
t.Parallel()
assert.Equal(t, uint32(0), TotalTokens(nil))
assert.Empty(t, cmp.Diff(uint32(0), TotalTokens(nil)))
}

View file

@ -5,6 +5,7 @@ import (
"testing"
"github.com/ZanzyTHEbar/dragonscale/pkg/rlm"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -12,24 +13,24 @@ import (
func TestRope_EmptyRope(t *testing.T) {
t.Parallel()
r := rlm.NewRope("")
assert.Equal(t, 0, r.Len())
assert.Equal(t, "", r.String())
assert.Empty(t, cmp.Diff(0, r.Len()))
assert.Empty(t, cmp.Diff("", r.String()))
}
func TestRope_BasicAppendAndString(t *testing.T) {
t.Parallel()
r := rlm.NewRope("hello")
r.Append(" world")
assert.Equal(t, 11, r.Len())
assert.Equal(t, "hello world", r.String())
assert.Empty(t, cmp.Diff(11, r.Len()))
assert.Empty(t, cmp.Diff("hello world", r.String()))
}
func TestRope_LargeContent(t *testing.T) {
t.Parallel()
content := strings.Repeat("abcdefghij", 1000) // 10000 bytes
r := rlm.NewRope(content)
assert.Equal(t, 10000, r.Len())
assert.Equal(t, content, r.String())
assert.Empty(t, cmp.Diff(10000, r.Len()))
assert.Empty(t, cmp.Diff(content, r.String()))
}
func TestRope_Slice_ValidRange(t *testing.T) {
@ -37,7 +38,7 @@ func TestRope_Slice_ValidRange(t *testing.T) {
r := rlm.NewRope("hello world")
s, err := r.Slice(6, 11)
require.NoError(t, err)
assert.Equal(t, "world", s)
assert.Empty(t, cmp.Diff("world", s))
}
func TestRope_Slice_ZeroLength(t *testing.T) {
@ -45,7 +46,7 @@ func TestRope_Slice_ZeroLength(t *testing.T) {
r := rlm.NewRope("hello")
s, err := r.Slice(2, 2)
require.NoError(t, err)
assert.Equal(t, "", s)
assert.Empty(t, cmp.Diff("", s))
}
func TestRope_Slice_OutOfRange(t *testing.T) {
@ -61,14 +62,14 @@ func TestRope_Slice_AcrossAppendBoundary(t *testing.T) {
r.Append(" world")
s, err := r.Slice(3, 8)
require.NoError(t, err)
assert.Equal(t, "lo wo", s)
assert.Empty(t, cmp.Diff("lo wo", s))
}
func TestRope_Lines(t *testing.T) {
t.Parallel()
r := rlm.NewRope("line1\nline2\nline3")
lines := r.Lines()
assert.Equal(t, []string{"line1", "line2", "line3"}, lines)
assert.Empty(t, cmp.Diff([]string{"line1", "line2", "line3"}, lines))
}
func TestRope_GrepLines_CaseSensitive(t *testing.T) {
@ -76,8 +77,8 @@ func TestRope_GrepLines_CaseSensitive(t *testing.T) {
r := rlm.NewRope("apple\nBanana\napricot\ncherry")
matches := r.GrepLines("ap", 0, false)
require.Len(t, matches, 2)
assert.Equal(t, 1, matches[0].LineNum)
assert.Equal(t, 3, matches[1].LineNum)
assert.Empty(t, cmp.Diff(1, matches[0].LineNum))
assert.Empty(t, cmp.Diff(3, matches[1].LineNum))
}
func TestRope_GrepLines_CaseInsensitive(t *testing.T) {
@ -85,7 +86,7 @@ func TestRope_GrepLines_CaseInsensitive(t *testing.T) {
r := rlm.NewRope("Apple\nbanana\nAPRICOT")
matches := r.GrepLines("apple", 0, true)
require.Len(t, matches, 1)
assert.Equal(t, "Apple", matches[0].Line)
assert.Empty(t, cmp.Diff("Apple", matches[0].Line))
}
func TestRope_GrepLines_MaxMatches(t *testing.T) {
@ -107,7 +108,7 @@ func TestRope_Partition_Even(t *testing.T) {
r := rlm.NewRope("12345678")
parts := r.Partition(4)
assert.Len(t, parts, 4)
assert.Equal(t, "12345678", strings.Join(parts, ""))
assert.Empty(t, cmp.Diff("12345678", strings.Join(parts, "")))
}
func TestRope_Partition_MoreThanContent(t *testing.T) {
@ -117,7 +118,7 @@ func TestRope_Partition_MoreThanContent(t *testing.T) {
assert.Len(t, parts, 10)
// All content should appear in first non-empty partition.
combined := strings.Join(parts, "")
assert.Equal(t, "hi", combined)
assert.Empty(t, cmp.Diff("hi", combined))
}
func TestRope_Partition_Empty(t *testing.T) {
@ -126,7 +127,7 @@ func TestRope_Partition_Empty(t *testing.T) {
parts := r.Partition(4)
assert.Len(t, parts, 4)
for _, p := range parts {
assert.Equal(t, "", p)
assert.Empty(t, cmp.Diff("", p))
}
}
@ -136,6 +137,6 @@ func TestRope_RuneLen(t *testing.T) {
)
r := rlm.NewRope("héllo") // 'é' is 2 bytes
assert.Equal(t, 5, r.RuneLen())
assert.Equal(t, 6, r.Len()) // bytes
assert.Empty(t, cmp.Diff(5, r.RuneLen()))
assert.Empty(t, cmp.Diff(6, r.Len())) // bytes
}

View file

@ -3,6 +3,7 @@ package rlm
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
@ -15,7 +16,7 @@ func TestStrategyPlanNextFinalAtMaxDepth(t *testing.T) {
})
op := sp.PlanNext(100000, "any query", 3)
assert.Equal(t, OpFinal, op.Type)
assert.Empty(t, cmp.Diff(OpFinal, op.Type))
}
func TestStrategyPlanNextFinalSmallContext(t *testing.T) {
@ -23,7 +24,7 @@ func TestStrategyPlanNextFinalSmallContext(t *testing.T) {
sp := NewStrategyPlanner(DefaultStrategyConfig())
op := sp.PlanNext(1000, "any query", 0)
assert.Equal(t, OpFinal, op.Type)
assert.Empty(t, cmp.Diff(OpFinal, op.Type))
}
func TestStrategyPlanNextGrepForKeywordQuery(t *testing.T) {
@ -43,7 +44,7 @@ func TestStrategyPlanNextGrepForKeywordQuery(t *testing.T) {
for _, tt := range tests {
t.Run(tt.query, func(t *testing.T) {
op := sp.PlanNext(1_000_000, tt.query, 0)
assert.Equal(t, OpGrep, op.Type)
assert.Empty(t, cmp.Diff(OpGrep, op.Type))
assert.NotEmpty(t, op.GrepQuery)
})
}
@ -53,31 +54,31 @@ func TestStrategyPlanNextPartitionDefault(t *testing.T) {
t.Parallel()
sp := NewStrategyPlanner(DefaultStrategyConfig())
op := sp.PlanNext(100_000, "summarize this document", 0)
assert.Equal(t, OpPartition, op.Type)
assert.Equal(t, 4, op.PartitionK)
assert.Empty(t, cmp.Diff(OpPartition, op.Type))
assert.Empty(t, cmp.Diff(4, op.PartitionK))
}
func TestStrategyPlanNextPartitionLargeContext(t *testing.T) {
t.Parallel()
sp := NewStrategyPlanner(DefaultStrategyConfig())
op := sp.PlanNext(5_000_000, "summarize this corpus", 0)
assert.Equal(t, OpPartition, op.Type)
assert.Equal(t, 8, op.PartitionK, "large contexts should use more partitions")
assert.Empty(t, cmp.Diff(OpPartition, op.Type))
assert.Empty(t, cmp.Diff(8, op.PartitionK), "large contexts should use more partitions")
}
func TestExtractKeywordQuoted(t *testing.T) {
t.Parallel()
assert.Equal(t, "handleRequest", extractKeyword(`find "handleRequest" in the codebase`))
assert.Empty(t, cmp.Diff("handleRequest", extractKeyword(`find "handleRequest" in the codebase`)))
}
func TestExtractKeywordNoQuotes(t *testing.T) {
t.Parallel()
assert.Equal(t, "find", extractKeyword("find the main function"))
assert.Empty(t, cmp.Diff("find", extractKeyword("find the main function")))
}
func TestExtractKeywordEmpty(t *testing.T) {
t.Parallel()
assert.Equal(t, "", extractKeyword(""))
assert.Empty(t, cmp.Diff("", extractKeyword("")))
}
func TestLooksLikeKeywordQuery(t *testing.T) {

View file

@ -9,6 +9,7 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg"
"github.com/ZanzyTHEbar/dragonscale/pkg/bus"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -40,11 +41,10 @@ func TestResolveBaseConfigPath_PrefersXDGOverLegacy(t *testing.T) {
require.NoError(t, os.WriteFile(legacyPath, []byte(`{}`), 0o644))
got := ResolveBaseConfigPath()
assert.Equal(t, xdgPath, got)
assert.Empty(t, cmp.Diff(xdgPath, got))
}
func TestResolveBaseConfigPath_FallsBackToLegacyWhenXDGMissing(t *testing.T) {
t.Parallel()
home := t.TempDir()
xdg := t.TempDir()
t.Setenv("HOME", home)
@ -55,7 +55,7 @@ func TestResolveBaseConfigPath_FallsBackToLegacyWhenXDGMissing(t *testing.T) {
require.NoError(t, os.WriteFile(legacyPath, []byte(`{}`), 0o644))
got := ResolveBaseConfigPath()
assert.Equal(t, legacyPath, got)
assert.Empty(t, cmp.Diff(legacyPath, got))
}
func TestLoadResolvedConfig_AppliesOverlayAndKeepsBaseValues(t *testing.T) {
@ -79,7 +79,7 @@ func TestLoadResolvedConfig_AppliesOverlayAndKeepsBaseValues(t *testing.T) {
OverlayConfigPath: overlayPath,
})
require.NoError(t, err)
assert.Equal(t, "base-key", cfg.Providers.OpenAI.APIKey)
assert.Empty(t, cmp.Diff("base-key", cfg.Providers.OpenAI.APIKey))
assert.True(t, cfg.Agents.Defaults.RestrictToSandbox)
}
@ -94,7 +94,7 @@ func TestEnsureMinProviderTimeout_SetsFloor(t *testing.T) {
MinProviderTimeout: 180 * time.Second,
})
require.NoError(t, err)
assert.Equal(t, 180, cfg.Providers.OpenAI.Timeout)
assert.Empty(t, cmp.Diff(180, cfg.Providers.OpenAI.Timeout))
}
func TestStartOutbound_DropAndConsumeDoNotBlockPublishers(t *testing.T) {
@ -156,7 +156,7 @@ func TestStartOutbound_CallbackReceivesMessages(t *testing.T) {
select {
case got := <-received:
assert.Equal(t, "hello", got.Content)
assert.Empty(t, cmp.Diff("hello", got.Content))
case <-time.After(2 * time.Second):
t.Fatal("did not receive callback outbound message")
}

View file

@ -4,6 +4,7 @@ import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -25,7 +26,7 @@ func TestExtractJSON_RawJSON(t *testing.T) {
var result map[string]interface{}
err := ExtractJSON(tc.input, &result, nil)
require.NoError(t, err)
assert.Equal(t, tc.wantVal, result[tc.wantKey])
assert.Empty(t, cmp.Diff(tc.wantVal, result[tc.wantKey]))
})
}
}
@ -54,7 +55,7 @@ func TestExtractJSON_CodeFence(t *testing.T) {
var result map[string]interface{}
err := ExtractJSON(tc.input, &result, nil)
require.NoError(t, err)
assert.Equal(t, float64(42), result["score"])
assert.Empty(t, cmp.Diff(float64(42), result["score"]))
})
}
}
@ -68,8 +69,8 @@ func TestExtractJSON_EmbeddedInProse(t *testing.T) {
}
err := ExtractJSON(input, &result, nil)
require.NoError(t, err)
assert.Equal(t, 0.9, result.Importance)
assert.Equal(t, "semantic", result.Sector)
assert.Empty(t, cmp.Diff(0.9, result.Importance))
assert.Empty(t, cmp.Diff("semantic", result.Sector))
}
func TestExtractJSON_NestedBracesInStrings(t *testing.T) {
@ -78,8 +79,8 @@ func TestExtractJSON_NestedBracesInStrings(t *testing.T) {
var result map[string]interface{}
err := ExtractJSON(input, &result, nil)
require.NoError(t, err)
assert.Equal(t, "function() { return {}; }", result["content"])
assert.Equal(t, float64(1), result["count"])
assert.Empty(t, cmp.Diff("function() { return {}; }", result["content"]))
assert.Empty(t, cmp.Diff(float64(1), result["count"]))
}
func TestExtractJSON_InjectionAttempts(t *testing.T) {
@ -148,7 +149,7 @@ func TestExtractJSON_MultipleFences_TakesFirst(t *testing.T) {
var result map[string]interface{}
err := ExtractJSON(input, &result, nil)
require.NoError(t, err)
assert.Equal(t, true, result["first"])
assert.Empty(t, cmp.Diff(true, result["first"]))
_, hasSecond := result["second"]
assert.False(t, hasSecond)
}
@ -168,9 +169,9 @@ func TestSanitizeToolArgs_ValidInput(t *testing.T) {
result, err := SanitizeToolArgs(args, schema)
require.NoError(t, err)
assert.Equal(t, "/tmp/test.txt", result["path"])
assert.Equal(t, "hello world", result["content"])
assert.Equal(t, "overwrite", result["mode"])
assert.Empty(t, cmp.Diff("/tmp/test.txt", result["path"]))
assert.Empty(t, cmp.Diff("hello world", result["content"]))
assert.Empty(t, cmp.Diff("overwrite", result["mode"]))
}
func TestSanitizeToolArgs_MissingRequired(t *testing.T) {
@ -224,7 +225,7 @@ func TestSanitizeToolArgs_TypeCoercion(t *testing.T) {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tc.expected, result["arg"])
assert.Empty(t, cmp.Diff(tc.expected, result["arg"]))
}
})
}
@ -234,7 +235,7 @@ func TestExtractFirstBraced_EscapedQuotes(t *testing.T) {
t.Parallel()
input := `{"msg": "say \"hello\" world"}`
result := extractFirstBraced(input)
assert.Equal(t, input, result)
assert.Empty(t, cmp.Diff(input, result))
}
func TestExtractFirstBraced_UnbalancedBraces(t *testing.T) {

View file

@ -3,6 +3,7 @@ package security
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
@ -92,7 +93,7 @@ func TestRedactor_SafeText(t *testing.T) {
t.Parallel()
r := NewRedactor()
safe := "This is a normal log message about processing 42 items."
assert.Equal(t, safe, r.Redact(safe))
assert.Empty(t, cmp.Diff(safe, r.Redact(safe)))
assert.False(t, r.ContainsSensitive(safe))
}
@ -109,10 +110,10 @@ func TestRedactor_RedactMap(t *testing.T) {
}
out := r.RedactMap(m)
assert.Contains(t, out["command"].(string), "[REDACTED:")
assert.Equal(t, "normal output", out["output"])
assert.Empty(t, cmp.Diff("normal output", out["output"]))
nested := out["nested"].(map[string]interface{})
assert.Contains(t, nested["secret"].(string), "[REDACTED:")
assert.Equal(t, 42, out["count"])
assert.Empty(t, cmp.Diff(42, out["count"]))
}
func TestMaskKey(t *testing.T) {
@ -128,7 +129,7 @@ func TestMaskKey(t *testing.T) {
}
for _, tc := range tests {
t.Run(tc.input, func(t *testing.T) {
assert.Equal(t, tc.want, MaskKey(tc.input))
assert.Empty(t, cmp.Diff(tc.want, MaskKey(tc.input)))
})
}
}

View file

@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -24,12 +25,12 @@ func TestAuditLogAppendAndRetrieve(t *testing.T) {
}
require.NoError(t, al.Append(event))
assert.Equal(t, 1, al.Len())
assert.Empty(t, cmp.Diff(1, al.Len()))
events := al.Events()
require.Len(t, events, 1)
assert.Equal(t, "req-1", events[0].RequestID)
assert.Equal(t, "read_file", events[0].ToolName)
assert.Empty(t, cmp.Diff("req-1", events[0].RequestID))
assert.Empty(t, cmp.Diff("read_file", events[0].ToolName))
}
func TestAuditLogConcurrentAppend(t *testing.T) {
@ -50,7 +51,7 @@ func TestAuditLogConcurrentAppend(t *testing.T) {
}
wg.Wait()
assert.Equal(t, n, al.Len())
assert.Empty(t, cmp.Diff(n, al.Len()))
}
func TestAuditLogFilterBySession(t *testing.T) {
@ -81,8 +82,8 @@ func TestAuditLogLeakEvents(t *testing.T) {
leaks := al.LeakEvents()
assert.Len(t, leaks, 2)
assert.Equal(t, "r2", leaks[0].RequestID)
assert.Equal(t, "r3", leaks[1].RequestID)
assert.Empty(t, cmp.Diff("r2", leaks[0].RequestID))
assert.Empty(t, cmp.Diff("r3", leaks[1].RequestID))
}
type mockSink struct {
@ -109,7 +110,7 @@ func TestAuditLogSinkIntegration(t *testing.T) {
_ = al.Append(AuditEvent{RequestID: "r1"})
_ = al.Append(AuditEvent{RequestID: "r2"})
assert.Equal(t, 2, al.Len())
assert.Empty(t, cmp.Diff(2, al.Len()))
assert.Len(t, sink.events, 2)
}
@ -123,7 +124,7 @@ func TestAuditLogSinkError(t *testing.T) {
err := al.Append(AuditEvent{RequestID: "r2"})
assert.Error(t, err)
assert.Equal(t, 2, al.Len(), "in-memory log should always append")
assert.Empty(t, cmp.Diff(2, al.Len()), "in-memory log should always append")
}
func TestAuditLogEventsImmutable(t *testing.T) {
@ -135,5 +136,5 @@ func TestAuditLogEventsImmutable(t *testing.T) {
events[0].RequestID = "mutated"
original := al.Events()
assert.Equal(t, "r1", original[0].RequestID, "original should be unaffected")
assert.Empty(t, cmp.Diff("r1", original[0].RequestID), "original should be unaffected")
}

View file

@ -10,6 +10,7 @@ import (
"github.com/ZanzyTHEbar/dragonscale/pkg/security"
"github.com/ZanzyTHEbar/dragonscale/pkg/security/securebus"
"github.com/ZanzyTHEbar/dragonscale/pkg/tools"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -86,8 +87,8 @@ func TestBus_SuccessfulToolExec(t *testing.T) {
resp := bus.Execute(t.Context(), req)
assert.False(t, resp.IsError)
assert.Equal(t, "hello world", resp.Result)
assert.Equal(t, 1, bus.AuditLog().Len())
assert.Empty(t, cmp.Diff("hello world", resp.Result))
assert.Empty(t, cmp.Diff(1, bus.AuditLog().Len()))
}
func TestBus_UnknownTool(t *testing.T) {
@ -111,7 +112,7 @@ func TestBus_ToolReturnsError(t *testing.T) {
resp := bus.Execute(t.Context(), req)
assert.True(t, resp.IsError)
assert.Equal(t, 1, bus.AuditLog().Len())
assert.Empty(t, cmp.Diff(1, bus.AuditLog().Len()))
events := bus.AuditLog().Events()
assert.True(t, events[0].IsError)
}
@ -187,7 +188,7 @@ func TestBus_SecretInjection_ArgVariant(t *testing.T) {
resp := bus.Execute(t.Context(), req)
assert.False(t, resp.IsError)
assert.Equal(t, "supersecret", resp.Result, "injected secret should appear in tool output")
assert.Empty(t, cmp.Diff("supersecret", resp.Result), "injected secret should appear in tool output")
events := bus.AuditLog().Events()
require.Len(t, events, 1)
@ -222,7 +223,7 @@ func TestBus_AuditLog_FilterBySession(t *testing.T) {
bus.Execute(t.Context(), req)
}
assert.Equal(t, 3, bus.AuditLog().Len())
assert.Empty(t, cmp.Diff(3, bus.AuditLog().Len()))
assert.Len(t, bus.AuditLog().FilterBySession("session-A"), 2)
assert.Len(t, bus.AuditLog().FilterBySession("session-B"), 1)
}
@ -237,7 +238,7 @@ func TestBus_Transport_Send(t *testing.T) {
resp, err := bus.Transport().Send(t.Context(), req)
require.NoError(t, err)
assert.Equal(t, "pong", resp.Result)
assert.Empty(t, cmp.Diff("pong", resp.Result))
}
func TestBus_InvalidArgsJSON(t *testing.T) {
@ -261,7 +262,7 @@ func TestBus_RLMFinalCommand(t *testing.T) {
resp := bus.Execute(t.Context(), req)
assert.False(t, resp.IsError)
assert.Equal(t, "the answer", resp.Result)
assert.Empty(t, cmp.Diff("the answer", resp.Result))
}
func TestBus_CloseIdempotent(t *testing.T) {

View file

@ -9,6 +9,7 @@ import (
"time"
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -50,7 +51,7 @@ func TestSocketTransportRoundTrip(t *testing.T) {
resp, err := client.Send(t.Context(), req)
require.NoError(t, err)
assert.Equal(t, "req-001", resp.ID)
assert.Empty(t, cmp.Diff("req-001", resp.ID))
assert.Contains(t, resp.Result, "req-001")
server.Close()
@ -86,7 +87,7 @@ func TestSocketTransportMultipleRequests(t *testing.T) {
)
resp, err := client.Send(t.Context(), req)
require.NoError(t, err)
assert.Equal(t, req.ID, resp.ID)
assert.Empty(t, cmp.Diff(req.ID, resp.ID))
}
}

View file

@ -4,6 +4,7 @@ import (
"net"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
@ -121,7 +122,7 @@ func TestIsBlockedIP(t *testing.T) {
if ip == nil {
t.Fatalf("invalid IP: %s", tc.ip)
}
assert.Equal(t, tc.blocked, isBlockedIP(ip))
assert.Empty(t, cmp.Diff(tc.blocked, isBlockedIP(ip)))
})
}
}

View file

@ -3,6 +3,7 @@ package security
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -30,7 +31,7 @@ func TestVault_RoundTrip(t *testing.T) {
dec, err := v.DecryptString(enc)
require.NoError(t, err)
assert.Equal(t, secret, dec)
assert.Empty(t, cmp.Diff(secret, dec))
})
}
}
@ -92,7 +93,7 @@ func TestVault_EmptyInput(t *testing.T) {
dec, err := v.DecryptString(enc)
require.NoError(t, err)
assert.Equal(t, "", dec)
assert.Empty(t, cmp.Diff("", dec))
}
func TestVault_BinaryData(t *testing.T) {
@ -106,5 +107,5 @@ func TestVault_BinaryData(t *testing.T) {
dec, err := v.Decrypt(enc)
require.NoError(t, err)
assert.Equal(t, binary, dec)
assert.Empty(t, cmp.Diff(binary, dec))
}

View file

@ -4,6 +4,7 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -95,7 +96,7 @@ func TestZKPSessionManagerIssueAndValidate(t *testing.T) {
st, err := sm.VerifyAndIssue(commit.RX, commit.RY, challenge, response)
require.NoError(t, err)
assert.True(t, st.IsValid())
assert.Equal(t, 1, sm.ActiveSessions())
assert.Empty(t, cmp.Diff(1, sm.ActiveSessions()))
assert.True(t, sm.ValidateToken(st.Token))
}
@ -111,7 +112,7 @@ func TestZKPSessionManagerRejectsInvalidProof(t *testing.T) {
_, err := sm.VerifyAndIssue(make([]byte, 32), make([]byte, 32), make([]byte, 32), make([]byte, 32))
assert.Error(t, err)
assert.Equal(t, 0, sm.ActiveSessions())
assert.Empty(t, cmp.Diff(0, sm.ActiveSessions()))
}
func TestZKPSessionManagerExpiry(t *testing.T) {
@ -152,7 +153,7 @@ func TestZKPSessionManagerRevoke(t *testing.T) {
sm.RevokeToken(st.Token)
assert.False(t, sm.ValidateToken(st.Token))
assert.Equal(t, 0, sm.ActiveSessions())
assert.Empty(t, cmp.Diff(0, sm.ActiveSessions()))
}
func TestHandshakePayloadBinaryRoundTrip(t *testing.T) {
@ -166,11 +167,11 @@ func TestHandshakePayloadBinaryRoundTrip(t *testing.T) {
}
data := hp.MarshalBinary()
assert.Equal(t, 129, len(data))
assert.Empty(t, cmp.Diff(129, len(data)))
decoded, err := UnmarshalBinaryHandshake(data)
require.NoError(t, err)
assert.Equal(t, hp, decoded)
assert.Empty(t, cmp.Diff(hp, decoded))
}
func TestHandshakeResultBinaryRoundTrip(t *testing.T) {
@ -181,11 +182,11 @@ func TestHandshakeResultBinaryRoundTrip(t *testing.T) {
}
data := hr.MarshalBinary()
assert.Equal(t, 40, len(data))
assert.Empty(t, cmp.Diff(40, len(data)))
decoded, err := UnmarshalBinaryResult(data)
require.NoError(t, err)
assert.Equal(t, hr, decoded)
assert.Empty(t, cmp.Diff(hr, decoded))
}
func TestZKPSessionManagerCleanup(t *testing.T) {
@ -203,10 +204,10 @@ func TestZKPSessionManagerCleanup(t *testing.T) {
response, _ := ProverRespond(commit, challenge, x)
_, _ = sm.VerifyAndIssue(commit.RX, commit.RY, challenge, response)
}
assert.Equal(t, 5, sm.ActiveSessions())
assert.Empty(t, cmp.Diff(5, sm.ActiveSessions()))
time.Sleep(5 * time.Millisecond)
cleaned := sm.Cleanup()
assert.Equal(t, 5, cleaned)
assert.Equal(t, 0, sm.ActiveSessions())
assert.Empty(t, cmp.Diff(5, cleaned))
assert.Empty(t, cmp.Diff(0, sm.ActiveSessions()))
}

View file

@ -324,7 +324,7 @@ func TestSessionManager_ProjectionPointerPersistedAndRestored(t *testing.T) {
assert.NotZero(t, ptr.LastMessageID)
assert.False(t, ptr.FirstCreatedAt.IsZero())
assert.False(t, ptr.LastCreatedAt.IsZero())
assert.Equal(t, 3, ptr.Count)
assert.Empty(t, cmp.Diff(3, ptr.Count))
// New manager restores; pointer is re-persisted (same values)
sm2 := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
@ -355,7 +355,7 @@ func TestSessionManager_ProjectionPointerUpdatedOnAppend(t *testing.T) {
require.NotEmpty(t, raw)
var ptr ProjectionPointer
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &ptr))
assert.Equal(t, i+1, ptr.Count)
assert.Empty(t, cmp.Diff(i+1, ptr.Count))
}
}
@ -380,8 +380,8 @@ func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
sm2 := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
history := sm2.GetHistory(sessionKey)
require.Len(t, history, 2)
assert.Equal(t, "a", history[0].Content)
assert.Equal(t, "b", history[1].Content)
assert.Empty(t, cmp.Diff("a", history[0].Content))
assert.Empty(t, cmp.Diff("b", history[1].Content))
// Pointer should now reflect restored state
raw, err := del.GetKV(t.Context(), "test-agent", projectionPointerKey(sessionKey))
@ -389,7 +389,7 @@ func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
require.NotEmpty(t, raw)
var ptr ProjectionPointer
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &ptr))
assert.Equal(t, 2, ptr.Count)
assert.Empty(t, cmp.Diff(2, ptr.Count))
}
func TestSessionManager_ProjectionBackfillStatusPersistedOnBootstrap(t *testing.T) {
@ -412,7 +412,7 @@ func TestSessionManager_ProjectionBackfillStatusPersistedOnBootstrap(t *testing.
var status ProjectionBackfillStatus
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &status))
assert.Equal(t, 1, status.Version)
assert.Empty(t, cmp.Diff(1, status.Version))
assert.GreaterOrEqual(t, status.SessionsScanned, 2)
assert.GreaterOrEqual(t, status.PointersUpdated, 0)
assert.WithinDuration(t, time.Now().UTC(), status.CompletedAt, 5*time.Second)

View file

@ -5,6 +5,7 @@ import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -61,7 +62,7 @@ func TestParseWikilinks(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := ParseWikilinks(tc.content)
assert.Equal(t, tc.want, got)
assert.Empty(t, cmp.Diff(tc.want, got))
})
}
}
@ -83,7 +84,7 @@ func TestMergeUnique(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := mergeUnique(tc.a, tc.b)
assert.Equal(t, tc.want, got)
assert.Empty(t, cmp.Diff(tc.want, got))
})
}
}
@ -140,8 +141,8 @@ No wikilinks here.
rm := g.GetNode("risk-management")
require.NotNil(t, rm)
assert.Equal(t, []string{"trading", "risk"}, rm.Tags)
assert.Equal(t, "finance", rm.Domain)
assert.Empty(t, cmp.Diff([]string{"trading", "risk"}, rm.Tags))
assert.Empty(t, cmp.Diff("finance", rm.Domain))
assert.Contains(t, rm.Links, "position-sizing")
assert.Contains(t, rm.Links, "technical-analysis")
@ -277,7 +278,7 @@ Content.
results = g.SearchSkills("software engineering")
require.True(t, len(results) >= 1)
assert.Equal(t, "code-review", results[0].Name)
assert.Empty(t, cmp.Diff("code-review", results[0].Name))
results = g.SearchSkills("")
assert.Empty(t, results)
@ -307,7 +308,7 @@ Content.
mocs := g.ListMOCs()
assert.Len(t, mocs, 1)
assert.Equal(t, "trading-moc", mocs[0].Name)
assert.Empty(t, cmp.Diff("trading-moc", mocs[0].Name))
assert.True(t, mocs[0].IsMOC)
}
@ -333,7 +334,7 @@ Content.
idx := g.GetIndex()
require.NotNil(t, idx)
assert.Equal(t, "index", idx.Name)
assert.Empty(t, cmp.Diff("index", idx.Name))
assert.True(t, idx.IsIndex)
assert.Contains(t, idx.Links, "trading-moc")
assert.Contains(t, idx.Links, "engineering-moc")
@ -369,9 +370,9 @@ Content with [[some-link]].
require.Len(t, skills, 1)
s := skills[0]
assert.Equal(t, "json-skill", s.Name)
assert.Equal(t, []string{"alpha", "beta"}, s.Tags)
assert.Equal(t, "testing", s.Domain)
assert.Empty(t, cmp.Diff("json-skill", s.Name))
assert.Empty(t, cmp.Diff([]string{"alpha", "beta"}, s.Tags))
assert.Empty(t, cmp.Diff("testing", s.Domain))
}
func nodeNames(nodes []*SkillNode) []string {

View file

@ -5,6 +5,7 @@ import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -54,7 +55,7 @@ func TestInstallTemplate_BuildsValidGraph(t *testing.T) {
rm := g.GetNode("risk-management")
require.NotNil(t, rm)
assert.Equal(t, "finance", rm.Domain)
assert.Empty(t, cmp.Diff("finance", rm.Domain))
assert.Contains(t, rm.Tags, "trading")
}

View file

@ -9,6 +9,7 @@ import (
"time"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -101,7 +102,7 @@ func TestSyncAll_InsertsNewFiles(t *testing.T) {
for _, name := range IdentityFiles {
doc := store.getDoc("agent-1", name)
require.NotNil(t, doc, "expected document for %s", name)
assert.Equal(t, syncCategory, doc.Category)
assert.Empty(t, cmp.Diff(syncCategory, doc.Category))
assert.NotEmpty(t, doc.Content)
hash := store.getHash("agent-1", name)
@ -125,7 +126,7 @@ func TestSyncAll_SkipsUnchangedFiles(t *testing.T) {
require.NoError(t, s.SyncAll(t.Context()))
secondDoc := store.getDoc("agent-1", "AGENT.md")
assert.Equal(t, firstID, secondDoc.ID, "unchanged file should not be re-upserted")
assert.Empty(t, cmp.Diff(firstID, secondDoc.ID), "unchanged file should not be re-upserted")
}
func TestSyncAll_UpsertsModifiedFiles(t *testing.T) {
@ -235,7 +236,7 @@ func TestCheckAndSync_SkipsUntouchedFiles(t *testing.T) {
require.NoError(t, s.CheckAndSync(t.Context()))
hash2 := store.getHash("agent-1", "AGENT.md")
assert.Equal(t, hash1, hash2, "untouched file should not trigger re-sync")
assert.Empty(t, cmp.Diff(hash1, hash2), "untouched file should not trigger re-sync")
}
func TestWatch_DetectsFileChange(t *testing.T) {
@ -285,7 +286,7 @@ func TestContentHash_Deterministic(t *testing.T) {
data := []byte("hello world")
h1 := contentHash(data)
h2 := contentHash(data)
assert.Equal(t, h1, h2)
assert.Empty(t, cmp.Diff(h1, h2))
assert.Len(t, h1, 64)
}
@ -313,7 +314,7 @@ func TestIsIdentityFile(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, isIdentityFile(tt.name))
assert.Empty(t, cmp.Diff(tt.want, isIdentityFile(tt.name)))
})
}
}
@ -322,7 +323,7 @@ func TestNew_SetsFields(t *testing.T) {
t.Parallel()
store := newMockStore()
s := New("/tmp/identity", "test-agent", store)
assert.Equal(t, "/tmp/identity", s.identityDir)
assert.Equal(t, "test-agent", s.agentID)
assert.Empty(t, cmp.Diff("/tmp/identity", s.identityDir))
assert.Empty(t, cmp.Diff("test-agent", s.agentID))
assert.NotNil(t, s.store)
}

View file

@ -6,6 +6,7 @@ import (
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -41,9 +42,9 @@ func TestAgenticMapTool_Execute_Success(t *testing.T) {
} `json:"summary"`
}
require.NoError(t, jsonv2.Unmarshal([]byte(result.ForLLM), &payload))
assert.Equal(t, 2, payload.Count)
assert.Equal(t, 2, payload.Summary.SuccessCount)
assert.Equal(t, 0, payload.Summary.FailureCount)
assert.Empty(t, cmp.Diff(2, payload.Count))
assert.Empty(t, cmp.Diff(2, payload.Summary.SuccessCount))
assert.Empty(t, cmp.Diff(0, payload.Summary.FailureCount))
}
func TestAgenticMapTool_Execute_RetriesFailedItems(t *testing.T) {
@ -78,7 +79,7 @@ func TestAgenticMapTool_Execute_RetriesFailedItems(t *testing.T) {
require.NoError(t, jsonv2.Unmarshal([]byte(result.ForLLM), &payload))
require.Len(t, payload.Results, 1)
assert.True(t, payload.Results[0].Success)
assert.Equal(t, 2, payload.Results[0].Attempts)
assert.Empty(t, cmp.Diff(2, payload.Results[0].Attempts))
}
func TestAgenticMapTool_Execute_RequiresPlaceholders(t *testing.T) {

View file

@ -5,6 +5,7 @@ import (
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/ZanzyTHEbar/dragonscale/pkg/messages"
"github.com/ZanzyTHEbar/dragonscale/pkg/session"
@ -78,8 +79,8 @@ func TestStartFocus(t *testing.T) {
var state FocusState
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &state))
assert.Equal(t, "investigate auth bug", state.Topic)
assert.Equal(t, 2, state.CheckpointIndex)
assert.Empty(t, cmp.Diff("investigate auth bug", state.Topic))
assert.Empty(t, cmp.Diff(2, state.CheckpointIndex))
}
func TestStartFocus_MissingTopic(t *testing.T) {
@ -115,7 +116,7 @@ func TestCompleteFocus(t *testing.T) {
sm.AddMessage(sk, "assistant", "all tests pass")
historyBefore := sm.GetHistory(sk)
require.Equal(t, 8, len(historyBefore))
assert.Empty(t, cmp.Diff(8, len(historyBefore)))
completeTool := NewCompleteFocusTool(delegate, sm, func() string { return sk })
result := completeTool.Execute(ctx, map[string]interface{}{
@ -130,8 +131,8 @@ func TestCompleteFocus(t *testing.T) {
assert.Less(t, len(historyAfter), len(historyBefore))
// Pre-checkpoint messages should be preserved
assert.Equal(t, "hello", historyAfter[0].Content)
assert.Equal(t, "hi", historyAfter[1].Content)
assert.Empty(t, cmp.Diff("hello", historyAfter[0].Content))
assert.Empty(t, cmp.Diff("hi", historyAfter[1].Content))
// Knowledge should be persisted
knowledgeRaw, _ := delegate.GetKV(ctx, focusAgentID, knowledgeKVPrefix+sk)
@ -140,7 +141,7 @@ func TestCompleteFocus(t *testing.T) {
var kb KnowledgeBlock
require.NoError(t, jsonv2.Unmarshal([]byte(knowledgeRaw), &kb))
require.Len(t, kb.Entries, 1)
assert.Equal(t, "debug auth", kb.Entries[0].Topic)
assert.Empty(t, cmp.Diff("debug auth", kb.Entries[0].Topic))
assert.Contains(t, kb.Entries[0].Summary, "token validation")
// Focus state should be cleaned up
@ -192,8 +193,8 @@ func TestCompleteFocus_MultipleKnowledgeEntries(t *testing.T) {
var kb KnowledgeBlock
require.NoError(t, jsonv2.Unmarshal([]byte(knowledgeRaw), &kb))
require.Len(t, kb.Entries, 2)
assert.Equal(t, "topic A", kb.Entries[0].Topic)
assert.Equal(t, "topic B", kb.Entries[1].Topic)
assert.Empty(t, cmp.Diff("topic A", kb.Entries[0].Topic))
assert.Empty(t, cmp.Diff("topic B", kb.Entries[1].Topic))
}
func TestPruneHistory(t *testing.T) {
@ -255,7 +256,7 @@ func TestPruneHistory(t *testing.T) {
// Pre-checkpoint messages should always be preserved
for i := 0; i < tt.checkpointIdx && i < len(result); i++ {
assert.Equal(t, tt.history[i].Content, result[i].Content)
assert.Empty(t, cmp.Diff(tt.history[i].Content, result[i].Content))
}
})
}

View file

@ -6,6 +6,7 @@ import (
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -85,9 +86,9 @@ func TestLLMMapTool_Execute_Success(t *testing.T) {
Results []map[string]interface{} `json:"results"`
}
require.NoError(t, jsonv2.Unmarshal([]byte(result.ForLLM), &payload))
assert.Equal(t, 2, payload.Count)
assert.Empty(t, cmp.Diff(2, payload.Count))
require.Len(t, payload.Results, 2)
assert.Equal(t, "alpha", payload.Results[0]["label"])
assert.Empty(t, cmp.Diff("alpha", payload.Results[0]["label"]))
}
func TestLLMMapTool_Execute_SchemaValidationFailure(t *testing.T) {

View file

@ -8,6 +8,7 @@ import (
fantasy "charm.land/fantasy"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -110,13 +111,13 @@ func TestLLMMap_WorkerJSONL_StatusAndRead(t *testing.T) {
}))
runID, _ := enqueue["run_id"].(string)
require.NotEmpty(t, runID)
assert.Equal(t, "worker", enqueue["execution_mode"])
assert.Empty(t, cmp.Diff("worker", enqueue["execution_mode"]))
status := decodeResultMap(t, statusTool.Execute(t.Context(), map[string]interface{}{
"run_id": runID,
"process_steps": float64(20),
}))
assert.Equal(t, mapRunStatusSucceeded, status["status"])
assert.Empty(t, cmp.Diff(mapRunStatusSucceeded, status["status"]))
assert.EqualValues(t, 2, status["succeeded_items"])
readJSON := decodeResultMap(t, readTool.Execute(t.Context(), map[string]interface{}{
@ -158,8 +159,8 @@ func TestLLMMap_IdempotencyReuse(t *testing.T) {
first := decodeResultMap(t, mapTool.Execute(t.Context(), args))
second := decodeResultMap(t, mapTool.Execute(t.Context(), args))
assert.Equal(t, first["run_id"], second["run_id"])
assert.Equal(t, true, second["idempotent_reuse"])
assert.Empty(t, cmp.Diff(first["run_id"], second["run_id"]))
assert.Empty(t, cmp.Diff(true, second["idempotent_reuse"]))
}
func TestLLMMap_IdempotencyReuse_Concurrent(t *testing.T) {
@ -248,7 +249,7 @@ func TestLLMMap_InlineRetriesThenSucceeds(t *testing.T) {
"execution_mode": "inline",
"max_retries": float64(2),
}))
assert.Equal(t, mapRunStatusSucceeded, result["status"])
assert.Empty(t, cmp.Diff(mapRunStatusSucceeded, result["status"]))
runID, _ := result["run_id"].(string)
require.NotEmpty(t, runID)
@ -261,7 +262,7 @@ func TestLLMMap_InlineRetriesThenSucceeds(t *testing.T) {
require.Len(t, itemsAny, 1)
item0 := itemsAny[0].(map[string]interface{})
assert.EqualValues(t, 2, item0["attempts"])
assert.Equal(t, mapItemStatusSucceeded, item0["status"])
assert.Empty(t, cmp.Diff(mapItemStatusSucceeded, item0["status"]))
}
func TestLLMMap_InlineExhaustedRetriesFailsRun(t *testing.T) {
@ -282,7 +283,7 @@ func TestLLMMap_InlineExhaustedRetriesFailsRun(t *testing.T) {
"execution_mode": "inline",
"max_retries": float64(1),
}))
assert.Equal(t, mapRunStatusFailed, payload["status"])
assert.Empty(t, cmp.Diff(mapRunStatusFailed, payload["status"]))
summary, ok := payload["summary"].(map[string]interface{})
require.True(t, ok)
assert.EqualValues(t, 1, summary["failure_count"])
@ -360,7 +361,7 @@ func TestAgenticMap_WorkerLifecycle(t *testing.T) {
"run_id": runID,
"process_steps": float64(20),
}))
assert.Equal(t, mapRunStatusSucceeded, status["status"])
assert.Empty(t, cmp.Diff(mapRunStatusSucceeded, status["status"]))
read := decodeResultMap(t, readTool.Execute(t.Context(), map[string]interface{}{
"run_id": runID,
@ -370,7 +371,7 @@ func TestAgenticMap_WorkerLifecycle(t *testing.T) {
require.True(t, ok)
require.Len(t, itemsAny, 1)
item0 := itemsAny[0].(map[string]interface{})
assert.Equal(t, mapItemStatusSucceeded, item0["status"])
assert.Empty(t, cmp.Diff(mapItemStatusSucceeded, item0["status"]))
}
func TestLLMMap_InvalidJSONLIngestFails(t *testing.T) {

View file

@ -5,6 +5,7 @@ import (
"time"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -32,7 +33,7 @@ func TestObligationTool_CreateAndList(t *testing.T) {
var rec ObligationRecord
require.NoError(t, jsonv2.Unmarshal([]byte(create.ForLLM), &rec))
require.NotEmpty(t, rec.ID)
assert.Equal(t, ObligationStateScheduled, rec.State)
assert.Empty(t, cmp.Diff(ObligationStateScheduled, rec.State))
list := tool.Execute(ctx, map[string]interface{}{"action": "list"})
require.NotNil(t, list)
@ -110,7 +111,7 @@ func TestObligationTool_StateMachineAndEvidence(t *testing.T) {
var verified ObligationRecord
require.NoError(t, jsonv2.Unmarshal([]byte(toVerified.ForLLM), &verified))
assert.Equal(t, ObligationStateVerified, verified.State)
assert.Empty(t, cmp.Diff(ObligationStateVerified, verified.State))
assert.NotZero(t, verified.VerifiedAt)
require.Len(t, verified.Evidence, 1)
}
@ -133,15 +134,15 @@ func TestObligationTool_CollectDueObligations_TransitionsScheduledToDue(t *testi
var created ObligationRecord
require.NoError(t, jsonv2.Unmarshal([]byte(create.ForLLM), &created))
require.Equal(t, ObligationStateScheduled, created.State)
assert.Empty(t, cmp.Diff(ObligationStateScheduled, created.State))
due, err := tool.CollectDueObligations(ctx, time.Now().UTC(), "heartbeat")
require.NoError(t, err)
require.Len(t, due, 1)
assert.Equal(t, created.ID, due[0].ID)
assert.Equal(t, ObligationStateDue, due[0].State)
assert.Empty(t, cmp.Diff(created.ID, due[0].ID))
assert.Empty(t, cmp.Diff(ObligationStateDue, due[0].State))
require.NotEmpty(t, due[0].Evidence)
assert.Equal(t, "heartbeat", due[0].Evidence[0].Source)
assert.Empty(t, cmp.Diff("heartbeat", due[0].Evidence[0].Source))
get := tool.Execute(ctx, map[string]interface{}{
"action": "get",
@ -151,7 +152,7 @@ func TestObligationTool_CollectDueObligations_TransitionsScheduledToDue(t *testi
var persisted ObligationRecord
require.NoError(t, jsonv2.Unmarshal([]byte(get.ForLLM), &persisted))
assert.Equal(t, ObligationStateDue, persisted.State)
assert.Empty(t, cmp.Diff(ObligationStateDue, persisted.State))
require.Len(t, persisted.Evidence, 1)
}
@ -182,5 +183,5 @@ func TestObligationTool_CollectDueObligations_DoesNotDuplicateDueEvidence(t *tes
require.NoError(t, err)
require.Len(t, second, 1)
require.Len(t, second[0].Evidence, 1, "due transition evidence should be appended only once")
assert.Equal(t, created.ID, second[0].ID)
assert.Empty(t, cmp.Diff(created.ID, second[0].ID))
}

View file

@ -3,6 +3,7 @@ package tools
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -10,7 +11,7 @@ import (
func TestKeywordSearchTool_Metadata(t *testing.T) {
t.Parallel()
tool := &KeywordSearchTool{agentID: "test"}
assert.Equal(t, "keyword_search", tool.Name())
assert.Empty(t, cmp.Diff("keyword_search", tool.Name()))
assert.Contains(t, tool.Description(), "FTS5")
params := tool.Parameters()
require.NotNil(t, params)
@ -30,7 +31,7 @@ func TestKeywordSearchTool_MissingQuery(t *testing.T) {
func TestSemanticSearchTool_Metadata(t *testing.T) {
t.Parallel()
tool := &SemanticSearchTool{agentID: "test"}
assert.Equal(t, "semantic_search", tool.Name())
assert.Empty(t, cmp.Diff("semantic_search", tool.Name()))
assert.Contains(t, tool.Description(), "semantic similarity")
params := tool.Parameters()
require.NotNil(t, params)
@ -46,7 +47,7 @@ func TestSemanticSearchTool_MissingQuery(t *testing.T) {
func TestChunkReadTool_Metadata(t *testing.T) {
t.Parallel()
tool := &ChunkReadTool{agentID: "test"}
assert.Equal(t, "chunk_read", tool.Name())
assert.Empty(t, cmp.Diff("chunk_read", tool.Name()))
assert.Contains(t, tool.Description(), "full content")
params := tool.Parameters()
require.NotNil(t, params)

View file

@ -6,6 +6,7 @@ import (
"testing"
"github.com/ZanzyTHEbar/dragonscale/pkg/skills"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -62,7 +63,7 @@ func TestSkillSearchTool(t *testing.T) {
loader := setupTestSkills(t)
tool := NewSkillSearchTool(loader)
assert.Equal(t, "skill_search", tool.Name())
assert.Empty(t, cmp.Diff("skill_search", tool.Name()))
t.Run("finds matching skills", func(t *testing.T) {
result := tool.Execute(t.Context(), map[string]interface{}{"query": "trading"})
@ -88,7 +89,7 @@ func TestSkillReadTool(t *testing.T) {
loader := setupTestSkills(t)
tool := NewSkillReadTool(loader)
assert.Equal(t, "skill_read", tool.Name())
assert.Empty(t, cmp.Diff("skill_read", tool.Name()))
t.Run("reads existing skill", func(t *testing.T) {
result := tool.Execute(t.Context(), map[string]interface{}{"name": "risk-management"})
@ -114,7 +115,7 @@ func TestSkillTraverseTool(t *testing.T) {
loader := setupTestSkills(t)
tool := NewSkillTraverseTool(loader)
assert.Equal(t, "skill_traverse", tool.Name())
assert.Empty(t, cmp.Diff("skill_traverse", tool.Name()))
t.Run("traverses links at depth 1", func(t *testing.T) {
result := tool.Execute(t.Context(), map[string]interface{}{"name": "risk-management"})

View file

@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -67,7 +68,7 @@ func TestWorker_RunOnce_HandlerCalled(t *testing.T) {
err := worker.RunOnce(ctx, q, opts)
require.NoError(t, err)
assert.Equal(t, int32(1), called.Load(), "handler must be called once")
assert.Empty(t, cmp.Diff(int32(1), called.Load()), "handler must be called once")
}
func TestWorker_RunOnce_JobMarkedSucceeded(t *testing.T) {
@ -90,7 +91,7 @@ func TestWorker_RunOnce_JobMarkedSucceeded(t *testing.T) {
done, err := q.GetJob(ctx, sqlc.GetJobParams{ID: job.ID})
require.NoError(t, err)
assert.Equal(t, "succeeded", done.Status)
assert.Empty(t, cmp.Diff("succeeded", done.Status))
}
func TestWorker_RunOnce_HandlerError_Requeued(t *testing.T) {
@ -114,8 +115,8 @@ func TestWorker_RunOnce_HandlerError_Requeued(t *testing.T) {
requeued, err := q.GetJob(ctx, sqlc.GetJobParams{ID: job.ID})
require.NoError(t, err)
assert.Equal(t, "queued", requeued.Status, "job should be requeued after transient failure")
assert.Equal(t, int64(1), requeued.Attempts, "attempt count must increment")
assert.Empty(t, cmp.Diff("queued", requeued.Status), "job should be requeued after transient failure")
assert.Empty(t, cmp.Diff(int64(1), requeued.Attempts), "attempt count must increment")
assert.NotNil(t, requeued.LastError)
}
@ -146,7 +147,7 @@ func TestWorker_RunOnce_MaxAttemptsExhausted_MarkedFailed(t *testing.T) {
failed++
}
}
assert.Equal(t, 1, failed, "job should be permanently failed after exhausting max attempts")
assert.Empty(t, cmp.Diff(1, failed), "job should be permanently failed after exhausting max attempts")
}
func TestWorker_RunOnce_UnknownKind_MarkedFailed(t *testing.T) {
@ -171,7 +172,7 @@ func TestWorker_RunOnce_UnknownKind_MarkedFailed(t *testing.T) {
failed++
}
}
assert.Equal(t, 1, failed, "unknown kind should permanently fail the job")
assert.Empty(t, cmp.Diff(1, failed), "unknown kind should permanently fail the job")
}
func TestWorker_RunOnce_NilQueries(t *testing.T) {
@ -224,5 +225,5 @@ func TestWorker_RunLoop_ProcessesJobs(t *testing.T) {
}
_ = worker.RunLoop(ctx, q, opts)
assert.Equal(t, int32(total), processed.Load(), "all jobs must be processed")
assert.Empty(t, cmp.Diff(int32(total), processed.Load()), "all jobs must be processed")
}