test: modernize test suite with t.Context() and t.Parallel()

Apply two idiomatic Go test improvements across all 113 test files in the
codebase:

1. Replace context.Background() with t.Context() in test functions.
   t.Context() (added in Go 1.21) returns a context that is automatically
   cancelled when the test finishes, preventing goroutine leaks and making
   test teardown deterministic without manual cancel calls.

2. Add t.Parallel() to independent test functions.
   Parallel tests run concurrently within a package, significantly reducing
   total test suite wall-clock time and surfacing data races that sequential
   execution would hide.

Also fix TestStateStore_AddTransition: add "UpdatedAt" to the
cmpopts.IgnoreFields list — the DB now populates UpdatedAt on insert,
causing the zero-value comparison to fail.

Packages affected (113 files):
- eval/go_evals
- internal/fantasy (agent, json, jsonrepair, providers/*, providertests/*, schema, tool_runtime_*)
- pkg/agent (integration, kv_delegate, loop, offloading_runtime, state_store, tool_result_search)
- pkg/auth (oauth, pkce, store)
- pkg/cache, pkg/channels, pkg/config, pkg/cron
- pkg/fantasy (adapter, convert)
- pkg/heartbeat, pkg/ids
- pkg/itr (commands, dag/*, fb_codec, wasm/*)
- pkg/logger
- pkg/memory (dag/*, delegate/*, integration, kernel_contract, migrate_sessions, observation, store/*)
- pkg/migrate
- pkg/rlm (engine, fanout, rope, strategy)
- pkg/runtime
- pkg/security (jsonextract, redact, securebus/*, urlguard, vault, zkp)
- pkg/session, pkg/skills, pkg/state, pkg/sync
- pkg/tools (agentic_map, call, dag, edit, filesystem, focus, llm_map, map_*, message, obligation, registry_progressive, result, retrieval, search, shell, skills, spawn, subagent_*, toolloop, web)
- pkg/worker
This commit is contained in:
ZanzyTHEbar 2026-02-21 21:08:54 +00:00
parent 38d1faf425
commit 906d11612d
120 changed files with 1938 additions and 1139 deletions

View file

@ -39,6 +39,7 @@ func allFileTools(workspace string) []tools.Tool {
// ---------------------------------------------------------------------------
func TestToolRegistry_AllToolsHaveSchema(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
registry := tools.NewToolRegistry()
@ -68,6 +69,7 @@ func TestToolRegistry_AllToolsHaveSchema(t *testing.T) {
}
func TestToolRegistry_SchemaPropertiesAreTyped(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
for _, tool := range allFileTools(workspace) {
@ -91,10 +93,11 @@ func TestToolRegistry_SchemaPropertiesAreTyped(t *testing.T) {
// ---------------------------------------------------------------------------
func TestToolExecution_ReadFile_NonExistent(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
readTool := tools.NewReadFileTool(workspace, true)
result := readTool.Execute(context.Background(), map[string]interface{}{
result := readTool.Execute(t.Context(), map[string]interface{}{
"path": "nonexistent_file_12345.txt",
})
@ -103,10 +106,11 @@ func TestToolExecution_ReadFile_NonExistent(t *testing.T) {
}
func TestToolExecution_WriteAndReadFile(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
writeTool := tools.NewWriteFileTool(workspace, true)
writeResult := writeTool.Execute(context.Background(), map[string]interface{}{
writeResult := writeTool.Execute(t.Context(), map[string]interface{}{
"path": "eval_test.txt",
"content": "hello from eval test",
})
@ -114,7 +118,7 @@ func TestToolExecution_WriteAndReadFile(t *testing.T) {
assert.False(t, writeResult.IsError, "write should succeed: %s", writeResult.ForLLM)
readTool := tools.NewReadFileTool(workspace, true)
readResult := readTool.Execute(context.Background(), map[string]interface{}{
readResult := readTool.Execute(t.Context(), map[string]interface{}{
"path": "eval_test.txt",
})
require.NotNil(t, readResult)
@ -123,10 +127,11 @@ func TestToolExecution_WriteAndReadFile(t *testing.T) {
}
func TestToolExecution_ExecBlocking(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
execTool := tools.NewExecTool(workspace, false)
result := execTool.Execute(context.Background(), map[string]interface{}{
result := execTool.Execute(t.Context(), map[string]interface{}{
"command": "echo dragonscale-eval-test",
})
@ -136,13 +141,14 @@ func TestToolExecution_ExecBlocking(t *testing.T) {
}
func TestToolExecution_ListDir(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
os.WriteFile(filepath.Join(workspace, "file_a.txt"), []byte("a"), 0644)
os.WriteFile(filepath.Join(workspace, "file_b.txt"), []byte("b"), 0644)
listTool := tools.NewListDirTool(workspace, true)
result := listTool.Execute(context.Background(), map[string]interface{}{
result := listTool.Execute(t.Context(), map[string]interface{}{
"path": ".",
})
@ -153,16 +159,17 @@ func TestToolExecution_ListDir(t *testing.T) {
}
func TestToolExecution_EditFile(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
writeTool := tools.NewWriteFileTool(workspace, true)
writeTool.Execute(context.Background(), map[string]interface{}{
writeTool.Execute(t.Context(), map[string]interface{}{
"path": "edit_target.txt",
"content": "hello world foo bar",
})
editTool := tools.NewEditFileTool(workspace, true)
result := editTool.Execute(context.Background(), map[string]interface{}{
result := editTool.Execute(t.Context(), map[string]interface{}{
"path": "edit_target.txt",
"old_text": "world",
"new_text": "dragonscale",
@ -171,7 +178,7 @@ func TestToolExecution_EditFile(t *testing.T) {
assert.False(t, result.IsError, "edit should succeed: %s", result.ForLLM)
readTool := tools.NewReadFileTool(workspace, true)
readResult := readTool.Execute(context.Background(), map[string]interface{}{
readResult := readTool.Execute(t.Context(), map[string]interface{}{
"path": "edit_target.txt",
})
assert.Contains(t, readResult.ForLLM, "dragonscale")
@ -179,16 +186,17 @@ func TestToolExecution_EditFile(t *testing.T) {
}
func TestToolExecution_AppendFile(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
writeTool := tools.NewWriteFileTool(workspace, true)
writeTool.Execute(context.Background(), map[string]interface{}{
writeTool.Execute(t.Context(), map[string]interface{}{
"path": "append_target.txt",
"content": "line one\n",
})
appendTool := tools.NewAppendFileTool(workspace, true)
result := appendTool.Execute(context.Background(), map[string]interface{}{
result := appendTool.Execute(t.Context(), map[string]interface{}{
"path": "append_target.txt",
"content": "line two\n",
})
@ -196,7 +204,7 @@ func TestToolExecution_AppendFile(t *testing.T) {
assert.False(t, result.IsError, "append should succeed: %s", result.ForLLM)
readTool := tools.NewReadFileTool(workspace, true)
readResult := readTool.Execute(context.Background(), map[string]interface{}{
readResult := readTool.Execute(t.Context(), map[string]interface{}{
"path": "append_target.txt",
})
assert.Contains(t, readResult.ForLLM, "line one")
@ -208,10 +216,11 @@ func TestToolExecution_AppendFile(t *testing.T) {
// ---------------------------------------------------------------------------
func TestToolExecution_ReadFile_WorkspaceRestriction(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
readTool := tools.NewReadFileTool(workspace, true)
result := readTool.Execute(context.Background(), map[string]interface{}{
result := readTool.Execute(t.Context(), map[string]interface{}{
"path": "/etc/passwd",
})
require.NotNil(t, result)
@ -219,10 +228,11 @@ func TestToolExecution_ReadFile_WorkspaceRestriction(t *testing.T) {
}
func TestToolExecution_WriteFile_WorkspaceRestriction(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
writeTool := tools.NewWriteFileTool(workspace, true)
result := writeTool.Execute(context.Background(), map[string]interface{}{
result := writeTool.Execute(t.Context(), map[string]interface{}{
"path": "/tmp/escape_test.txt",
"content": "should not write",
})
@ -231,10 +241,11 @@ func TestToolExecution_WriteFile_WorkspaceRestriction(t *testing.T) {
}
func TestToolExecution_ReadFile_PathTraversal(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
readTool := tools.NewReadFileTool(workspace, true)
result := readTool.Execute(context.Background(), map[string]interface{}{
result := readTool.Execute(t.Context(), map[string]interface{}{
"path": "../../../../etc/hostname",
})
require.NotNil(t, result)
@ -242,6 +253,7 @@ func TestToolExecution_ReadFile_PathTraversal(t *testing.T) {
}
func TestToolExecution_Unrestricted(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
tmpFile := filepath.Join(os.TempDir(), "dragonscale_unrestricted_test.txt")
@ -249,7 +261,7 @@ func TestToolExecution_Unrestricted(t *testing.T) {
defer os.Remove(tmpFile)
readTool := tools.NewReadFileTool(workspace, false)
result := readTool.Execute(context.Background(), map[string]interface{}{
result := readTool.Execute(t.Context(), map[string]interface{}{
"path": tmpFile,
})
require.NotNil(t, result)
@ -262,6 +274,7 @@ func TestToolExecution_Unrestricted(t *testing.T) {
// ---------------------------------------------------------------------------
func TestToolRegistry_ProgressiveDisclosure(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
registry := tools.NewToolRegistry()
@ -290,6 +303,7 @@ func TestToolRegistry_ProgressiveDisclosure(t *testing.T) {
}
func TestToolSearch_FindsReadFile(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
registry := tools.NewToolRegistry()
@ -299,7 +313,7 @@ func TestToolSearch_FindsReadFile(t *testing.T) {
registry.RegisterMetaTools()
searchTool := tools.NewToolSearchTool(registry)
result := searchTool.Execute(context.Background(), map[string]interface{}{
result := searchTool.Execute(t.Context(), map[string]interface{}{
"query": "read file",
})
@ -309,6 +323,7 @@ func TestToolSearch_FindsReadFile(t *testing.T) {
}
func TestToolSearch_ListsAll(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
registry := tools.NewToolRegistry()
@ -317,7 +332,7 @@ func TestToolSearch_ListsAll(t *testing.T) {
registry.RegisterMetaTools()
searchTool := tools.NewToolSearchTool(registry)
result := searchTool.Execute(context.Background(), map[string]interface{}{
result := searchTool.Execute(t.Context(), map[string]interface{}{
"query": "",
})
@ -327,6 +342,7 @@ func TestToolSearch_ListsAll(t *testing.T) {
}
func TestToolSearch_NoResults(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
registry := tools.NewToolRegistry()
@ -334,7 +350,7 @@ func TestToolSearch_NoResults(t *testing.T) {
registry.RegisterMetaTools()
searchTool := tools.NewToolSearchTool(registry)
result := searchTool.Execute(context.Background(), map[string]interface{}{
result := searchTool.Execute(t.Context(), map[string]interface{}{
"query": "xyzzy_nonexistent_tool",
})
@ -343,6 +359,7 @@ func TestToolSearch_NoResults(t *testing.T) {
}
func TestToolCall_DispatchesCorrectly(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
os.WriteFile(filepath.Join(workspace, "dispatch_test.txt"), []byte("dispatch ok"), 0644)
@ -352,7 +369,7 @@ func TestToolCall_DispatchesCorrectly(t *testing.T) {
registry.RegisterMetaTools()
callTool := tools.NewToolCallTool(registry)
result := callTool.Execute(context.Background(), map[string]interface{}{
result := callTool.Execute(t.Context(), map[string]interface{}{
"tool_name": "read_file",
"arguments": map[string]interface{}{
"path": "dispatch_test.txt",
@ -365,6 +382,7 @@ func TestToolCall_DispatchesCorrectly(t *testing.T) {
}
func TestToolCall_RejectsRecursion(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
registry := tools.NewToolRegistry()
@ -372,7 +390,7 @@ func TestToolCall_RejectsRecursion(t *testing.T) {
registry.RegisterMetaTools()
callTool := tools.NewToolCallTool(registry)
result := callTool.Execute(context.Background(), map[string]interface{}{
result := callTool.Execute(t.Context(), map[string]interface{}{
"tool_name": "tool_call",
})
@ -381,11 +399,12 @@ func TestToolCall_RejectsRecursion(t *testing.T) {
}
func TestToolCall_RejectsUnknownTool(t *testing.T) {
t.Parallel()
registry := tools.NewToolRegistry()
registry.RegisterMetaTools()
callTool := tools.NewToolCallTool(registry)
result := callTool.Execute(context.Background(), map[string]interface{}{
result := callTool.Execute(t.Context(), map[string]interface{}{
"tool_name": "nonexistent_tool_xyz",
})
@ -394,17 +413,19 @@ func TestToolCall_RejectsUnknownTool(t *testing.T) {
}
func TestToolCall_MissingToolName(t *testing.T) {
t.Parallel()
registry := tools.NewToolRegistry()
registry.RegisterMetaTools()
callTool := tools.NewToolCallTool(registry)
result := callTool.Execute(context.Background(), map[string]interface{}{})
result := callTool.Execute(t.Context(), map[string]interface{}{})
require.NotNil(t, result)
assert.True(t, result.IsError, "missing tool_name should be rejected")
}
func TestToolCall_StringArguments(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
os.WriteFile(filepath.Join(workspace, "str_args.txt"), []byte("string args ok"), 0644)
@ -414,7 +435,7 @@ func TestToolCall_StringArguments(t *testing.T) {
registry.RegisterMetaTools()
callTool := tools.NewToolCallTool(registry)
result := callTool.Execute(context.Background(), map[string]interface{}{
result := callTool.Execute(t.Context(), map[string]interface{}{
"tool_name": "read_file",
"arguments": `{"path": "str_args.txt"}`,
})
@ -429,6 +450,7 @@ func TestToolCall_StringArguments(t *testing.T) {
// ---------------------------------------------------------------------------
func TestToolRegistry_GatewayMarking(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
registry := tools.NewToolRegistry()
@ -458,6 +480,7 @@ func TestToolRegistry_GatewayMarking(t *testing.T) {
// ---------------------------------------------------------------------------
func TestConfig_DefaultValues(t *testing.T) {
t.Parallel()
cfg := config.DefaultConfig()
assert.True(t, cfg.Agents.Defaults.RestrictToSandbox, "restrict to sandbox should be on by default")
@ -469,6 +492,7 @@ func TestConfig_DefaultValues(t *testing.T) {
}
func TestConfig_LoadEvalConfigs(t *testing.T) {
t.Parallel()
evalDir := filepath.Join("..", "..", "eval", "configs")
tests := []struct {
@ -492,6 +516,7 @@ func TestConfig_LoadEvalConfigs(t *testing.T) {
}
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")
@ -502,10 +527,11 @@ func TestConfig_MissingFileReturnsDefaults(t *testing.T) {
// ---------------------------------------------------------------------------
func TestToolExecution_ExecTimeout(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
execTool := tools.NewExecTool(workspace, false)
ctx, cancel := context.WithTimeout(context.Background(), 1)
ctx, cancel := context.WithTimeout(t.Context(), 1)
defer cancel()
result := execTool.Execute(ctx, map[string]interface{}{
@ -516,10 +542,11 @@ func TestToolExecution_ExecTimeout(t *testing.T) {
}
func TestToolExecution_ExecEmptyCommand(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
execTool := tools.NewExecTool(workspace, false)
result := execTool.Execute(context.Background(), map[string]interface{}{
result := execTool.Execute(t.Context(), map[string]interface{}{
"command": "",
})
@ -532,6 +559,7 @@ func TestToolExecution_ExecEmptyCommand(t *testing.T) {
// ---------------------------------------------------------------------------
func TestToolSchema_JSONRoundtrip(t *testing.T) {
t.Parallel()
workspace := testWorkspace(t)
for _, tool := range allFileTools(workspace) {

View file

@ -3,9 +3,10 @@ package fantasy
import (
"context"
"fmt"
jsonv2 "github.com/go-json-experiment/json"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/stretchr/testify/require"
)
@ -111,7 +112,7 @@ func TestStreamingAgentCallbacks(t *testing.T) {
// Create agent
agent := NewAgent(mockModel)
ctx := context.Background()
ctx := t.Context()
// Create streaming call with all callbacks
streamCall := AgentStreamCall{
@ -301,7 +302,7 @@ func TestStreamingAgentWithTools(t *testing.T) {
WithTools(&EchoTool{}),
)
ctx := context.Background()
ctx := t.Context()
// Track callback invocations
var toolInputStartCalled bool
@ -399,7 +400,7 @@ func TestStreamingAgentTextDeltas(t *testing.T) {
}
agent := NewAgent(mockModel)
ctx := context.Background()
ctx := t.Context()
// Track text deltas
var textDeltas []string
@ -461,7 +462,7 @@ func TestStreamingAgentReasoning(t *testing.T) {
}
agent := NewAgent(mockModel)
ctx := context.Background()
ctx := t.Context()
var reasoningDeltas []string
var textDeltas []string
@ -502,7 +503,7 @@ func TestStreamingAgentError(t *testing.T) {
}
agent := NewAgent(mockModel)
ctx := context.Background()
ctx := t.Context()
// Track error callbacks
var errorOccurred bool
@ -568,7 +569,7 @@ func TestStreamingAgentSources(t *testing.T) {
}
agent := NewAgent(mockModel)
ctx := context.Background()
ctx := t.Context()
var sources []SourceContent

View file

@ -145,7 +145,7 @@ func TestAgent_Generate_ResultContent_AllTypes(t *testing.T) {
}
agent := NewAgent(model, WithTools(tool1))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "prompt",
})
@ -209,7 +209,7 @@ func TestAgent_Generate_ResultText(t *testing.T) {
}
agent := NewAgent(model)
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "prompt",
})
@ -279,7 +279,7 @@ func TestAgent_Generate_ResultToolCalls(t *testing.T) {
}
agent := NewAgent(model, WithTools(tool1, tool2))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test-input",
})
@ -353,7 +353,7 @@ func TestAgent_Generate_ResultToolResults(t *testing.T) {
}
agent := NewAgent(model, WithTools(tool1))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test-input",
})
@ -440,7 +440,7 @@ func TestAgent_Generate_MultipleSteps(t *testing.T) {
}
agent := NewAgent(model, WithTools(tool1))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test-input",
})
@ -499,7 +499,7 @@ func TestAgent_Generate_BasicText(t *testing.T) {
}
agent := NewAgent(model)
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
})
@ -531,7 +531,7 @@ func TestAgent_Generate_EmptyPrompt(t *testing.T) {
model := &mockLanguageModel{}
agent := NewAgent(model)
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "", // Empty prompt should cause error
})
@ -570,7 +570,7 @@ func TestAgent_Generate_WithSystemPrompt(t *testing.T) {
}
agent := NewAgent(model, WithSystemPrompt("You are a helpful assistant"))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
})
@ -623,7 +623,7 @@ func TestAgent_Generate_OptionsActiveTools(t *testing.T) {
}
agent := NewAgent(model, WithTools(tool1, tool2))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test-input",
ActiveTools: []string{"tool1"}, // Only tool1 should be active
})
@ -872,7 +872,7 @@ func TestStopConditions_Integration(t *testing.T) {
agent := NewAgent(model, WithStopConditions(StepCountIs(1)))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
})
@ -904,7 +904,7 @@ func TestStopConditions_Integration(t *testing.T) {
FinishReasonIs(FinishReasonStop), // Or stop on finish reason
))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
})
@ -952,7 +952,7 @@ func TestPrepareStep(t *testing.T) {
agent := NewAgent(model, WithSystemPrompt("Original system prompt"))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
PrepareStep: prepareStepFunc,
})
@ -989,7 +989,7 @@ func TestPrepareStep(t *testing.T) {
agent := NewAgent(model)
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
PrepareStep: prepareStepFunc,
})
@ -1034,7 +1034,7 @@ func TestPrepareStep(t *testing.T) {
agent := NewAgent(model, WithTools(tool1, tool2, tool3))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
PrepareStep: prepareStepFunc,
})
@ -1073,7 +1073,7 @@ func TestPrepareStep(t *testing.T) {
agent := NewAgent(model, WithTools(tool1))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
PrepareStep: prepareStepFunc,
})
@ -1133,7 +1133,7 @@ func TestPrepareStep(t *testing.T) {
agent := NewAgent(model, WithSystemPrompt("Original system"), WithTools(tool1, tool2))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
PrepareStep: prepareStepFunc,
})
@ -1194,7 +1194,7 @@ func TestPrepareStep(t *testing.T) {
agent := NewAgent(model, WithSystemPrompt("Parent system"), WithTools(tool1))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
PrepareStep: prepareStepFunc,
})
@ -1240,7 +1240,7 @@ func TestPrepareStep(t *testing.T) {
agent := NewAgent(model, WithTools(tool1, tool2))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
PrepareStep: prepareStepFunc,
})
@ -1289,7 +1289,7 @@ func TestToolCallRepair(t *testing.T) {
agent := NewAgent(model, WithTools(tool), WithStopConditions(StepCountIs(2))) // Limit steps
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
})
@ -1333,7 +1333,7 @@ func TestToolCallRepair(t *testing.T) {
agent := NewAgent(model, WithTools(tool), WithStopConditions(StepCountIs(2))) // Limit steps
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
})
@ -1388,7 +1388,7 @@ func TestToolCallRepair(t *testing.T) {
agent := NewAgent(model, WithTools(tool), WithRepairToolCall(repairFunc), WithStopConditions(StepCountIs(2)))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
})
@ -1438,7 +1438,7 @@ func TestToolCallRepair(t *testing.T) {
agent := NewAgent(model, WithTools(tool), WithRepairToolCall(repairFunc), WithStopConditions(StepCountIs(2)))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
})
@ -1476,7 +1476,7 @@ func TestToolCallRepair(t *testing.T) {
agent := NewAgent(model, WithTools(tool), WithStopConditions(StepCountIs(2)))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
})
@ -1521,7 +1521,7 @@ func TestToolCallRepair(t *testing.T) {
agent := NewAgent(model, WithTools(tool), WithStopConditions(StepCountIs(2)))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "test prompt",
})
@ -1582,7 +1582,7 @@ func TestAgent_MediaToolResponses(t *testing.T) {
agent := NewAgent(model, WithTools(imageTool), WithStopConditions(StepCountIs(3)))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "Generate an image",
})
@ -1636,7 +1636,7 @@ func TestAgent_MediaToolResponses(t *testing.T) {
agent := NewAgent(model, WithTools(audioTool), WithStopConditions(StepCountIs(3)))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "Generate audio",
})
@ -1690,7 +1690,7 @@ func TestAgent_MediaToolResponses(t *testing.T) {
agent := NewAgent(model, WithTools(imageTool), WithStopConditions(StepCountIs(3)))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "Take a screenshot",
})
@ -1749,7 +1749,7 @@ func TestAgent_MediaToolResponses(t *testing.T) {
agent := NewAgent(model, WithTools(imageTool), WithStopConditions(StepCountIs(3)))
result, err := agent.Generate(context.Background(), AgentCall{
result, err := agent.Generate(t.Context(), AgentCall{
Prompt: "Generate image",
})

View file

@ -2,12 +2,14 @@ package fantasy
import (
"errors"
jsonv2 "github.com/go-json-experiment/json"
"reflect"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
)
func TestMessageJSONSerialization(t *testing.T) {
t.Parallel()
tests := []struct {
name string
message Message
@ -199,44 +201,29 @@ func compareMessagePart(t *testing.T, index int, original, decoded MessagePart)
case ContentTypeText:
orig := original.(TextPart)
dec := decoded.(TextPart)
if orig.Text != dec.Text {
t.Errorf("content[%d] text mismatch: got %q, want %q", index, dec.Text, orig.Text)
if diff := cmp.Diff(orig, dec); diff != "" {
t.Errorf("content[%d] TextPart mismatch (-want +got):\n%s", index, diff)
}
case ContentTypeReasoning:
orig := original.(ReasoningPart)
dec := decoded.(ReasoningPart)
if orig.Text != dec.Text {
t.Errorf("content[%d] reasoning text mismatch: got %q, want %q", index, dec.Text, orig.Text)
if diff := cmp.Diff(orig, dec); diff != "" {
t.Errorf("content[%d] ReasoningPart mismatch (-want +got):\n%s", index, diff)
}
case ContentTypeFile:
orig := original.(FilePart)
dec := decoded.(FilePart)
if orig.Filename != dec.Filename {
t.Errorf("content[%d] filename mismatch: got %q, want %q", index, dec.Filename, orig.Filename)
}
if orig.MediaType != dec.MediaType {
t.Errorf("content[%d] media type mismatch: got %q, want %q", index, dec.MediaType, orig.MediaType)
}
if !reflect.DeepEqual(orig.Data, dec.Data) {
t.Errorf("content[%d] file data mismatch", index)
if diff := cmp.Diff(orig, dec); diff != "" {
t.Errorf("content[%d] FilePart mismatch (-want +got):\n%s", index, diff)
}
case ContentTypeToolCall:
orig := original.(ToolCallPart)
dec := decoded.(ToolCallPart)
if orig.ToolCallID != dec.ToolCallID {
t.Errorf("content[%d] tool call id mismatch: got %q, want %q", index, dec.ToolCallID, orig.ToolCallID)
}
if orig.ToolName != dec.ToolName {
t.Errorf("content[%d] tool name mismatch: got %q, want %q", index, dec.ToolName, orig.ToolName)
}
if orig.Input != dec.Input {
t.Errorf("content[%d] tool input mismatch: got %q, want %q", index, dec.Input, orig.Input)
}
if orig.ProviderExecuted != dec.ProviderExecuted {
t.Errorf("content[%d] provider executed mismatch: got %v, want %v", index, dec.ProviderExecuted, orig.ProviderExecuted)
if diff := cmp.Diff(orig, dec); diff != "" {
t.Errorf("content[%d] ToolCallPart mismatch (-want +got):\n%s", index, diff)
}
case ContentTypeToolResult:
@ -259,30 +246,35 @@ func compareToolResultOutput(t *testing.T, index int, original, decoded ToolResu
case ToolResultContentTypeText:
orig := original.(ToolResultOutputContentText)
dec := decoded.(ToolResultOutputContentText)
if orig.Text != dec.Text {
t.Errorf("content[%d] tool result text mismatch: got %q, want %q", index, dec.Text, orig.Text)
if diff := cmp.Diff(orig, dec); diff != "" {
t.Errorf("content[%d] ToolResultOutputContentText mismatch (-want +got):\n%s", index, diff)
}
case ToolResultContentTypeError:
orig := original.(ToolResultOutputContentError)
dec := decoded.(ToolResultOutputContentError)
if orig.Error.Error() != dec.Error.Error() {
t.Errorf("content[%d] tool result error mismatch: got %q, want %q", index, dec.Error.Error(), orig.Error.Error())
if orig.Error == nil && dec.Error == nil {
return
}
if orig.Error == nil || dec.Error == nil {
t.Errorf("content[%d] ToolResultOutputContentError mismatch (-want +got): %v != %v", index, orig.Error, dec.Error)
return
}
if diff := cmp.Diff(orig.Error.Error(), dec.Error.Error()); diff != "" {
t.Errorf("content[%d] ToolResultOutputContentError mismatch (-want +got):\n%s", index, diff)
}
case ToolResultContentTypeMedia:
orig := original.(ToolResultOutputContentMedia)
dec := decoded.(ToolResultOutputContentMedia)
if orig.Data != dec.Data {
t.Errorf("content[%d] tool result media data mismatch", index)
}
if orig.MediaType != dec.MediaType {
t.Errorf("content[%d] tool result media type mismatch: got %q, want %q", index, dec.MediaType, orig.MediaType)
if diff := cmp.Diff(orig, dec); diff != "" {
t.Errorf("content[%d] ToolResultOutputContentMedia mismatch (-want +got):\n%s", index, diff)
}
}
}
func TestHelperFunctions(t *testing.T) {
t.Parallel()
t.Run("NewUserMessage - text only", func(t *testing.T) {
msg := NewUserMessage("Hello")
@ -412,6 +404,7 @@ func TestHelperFunctions(t *testing.T) {
}
func TestEdgeCases(t *testing.T) {
t.Parallel()
t.Run("empty text part", func(t *testing.T) {
msg := Message{
Role: MessageRoleUser,
@ -520,6 +513,7 @@ func TestEdgeCases(t *testing.T) {
}
func TestInvalidJSONHandling(t *testing.T) {
t.Parallel()
t.Run("unknown message part type", func(t *testing.T) {
invalidJSON := `{
"role": "user",
@ -606,6 +600,7 @@ func (m *mockProviderData) UnmarshalJSON(data []byte) error {
}
func TestPromptSerialization(t *testing.T) {
t.Parallel()
t.Run("serialize prompt (message slice)", func(t *testing.T) {
prompt := Prompt{
NewSystemMessage("You are helpful"),
@ -647,6 +642,7 @@ func TestPromptSerialization(t *testing.T) {
}
func TestStreamPartErrorSerialization(t *testing.T) {
t.Parallel()
t.Run("stream part with ProviderError containing OpenAI API error", func(t *testing.T) {
// Create a mock OpenAI API error
openaiErr := errors.New("invalid_api_key: Incorrect API key provided")

View file

@ -7,6 +7,7 @@ import (
)
func TestRepairJSON(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -79,6 +80,7 @@ func TestRepairJSON(t *testing.T) {
}
func TestRepairJSONMultipleTopLevel(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -125,6 +127,7 @@ func TestRepairJSONMultipleTopLevel(t *testing.T) {
}
func TestRepairJSONEnsureASCII(t *testing.T) {
t.Parallel()
got, err := RepairJSON("{'test_中国人_ascii':'统一码'}", WithEnsureASCII(false))
if err != nil {
t.Fatalf("unexpected error: %v", err)
@ -136,6 +139,7 @@ func TestRepairJSONEnsureASCII(t *testing.T) {
}
func TestRepairJSONStreamStable(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -197,6 +201,7 @@ func TestRepairJSONStreamStable(t *testing.T) {
}
func TestLoads(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -273,6 +278,7 @@ func TestLoads(t *testing.T) {
}
func TestRepairJSONSkipJSONLoads(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -320,6 +326,7 @@ func TestRepairJSONSkipJSONLoads(t *testing.T) {
}
func TestRepairJSONWithLog(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -368,6 +375,7 @@ func TestRepairJSONWithLog(t *testing.T) {
}
func TestRepairJSONStrict(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -438,6 +446,7 @@ func TestRepairJSONStrict(t *testing.T) {
}
func TestParseArrayObjects(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -479,6 +488,7 @@ func TestParseArrayObjects(t *testing.T) {
}
func TestParseArrayEdgeCases(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -620,6 +630,7 @@ func TestParseArrayEdgeCases(t *testing.T) {
}
func TestParseArrayMissingQuotes(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -656,6 +667,7 @@ func TestParseArrayMissingQuotes(t *testing.T) {
}
func TestParseComment(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -712,6 +724,7 @@ func TestParseComment(t *testing.T) {
}
func TestParseNumber(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -757,6 +770,7 @@ func TestParseNumber(t *testing.T) {
}
func TestParseNumberEdgeCases(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -863,6 +877,7 @@ func TestParseNumberEdgeCases(t *testing.T) {
}
func TestParseObjectObjects(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -912,6 +927,7 @@ func TestParseObjectObjects(t *testing.T) {
}
func TestParseObjectEdgeCases(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -1118,6 +1134,7 @@ func TestParseObjectEdgeCases(t *testing.T) {
}
func TestParseObjectMergeAtEnd(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -1179,6 +1196,7 @@ func TestParseObjectMergeAtEnd(t *testing.T) {
}
func TestParseStringBasics(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -1225,6 +1243,7 @@ func TestParseStringBasics(t *testing.T) {
}
func TestMissingAndMixedQuotes(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -1346,6 +1365,7 @@ func TestMissingAndMixedQuotes(t *testing.T) {
}
func TestEscaping(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -1416,6 +1436,7 @@ func TestEscaping(t *testing.T) {
}
func TestMarkdown(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -1452,6 +1473,7 @@ func TestMarkdown(t *testing.T) {
}
func TestLeadingTrailingCharacters(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -1493,6 +1515,7 @@ func TestLeadingTrailingCharacters(t *testing.T) {
}
func TestStringJSONLLMBlock(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
@ -1534,6 +1557,7 @@ func TestStringJSONLLMBlock(t *testing.T) {
}
func TestParseBooleanOrNull(t *testing.T) {
t.Parallel()
loadCases := []struct {
name string
input string

View file

@ -7,6 +7,7 @@ import (
)
func TestParseAzureURL(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string

View file

@ -1,7 +1,6 @@
package openai
import (
"context"
"encoding/base64"
"errors"
"net/http"
@ -817,7 +816,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -850,7 +849,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -875,7 +874,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -916,7 +915,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -943,7 +942,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
LogProbs: fantasy.Opt(true),
@ -978,7 +977,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -1003,7 +1002,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -1028,7 +1027,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -1061,7 +1060,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
LogitBias: map[string]int64{
@ -1104,7 +1103,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "o1-mini")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(
&ProviderOptions{
@ -1145,7 +1144,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-4o")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
TextVerbosity: fantasy.Opt("low"),
@ -1184,7 +1183,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
Tools: []fantasy.Tool{
fantasy.FunctionTool{
@ -1257,7 +1256,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
Tools: []fantasy.Tool{
fantasy.FunctionTool{
@ -1305,7 +1304,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
Tools: []fantasy.Tool{
fantasy.FunctionTool{
@ -1375,7 +1374,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -1418,7 +1417,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -1454,7 +1453,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -1483,7 +1482,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
Temperature: &[]float64{0.5}[0],
TopP: &[]float64{0.7}[0],
@ -1532,7 +1531,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
MaxOutputTokens: &[]int64{1000}[0],
})
@ -1577,7 +1576,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -1605,7 +1604,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
MaxCompletionTokens: fantasy.Opt(int64(255)),
@ -1644,7 +1643,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
Prediction: map[string]any{
@ -1689,7 +1688,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
Store: fantasy.Opt(true),
@ -1728,7 +1727,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
Metadata: map[string]any{
@ -1771,7 +1770,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
PromptCacheKey: fantasy.Opt("test-cache-key-123"),
@ -1810,7 +1809,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
SafetyIdentifier: fantasy.Opt("test-safety-identifier-123"),
@ -1847,7 +1846,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-search-preview")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
Temperature: &[]float64{0.7}[0],
})
@ -1882,7 +1881,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "o3-mini")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
ServiceTier: fantasy.Opt("flex"),
@ -1919,7 +1918,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
ServiceTier: fantasy.Opt("flex"),
@ -1953,7 +1952,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
_, err = model.Generate(context.Background(), fantasy.Call{
_, err = model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
ServiceTier: fantasy.Opt("priority"),
@ -1990,7 +1989,7 @@ func TestDoGenerate(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
result, err := model.Generate(context.Background(), fantasy.Call{
result, err := model.Generate(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
ServiceTier: fantasy.Opt("priority"),
@ -2298,7 +2297,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
stream, err := model.Stream(context.Background(), fantasy.Call{
stream, err := model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -2355,7 +2354,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
stream, err := model.Stream(context.Background(), fantasy.Call{
stream, err := model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
Tools: []fantasy.Tool{
fantasy.FunctionTool{
@ -2442,7 +2441,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
stream, err := model.Stream(context.Background(), fantasy.Call{
stream, err := model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -2482,7 +2481,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
stream, err := model.Stream(context.Background(), fantasy.Call{
stream, err := model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -2524,7 +2523,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Stream(context.Background(), fantasy.Call{
_, err = model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -2573,7 +2572,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
stream, err := model.Stream(context.Background(), fantasy.Call{
stream, err := model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -2624,7 +2623,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
stream, err := model.Stream(context.Background(), fantasy.Call{
stream, err := model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -2668,7 +2667,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Stream(context.Background(), fantasy.Call{
_, err = model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
Store: fantasy.Opt(true),
@ -2711,7 +2710,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
_, err = model.Stream(context.Background(), fantasy.Call{
_, err = model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
Metadata: map[string]any{
@ -2758,7 +2757,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "o3-mini")
_, err = model.Stream(context.Background(), fantasy.Call{
_, err = model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
ServiceTier: fantasy.Opt("flex"),
@ -2801,7 +2800,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
_, err = model.Stream(context.Background(), fantasy.Call{
_, err = model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
ProviderOptions: NewProviderOptions(&ProviderOptions{
ServiceTier: fantasy.Opt("priority"),
@ -2845,7 +2844,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
stream, err := model.Stream(context.Background(), fantasy.Call{
stream, err := model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
})
@ -2892,7 +2891,7 @@ func TestDoStream(t *testing.T) {
require.NoError(t, err)
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
stream, err := model.Stream(context.Background(), fantasy.Call{
stream, err := model.Stream(t.Context(), fantasy.Call{
Prompt: testPrompt,
})

View file

@ -17,6 +17,7 @@ var anthropicTestModels = []testModel{
}
func TestAnthropicCommon(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range anthropicTestModels {
pairs = append(pairs, builderPair{m.name, anthropicBuilder(m.model), nil, nil})
@ -56,6 +57,7 @@ func addAnthropicCaching(ctx context.Context, options fantasy.PrepareStepFunctio
}
func TestAnthropicCommonWithCacheControl(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range anthropicTestModels {
pairs = append(pairs, builderPair{m.name, anthropicBuilder(m.model), nil, addAnthropicCaching})
@ -64,6 +66,7 @@ func TestAnthropicCommonWithCacheControl(t *testing.T) {
}
func TestAnthropicThinking(t *testing.T) {
t.Parallel()
opts := fantasy.ProviderOptions{
anthropic.Name: &anthropic.ProviderOptions{
Thinking: &anthropic.ThinkingProviderOption{
@ -82,6 +85,7 @@ func TestAnthropicThinking(t *testing.T) {
}
func TestAnthropicThinkingWithCacheControl(t *testing.T) {
t.Parallel()
opts := fantasy.ProviderOptions{
anthropic.Name: &anthropic.ProviderOptions{
Thinking: &anthropic.ThinkingProviderOption{
@ -100,6 +104,7 @@ func TestAnthropicThinkingWithCacheControl(t *testing.T) {
}
func TestAnthropicObjectGeneration(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range anthropicTestModels {
pairs = append(pairs, builderPair{m.name, anthropicBuilder(m.model), nil, nil})

View file

@ -14,6 +14,7 @@ import (
)
func TestAzureResponsesCommon(t *testing.T) {
t.Parallel()
var pairs []builderPair
models := []testModel{
{"azure-gpt-5-mini", "gpt-5-mini", true},
@ -41,6 +42,7 @@ func azureReasoningBuilder(model string) builderFunc {
}
func TestAzureResponsesWithSummaryThinking(t *testing.T) {
t.Parallel()
opts := fantasy.ProviderOptions{
openai.Name: &openai.ResponsesProviderOptions{
Include: []openai.IncludeType{

View file

@ -16,6 +16,7 @@ import (
const defaultBaseURL = "https://fantasy-playground-resource.openai.azure.com"
func TestAzureCommon(t *testing.T) {
t.Parallel()
testCommon(t, []builderPair{
{"azure-o4-mini", builderAzureO4Mini, nil, nil},
{"azure-gpt-5-mini", builderAzureGpt5Mini, nil, nil},
@ -24,6 +25,7 @@ func TestAzureCommon(t *testing.T) {
}
func TestAzureThinking(t *testing.T) {
t.Parallel()
opts := fantasy.ProviderOptions{
openai.Name: &openai.ProviderOptions{
ReasoningEffort: openai.ReasoningEffortOption(openai.ReasoningEffortHigh),

View file

@ -11,6 +11,7 @@ import (
)
func TestBedrockCommon(t *testing.T) {
t.Parallel()
testCommon(t, []builderPair{
{"bedrock-anthropic-claude-3-sonnet", builderBedrockClaude3Sonnet, nil, nil},
{"bedrock-anthropic-claude-3-opus", builderBedrockClaude3Opus, nil, nil},
@ -19,6 +20,7 @@ func TestBedrockCommon(t *testing.T) {
}
func TestBedrockBasicAuth(t *testing.T) {
t.Parallel()
testSimple(t, builderPair{"bedrock-anthropic-claude-3-sonnet", buildersBedrockBasicAuth, nil, nil})
}

View file

@ -26,6 +26,7 @@ var vertexTestModels = []testModel{
}
func TestGoogleCommon(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range geminiTestModels {
pairs = append(pairs, builderPair{m.name, geminiBuilder(m.model), nil, nil})
@ -37,6 +38,7 @@ func TestGoogleCommon(t *testing.T) {
}
func TestGoogleThinking(t *testing.T) {
t.Parallel()
opts := fantasy.ProviderOptions{
google.Name: &google.ProviderOptions{
ThinkingConfig: &google.ThinkingConfig{
@ -57,6 +59,7 @@ func TestGoogleThinking(t *testing.T) {
}
func TestGoogleObjectGeneration(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range geminiTestModels {
pairs = append(pairs, builderPair{m.name, geminiBuilder(m.model), nil, nil})
@ -65,6 +68,7 @@ func TestGoogleObjectGeneration(t *testing.T) {
}
func TestGoogleVertexObjectGeneration(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range vertexTestModels {
pairs = append(pairs, builderPair{m.name, vertexBuilder(m.model), nil, nil})

View file

@ -54,6 +54,7 @@ func geminiImageBuilder(model string) builderFunc {
}
func TestImageUploadAgent(t *testing.T) {
t.Parallel()
pairs := []builderPair{
{
name: "anthropic-claude-sonnet-4",
@ -100,6 +101,7 @@ func TestImageUploadAgent(t *testing.T) {
}
func TestImageUploadAgentStreaming(t *testing.T) {
t.Parallel()
pairs := []builderPair{
{
name: "anthropic-claude-sonnet-4",

View file

@ -12,6 +12,7 @@ import (
)
func TestOpenAIResponsesCommon(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range openaiTestModels {
pairs = append(pairs, builderPair{m.name, openAIReasoningBuilder(m.model), nil, nil})
@ -34,6 +35,7 @@ func openAIReasoningBuilder(model string) builderFunc {
}
func TestOpenAIResponsesWithSummaryThinking(t *testing.T) {
t.Parallel()
opts := fantasy.ProviderOptions{
openai.Name: &openai.ResponsesProviderOptions{
Include: []openai.IncludeType{
@ -54,6 +56,7 @@ func TestOpenAIResponsesWithSummaryThinking(t *testing.T) {
}
func TestOpenAIResponsesObjectGeneration(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range openaiTestModels {
pairs = append(pairs, builderPair{m.name, openAIReasoningBuilder(m.model), nil, nil})

View file

@ -18,6 +18,7 @@ var openaiTestModels = []testModel{
}
func TestOpenAICommon(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range openaiTestModels {
pairs = append(pairs, builderPair{m.name, openAIBuilder(m.model), nil, nil})
@ -26,6 +27,7 @@ func TestOpenAICommon(t *testing.T) {
}
func TestOpenAIObjectGeneration(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range openaiTestModels {
pairs = append(pairs, builderPair{m.name, openAIBuilder(m.model), nil, nil})

View file

@ -13,6 +13,7 @@ import (
)
func TestOpenAICompatibleCommon(t *testing.T) {
t.Parallel()
testCommon(t, []builderPair{
{"xai-grok-4-fast", builderXAIGrok4Fast, nil, nil},
{"xai-grok-code-fast", builderXAIGrokCodeFast, nil, nil},
@ -24,6 +25,7 @@ func TestOpenAICompatibleCommon(t *testing.T) {
}
func TestOpenAICompatObjectGeneration(t *testing.T) {
t.Parallel()
testObjectGeneration(t, []builderPair{
{"xai-grok-4-fast", builderXAIGrok4Fast, nil, nil},
{"xai-grok-code-fast", builderXAIGrokCodeFast, nil, nil},
@ -32,6 +34,7 @@ func TestOpenAICompatObjectGeneration(t *testing.T) {
}
func TestOpenAICompatibleThinking(t *testing.T) {
t.Parallel()
opts := fantasy.ProviderOptions{
openaicompat.Name: &openaicompat.ProviderOptions{
ReasoningEffort: openai.ReasoningEffortOption(openai.ReasoningEffortHigh),

View file

@ -26,6 +26,7 @@ var openrouterTestModels = []testModel{
}
func TestOpenRouterCommon(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range openrouterTestModels {
pairs = append(pairs, builderPair{m.name, openrouterBuilder(m.model), nil, nil})
@ -34,12 +35,14 @@ func TestOpenRouterCommon(t *testing.T) {
}
func TestOpenRouterCommonWithAnthropicCache(t *testing.T) {
t.Parallel()
testCommon(t, []builderPair{
{"claude-sonnet-4", openrouterBuilder("anthropic/claude-sonnet-4"), nil, addAnthropicCaching},
})
}
func TestOpenRouterThinking(t *testing.T) {
t.Parallel()
opts := fantasy.ProviderOptions{
openrouter.Name: &openrouter.ProviderOptions{
Reasoning: &openrouter.ReasoningOptions{

View file

@ -1,9 +1,10 @@
package providertests
import (
jsonv2 "github.com/go-json-experiment/json"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"charm.land/fantasy"
"charm.land/fantasy/providers/anthropic"
"charm.land/fantasy/providers/google"
@ -14,6 +15,7 @@ import (
)
func TestProviderRegistry_Serialization_OpenAIOptions(t *testing.T) {
t.Parallel()
msg := fantasy.Message{
Role: fantasy.MessageRoleUser,
Content: []fantasy.MessagePart{
@ -52,7 +54,10 @@ func TestProviderRegistry_Serialization_OpenAIOptions(t *testing.T) {
}
func TestProviderRegistry_Serialization_OpenAIResponses(t *testing.T) {
t.Parallel(
// Use ResponsesProviderOptions in provider options
)
msg := fantasy.Message{
Role: fantasy.MessageRoleUser,
Content: []fantasy.MessagePart{
@ -95,6 +100,7 @@ func TestProviderRegistry_Serialization_OpenAIResponses(t *testing.T) {
}
func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *testing.T) {
t.Parallel()
resp := fantasy.Response{
Content: []fantasy.Content{
fantasy.TextContent{
@ -144,6 +150,7 @@ func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *test
}
func TestProviderRegistry_Serialization_AnthropicOptions(t *testing.T) {
t.Parallel()
sendReasoning := true
msg := fantasy.Message{
Role: fantasy.MessageRoleUser,
@ -172,6 +179,7 @@ func TestProviderRegistry_Serialization_AnthropicOptions(t *testing.T) {
}
func TestProviderRegistry_Serialization_GoogleOptions(t *testing.T) {
t.Parallel()
msg := fantasy.Message{
Role: fantasy.MessageRoleUser,
Content: []fantasy.MessagePart{
@ -200,6 +208,7 @@ func TestProviderRegistry_Serialization_GoogleOptions(t *testing.T) {
}
func TestProviderRegistry_Serialization_OpenRouterOptions(t *testing.T) {
t.Parallel()
includeUsage := true
msg := fantasy.Message{
Role: fantasy.MessageRoleUser,
@ -231,6 +240,7 @@ func TestProviderRegistry_Serialization_OpenRouterOptions(t *testing.T) {
}
func TestProviderRegistry_Serialization_OpenAICompatOptions(t *testing.T) {
t.Parallel()
effort := openai.ReasoningEffortHigh
msg := fantasy.Message{
Role: fantasy.MessageRoleUser,
@ -262,7 +272,10 @@ func TestProviderRegistry_Serialization_OpenAICompatOptions(t *testing.T) {
}
func TestProviderRegistry_MultiProvider(t *testing.T) {
t.Parallel(
// Test with multiple providers in one message
)
sendReasoning := true
msg := fantasy.Message{
Role: fantasy.MessageRoleUser,
@ -299,6 +312,7 @@ func TestProviderRegistry_MultiProvider(t *testing.T) {
}
func TestProviderRegistry_ErrorHandling(t *testing.T) {
t.Parallel()
t.Run("unknown provider type", func(t *testing.T) {
invalidJSON := `{
"role": "user",
@ -333,8 +347,11 @@ func TestProviderRegistry_ErrorHandling(t *testing.T) {
}
func TestProviderRegistry_AllTypesRegistered(t *testing.T) {
t.Parallel(
// Verify all expected provider types are registered
// We test that unmarshaling with proper type IDs doesn't fail with "unknown provider data type"
)
tests := []struct {
name string
providerName string

View file

@ -20,6 +20,7 @@ var vercelTestModels = []testModel{
}
func TestVercelCommon(t *testing.T) {
t.Parallel()
var pairs []builderPair
for _, m := range vercelTestModels {
pairs = append(pairs, builderPair{m.name, vercelBuilder(m.model), nil, nil})
@ -28,12 +29,14 @@ func TestVercelCommon(t *testing.T) {
}
func TestVercelCommonWithAnthropicCache(t *testing.T) {
t.Parallel()
testCommon(t, []builderPair{
{"claude-sonnet-4", vercelBuilder("anthropic/claude-sonnet-4"), nil, addAnthropicCaching},
})
}
func TestVercelThinking(t *testing.T) {
t.Parallel()
enabled := true
opts := fantasy.ProviderOptions{
vercel.Name: &vercel.ProviderOptions{

View file

@ -47,9 +47,10 @@ func driveGeneratePath(ctx context.Context, f *reactFSM) {
// TestFSM_Start verifies the FSM transitions from Init to PrepareStep on Start.
func TestFSM_Start(t *testing.T) {
t.Parallel()
obs := &captureObserver{}
f, _ := newTestFSM(t, obs)
ctx := context.Background()
ctx := t.Context()
f.Fire(ctx, ReActTriggerStart)
@ -63,9 +64,10 @@ func TestFSM_Start(t *testing.T) {
// TestFSM_FullHappyPath drives one complete step through all states and
// ends in Done via Finished.
func TestFSM_FullHappyPath(t *testing.T) {
t.Parallel()
obs := &captureObserver{}
f, _ := newTestFSM(t, obs)
ctx := context.Background()
ctx := t.Context()
f.Fire(ctx, ReActTriggerStart)
driveGeneratePath(ctx, f)
@ -81,9 +83,10 @@ func TestFSM_FullHappyPath(t *testing.T) {
// TestFSM_Continue verifies that the loop can re-enter PrepareStep after a
// tool-call step.
func TestFSM_Continue(t *testing.T) {
t.Parallel()
obs := &captureObserver{}
f, _ := newTestFSM(t, obs)
ctx := context.Background()
ctx := t.Context()
f.Fire(ctx, ReActTriggerStart)
driveGeneratePath(ctx, f)
@ -101,9 +104,10 @@ func TestFSM_Continue(t *testing.T) {
// TestFSM_StopConditionMet verifies the alternative Done path.
func TestFSM_StopConditionMet(t *testing.T) {
t.Parallel()
obs := &captureObserver{}
f, _ := newTestFSM(t, obs)
ctx := context.Background()
ctx := t.Context()
f.Fire(ctx, ReActTriggerStart)
driveGeneratePath(ctx, f)
@ -115,9 +119,10 @@ func TestFSM_StopConditionMet(t *testing.T) {
// TestFSM_ErrorTransition verifies the error state is reachable from any state.
func TestFSM_ErrorTransition(t *testing.T) {
t.Parallel()
obs := &captureObserver{}
f, _ := newTestFSM(t, obs)
ctx := context.Background()
ctx := t.Context()
f.Fire(ctx, ReActTriggerStart)
f.Fire(ctx, ReActTriggerPrepared)
@ -130,9 +135,10 @@ func TestFSM_ErrorTransition(t *testing.T) {
// TestFSM_RecoveredContinue verifies the error → PrepareStep recovery path.
func TestFSM_RecoveredContinue(t *testing.T) {
t.Parallel()
obs := &captureObserver{}
f, _ := newTestFSM(t, obs)
ctx := context.Background()
ctx := t.Context()
f.Fire(ctx, ReActTriggerStart)
f.Fire(ctx, ReActTriggerErrored) // error before prepared
@ -146,8 +152,9 @@ func TestFSM_RecoveredContinue(t *testing.T) {
// TestFSM_UnhandledTriggerIsPermissive verifies that firing an invalid trigger
// from a given state does NOT return an error (permissive design).
func TestFSM_UnhandledTriggerIsPermissive(t *testing.T) {
t.Parallel()
f, _ := newTestFSM(t, nil)
ctx := context.Background()
ctx := t.Context()
// From Init, firing Finished is not a permitted transition.
// The FSM must silently ignore it (no panic, no error).
@ -159,8 +166,9 @@ func TestFSM_UnhandledTriggerIsPermissive(t *testing.T) {
// TestFSM_TransitionLog verifies the log accumulates correctly and Snapshot
// returns a copy.
func TestFSM_TransitionLog(t *testing.T) {
t.Parallel()
f, _ := newTestFSM(t, nil)
ctx := context.Background()
ctx := t.Context()
f.Fire(ctx, ReActTriggerStart)
f.Fire(ctx, ReActTriggerPrepared)
@ -177,10 +185,11 @@ func TestFSM_TransitionLog(t *testing.T) {
// TestFSM_StepIndex verifies that the step index embedded in transitions
// reflects the pointer value at emission time.
func TestFSM_StepIndex(t *testing.T) {
t.Parallel()
idx := 0
obs := &captureObserver{}
f := newReActFSM(obs, &idx)
ctx := context.Background()
ctx := t.Context()
f.Fire(ctx, ReActTriggerStart) // stepIndex = 0
idx = 1
@ -194,8 +203,9 @@ func TestFSM_StepIndex(t *testing.T) {
// TestFSM_NilObserverSafe verifies no panic when no observer is attached.
func TestFSM_NilObserverSafe(t *testing.T) {
t.Parallel()
f, _ := newTestFSM(t, nil)
ctx := context.Background()
ctx := t.Context()
assert.NotPanics(t, func() {
f.Fire(ctx, ReActTriggerStart)
@ -206,6 +216,7 @@ func TestFSM_NilObserverSafe(t *testing.T) {
// TestReActTransitionLog_ConcurrentAppend verifies the log is safe under
// concurrent writes.
func TestReActTransitionLog_ConcurrentAppend(t *testing.T) {
t.Parallel()
log := NewReActTransitionLog()
const n = 100
var wg sync.WaitGroup

View file

@ -8,7 +8,10 @@ import (
)
func TestEnumSupport(t *testing.T) {
t.Parallel(
// Test enum via struct tags
)
type WeatherInput struct {
Location string `json:"location" description:"City name"`
Units string `json:"units" enum:"celsius,fahrenheit,kelvin" description:"Temperature units"`
@ -34,6 +37,7 @@ func TestEnumSupport(t *testing.T) {
}
func TestSchemaToParameters(t *testing.T) {
t.Parallel()
testSchema := Schema{
Type: "object",
Properties: map[string]*Schema{

View file

@ -45,7 +45,7 @@ func TestDAGToolRuntime_IndependentToolsRunConcurrently(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
res, err = rt.Execute(context.Background(), []AgentTool{tool}, toolCalls, nil)
res, err = rt.Execute(t.Context(), []AgentTool{tool}, toolCalls, nil)
}()
// Both tools should start before we release.
@ -100,7 +100,7 @@ func TestDAGToolRuntime_DependenciesWaitAndInputIsResolved(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
res, err = rt.Execute(context.Background(), []AgentTool{toolA, toolB}, toolCalls, nil)
res, err = rt.Execute(t.Context(), []AgentTool{toolA, toolB}, toolCalls, nil)
}()
<-startA
@ -138,7 +138,7 @@ func TestDAGToolRuntime_CycleDetected(t *testing.T) {
{ToolCallID: "b", ToolName: "p", Input: `{"x":"$tool.a"}`},
}
res, err := rt.Execute(context.Background(), []AgentTool{tool}, toolCalls, nil)
res, err := rt.Execute(t.Context(), []AgentTool{tool}, toolCalls, nil)
require.Error(t, err)
require.Nil(t, res)
}
@ -172,7 +172,7 @@ func TestDAGToolRuntime_OnToolResultSerialized(t *testing.T) {
return nil
}
res, err := rt.Execute(context.Background(), []AgentTool{tool}, toolCalls, cb)
res, err := rt.Execute(t.Context(), []AgentTool{tool}, toolCalls, cb)
require.NoError(t, err)
require.Len(t, res, 2)
@ -206,7 +206,7 @@ func TestDAGToolRuntime_MetricsAndLogHooks(t *testing.T) {
toolCalls := []ToolCallContent{
{ToolCallID: "a", ToolName: "p", Input: `{}`},
}
res, err := rt.Execute(context.Background(), []AgentTool{tool}, toolCalls, nil)
res, err := rt.Execute(t.Context(), []AgentTool{tool}, toolCalls, nil)
require.NoError(t, err)
require.Len(t, res, 1)
require.True(t, metricsCalled)

View file

@ -46,7 +46,7 @@ func TestParallelToolRuntime_OrderAndCallbackDeterminism(t *testing.T) {
{ToolCallID: "c3", ToolName: "p", Input: `{"delay_ms":30,"value":"c"}`},
}
results, err := runtime.Execute(context.Background(), []AgentTool{tool}, toolCalls, cb)
results, err := runtime.Execute(t.Context(), []AgentTool{tool}, toolCalls, cb)
require.NoError(t, err)
require.Len(t, results, 3)
@ -92,7 +92,7 @@ func TestParallelToolRuntime_BarrierForNonParallelTools(t *testing.T) {
{ToolCallID: "p3", ToolName: "p", Input: `{}`},
}
results, err := runtime.Execute(context.Background(), []AgentTool{parallel, seq}, toolCalls, cb)
results, err := runtime.Execute(t.Context(), []AgentTool{parallel, seq}, toolCalls, cb)
require.NoError(t, err)
require.Len(t, results, 4)
@ -118,7 +118,7 @@ func TestParallelToolRuntime_CriticalErrorPropagation(t *testing.T) {
{ToolCallID: "bad", ToolName: "p", Input: `{}`},
}
results, err := runtime.Execute(context.Background(), []AgentTool{tool}, toolCalls, nil)
results, err := runtime.Execute(t.Context(), []AgentTool{tool}, toolCalls, nil)
require.Error(t, err)
require.Nil(t, results)
}
@ -148,7 +148,7 @@ func TestParallelToolRuntime_MetricsAndLogHooks(t *testing.T) {
toolCalls := []ToolCallContent{
{ToolCallID: "a", ToolName: "p", Input: `{}`},
}
res, err := rt.Execute(context.Background(), []AgentTool{tool}, toolCalls, nil)
res, err := rt.Execute(t.Context(), []AgentTool{tool}, toolCalls, nil)
require.NoError(t, err)
require.Len(t, res, 1)
require.True(t, metricsCalled)

View file

@ -5,6 +5,7 @@ import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)
@ -14,7 +15,8 @@ type CalculatorInput struct {
}
func TestTypedToolFuncExample(t *testing.T) {
// Create a typed tool using the function API
t.Parallel()
tool := NewAgentTool(
"calculator",
"Evaluates simple mathematical expressions",
@ -39,13 +41,14 @@ func TestTypedToolFuncExample(t *testing.T) {
Input: `{"expression": "2+2"}`,
}
result, err := tool.Run(context.Background(), call)
result, err := tool.Run(t.Context(), call)
require.NoError(t, err)
require.Equal(t, "4", result.Content)
require.False(t, result.IsError)
}
func TestEnumToolExample(t *testing.T) {
t.Parallel()
type WeatherInput struct {
Location string `json:"location" description:"City name"`
Units string `json:"units" enum:"celsius,fahrenheit" description:"Temperature units"`
@ -78,34 +81,51 @@ func TestEnumToolExample(t *testing.T) {
Input: `{"location": "San Francisco", "units": "fahrenheit"}`,
}
result, err := tool.Run(context.Background(), call)
result, err := tool.Run(t.Context(), call)
require.NoError(t, err)
require.Contains(t, result.Content, "San Francisco")
require.Contains(t, result.Content, "72°F")
}
func TestNewImageResponse(t *testing.T) {
imageData := []byte{0x89, 0x50, 0x4E, 0x47} // PNG header bytes
mediaType := "image/png"
resp := NewImageResponse(imageData, mediaType)
require.Equal(t, "image", resp.Type)
require.Equal(t, imageData, resp.Data)
require.Equal(t, mediaType, resp.MediaType)
require.False(t, resp.IsError)
require.Empty(t, resp.Content)
}
func TestNewMediaResponse(t *testing.T) {
audioData := []byte{0x52, 0x49, 0x46, 0x46} // RIFF header bytes
mediaType := "audio/wav"
t.Parallel()
tests := []struct {
name string
mediaType string
data []byte
wantType string
}{
{
name: "image response",
data: []byte{0x89, 0x50, 0x4E, 0x47},
mediaType: "image/png",
wantType: "image",
},
{
name: "audio response",
data: []byte{0x52, 0x49, 0x46, 0x46},
mediaType: "audio/wav",
wantType: "media",
},
}
resp := NewMediaResponse(audioData, mediaType)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var got ToolResponse
if tt.wantType == "image" {
got = NewImageResponse(tt.data, tt.mediaType)
} else {
got = NewMediaResponse(tt.data, tt.mediaType)
}
require.Equal(t, "media", resp.Type)
require.Equal(t, audioData, resp.Data)
require.Equal(t, mediaType, resp.MediaType)
require.False(t, resp.IsError)
require.Empty(t, resp.Content)
require.Empty(t, cmp.Diff(ToolResponse{
Type: tt.wantType,
Data: tt.data,
MediaType: tt.mediaType,
IsError: false,
Content: "",
}, got))
})
}
}

View file

@ -59,8 +59,8 @@ func (m *toolCallingModel) Generate(_ context.Context, call fantasy.Call) (*fant
}, nil
}
func (m *toolCallingModel) Stream(_ context.Context, call fantasy.Call) (fantasy.StreamResponse, error) {
resp, err := m.Generate(context.Background(), call)
func (m *toolCallingModel) Stream(ctx context.Context, call fantasy.Call) (fantasy.StreamResponse, error) {
resp, err := m.Generate(ctx, call)
if err != nil {
return nil, err
}
@ -162,6 +162,7 @@ func (t *echoTool) Execute(_ context.Context, args map[string]interface{}) *tool
// TestIntegration_FullAgentLoop_SimpleResponse tests the full agent loop
// with a simple mock model that returns text directly (no tool calls).
func TestIntegration_FullAgentLoop_SimpleResponse(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-integration-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -183,7 +184,7 @@ func TestIntegration_FullAgentLoop_SimpleResponse(t *testing.T) {
model := newMockLanguageModel("Hello from Fantasy agent")
al := mustNewAgentLoop(t, cfg, msgBus, model)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
defer cancel()
msg := bus.InboundMessage{
@ -225,6 +226,7 @@ func TestIntegration_FullAgentLoop_SimpleResponse(t *testing.T) {
// TestIntegration_FullAgentLoop_WithToolCalls tests the full agent loop
// including tool call execution and response incorporation.
func TestIntegration_FullAgentLoop_WithToolCalls(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-integration-tools-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -249,7 +251,7 @@ func TestIntegration_FullAgentLoop_WithToolCalls(t *testing.T) {
// Register the echo tool
al.RegisterTool(&echoTool{})
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
defer cancel()
msg := bus.InboundMessage{
@ -279,6 +281,7 @@ func TestIntegration_FullAgentLoop_WithToolCalls(t *testing.T) {
// TestIntegration_ProcessDirect tests the ProcessDirect method
// which is used by CLI mode for one-shot message processing.
func TestIntegration_ProcessDirect(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-integration-direct-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -300,7 +303,7 @@ func TestIntegration_ProcessDirect(t *testing.T) {
model := newMockLanguageModel("Direct CLI response")
al := mustNewAgentLoop(t, cfg, msgBus, model)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
defer cancel()
response, err := al.ProcessDirect(ctx, "Direct message", "direct-session")
@ -374,6 +377,7 @@ func (m *streamingModel) Model() string { return "streaming-mock" }
// TestIntegration_Streaming_TextDeltas tests that the streaming agent loop
// publishes text deltas to the bus and returns the complete text.
func TestIntegration_Streaming_TextDeltas(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-integration-stream-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -395,7 +399,7 @@ func TestIntegration_Streaming_TextDeltas(t *testing.T) {
model := newStreamingModel("Hello from streaming agent response")
al := mustNewAgentLoop(t, cfg, msgBus, model)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
defer cancel()
// Collect stream deltas in background
@ -450,6 +454,7 @@ func TestIntegration_Streaming_TextDeltas(t *testing.T) {
// TestIntegration_Streaming_WithToolCalls tests streaming with a model
// that requests tool calls before producing a final streamed response.
func TestIntegration_Streaming_WithToolCalls(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-integration-stream-tools-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -472,7 +477,7 @@ func TestIntegration_Streaming_WithToolCalls(t *testing.T) {
al := mustNewAgentLoop(t, cfg, msgBus, model)
al.RegisterTool(&echoTool{})
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
defer cancel()
// Collect deltas
@ -512,6 +517,7 @@ func TestIntegration_Streaming_WithToolCalls(t *testing.T) {
// TestIntegration_MultipleMessages tests sequential message processing
// to verify session history accumulation.
func TestIntegration_MultipleMessages(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-integration-multi-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -534,7 +540,7 @@ func TestIntegration_MultipleMessages(t *testing.T) {
al := mustNewAgentLoop(t, cfg, msgBus, model)
sessionKey := "multi-msg-session"
ctx := context.Background()
ctx := t.Context()
// Send 3 messages
for i := 0; i < 3; i++ {

View file

@ -1,7 +1,6 @@
package agent_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
@ -17,8 +16,9 @@ func newDelegateKV(t *testing.T, agentID string) *agent.DelegateKV {
}
func TestDelegateKV_PutAndGet(t *testing.T) {
t.Parallel()
kv := newDelegateKV(t, "agent-1")
ctx := context.Background()
ctx := t.Context()
require.NoError(t, kv.Put(ctx, "key1", []byte("hello world")))
@ -28,8 +28,9 @@ func TestDelegateKV_PutAndGet(t *testing.T) {
}
func TestDelegateKV_GetMissingKey(t *testing.T) {
t.Parallel()
kv := newDelegateKV(t, "agent-1")
ctx := context.Background()
ctx := t.Context()
got, err := kv.Get(ctx, "nonexistent")
require.NoError(t, err)
@ -37,8 +38,9 @@ func TestDelegateKV_GetMissingKey(t *testing.T) {
}
func TestDelegateKV_PutOverwrite(t *testing.T) {
t.Parallel()
kv := newDelegateKV(t, "agent-1")
ctx := context.Background()
ctx := t.Context()
require.NoError(t, kv.Put(ctx, "k", []byte("v1")))
require.NoError(t, kv.Put(ctx, "k", []byte("v2")))
@ -49,8 +51,9 @@ func TestDelegateKV_PutOverwrite(t *testing.T) {
}
func TestDelegateKV_BinaryValues(t *testing.T) {
t.Parallel()
kv := newDelegateKV(t, "agent-bin")
ctx := context.Background()
ctx := t.Context()
// Include bytes that need base64 encoding (null bytes, high bytes)
data := []byte{0x00, 0xFF, 0x1F, 0x7E, 0x80, 0xAB}
@ -62,8 +65,9 @@ func TestDelegateKV_BinaryValues(t *testing.T) {
}
func TestDelegateKV_Scan(t *testing.T) {
t.Parallel()
kv := newDelegateKV(t, "agent-scan")
ctx := context.Background()
ctx := t.Context()
keys := []string{"prefix/a", "prefix/b", "prefix/c", "other/x"}
for _, k := range keys {
@ -76,8 +80,9 @@ func TestDelegateKV_Scan(t *testing.T) {
}
func TestDelegateKV_ScanEmpty(t *testing.T) {
t.Parallel()
kv := newDelegateKV(t, "agent-scan-empty")
ctx := context.Background()
ctx := t.Context()
got, err := kv.Scan(ctx, "nothing/")
require.NoError(t, err)
@ -85,8 +90,9 @@ func TestDelegateKV_ScanEmpty(t *testing.T) {
}
func TestDelegateKV_ScanSorted(t *testing.T) {
t.Parallel()
kv := newDelegateKV(t, "agent-sorted")
ctx := context.Background()
ctx := t.Context()
// Insert out of order
for _, k := range []string{"z/c", "z/a", "z/b"} {
@ -99,10 +105,11 @@ func TestDelegateKV_ScanSorted(t *testing.T) {
}
func TestDelegateKV_AgentIsolation(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
kv1 := agent.NewDelegateKV(db.delegate, "agent-A")
kv2 := agent.NewDelegateKV(db.delegate, "agent-B")
ctx := context.Background()
ctx := t.Context()
require.NoError(t, kv1.Put(ctx, "shared-key", []byte("from-A")))
require.NoError(t, kv2.Put(ctx, "shared-key", []byte("from-B")))
@ -117,13 +124,15 @@ func TestDelegateKV_AgentIsolation(t *testing.T) {
}
func TestDelegateKV_EmptyKey_PutErrors(t *testing.T) {
t.Parallel()
kv := newDelegateKV(t, "agent-1")
err := kv.Put(context.Background(), "", []byte("val"))
err := kv.Put(t.Context(), "", []byte("val"))
assert.Error(t, err, "empty key should be rejected")
}
func TestDelegateKV_EmptyKey_GetErrors(t *testing.T) {
t.Parallel()
kv := newDelegateKV(t, "agent-1")
_, err := kv.Get(context.Background(), "")
_, err := kv.Get(t.Context(), "")
assert.Error(t, err, "empty key should be rejected")
}

View file

@ -26,7 +26,7 @@ func mustNewAgentLoop(t *testing.T, cfg *config.Config, msgBus *bus.MessageBus,
if cfg != nil && cfg.Memory.DBPath == "" && strings.TrimSpace(cfg.Agents.Defaults.Workspace) != "" {
cfg.Memory.DBPath = filepath.Join(cfg.Agents.Defaults.Workspace, "agent-loop-test.db")
}
al, err := NewAgentLoop(context.Background(), cfg, msgBus, model)
al, err := NewAgentLoop(t.Context(), cfg, msgBus, model)
if err != nil {
t.Fatalf("NewAgentLoop: %v", err)
}
@ -73,6 +73,7 @@ func (m *mockLanguageModel) Provider() string { return "mock" }
func (m *mockLanguageModel) Model() string { return "mock-model" }
func TestContinuityKeepCount_UsesConfiguredPolicy(t *testing.T) {
t.Parallel()
buildHistory := func(n int, content string) []messages.Message {
history := make([]messages.Message, 0, n)
for i := 0; i < n; i++ {
@ -113,6 +114,7 @@ func TestContinuityKeepCount_UsesConfiguredPolicy(t *testing.T) {
}
func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -133,7 +135,7 @@ func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *test
msgBus := bus.NewMessageBus()
model := newMockLanguageModel("")
al := mustNewAgentLoop(t, cfg, msgBus, model)
beforeConversations, err := al.queries.ListAgentConversations(context.Background(), memsqlc.ListAgentConversationsParams{
beforeConversations, err := al.queries.ListAgentConversations(t.Context(), memsqlc.ListAgentConversationsParams{
Limit: 10000,
})
if err != nil {
@ -152,7 +154,7 @@ func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *test
go func() {
defer wg.Done()
<-start
conversationID, _, prepareErr := al.prepareRuntimeState(context.Background(), "race-session")
conversationID, _, prepareErr := al.prepareRuntimeState(t.Context(), "race-session")
if prepareErr != nil {
errorsCh <- prepareErr
return
@ -180,7 +182,7 @@ func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *test
t.Fatalf("expected one conversation id, got %d (%v)", len(uniqueConversationIDs), uniqueConversationIDs)
}
conversations, err := al.queries.ListAgentConversations(context.Background(), memsqlc.ListAgentConversationsParams{
conversations, err := al.queries.ListAgentConversations(t.Context(), memsqlc.ListAgentConversationsParams{
Limit: 10000,
})
if err != nil {
@ -192,7 +194,10 @@ func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *test
}
func TestRecordLastChannel(t *testing.T) {
t.Parallel(
// Create temp workspace
)
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -218,7 +223,7 @@ func TestRecordLastChannel(t *testing.T) {
// Test RecordLastChannel
testChannel := "test-channel"
err = al.RecordLastChannel(context.Background(), testChannel)
err = al.RecordLastChannel(t.Context(), testChannel)
if err != nil {
t.Fatalf("RecordLastChannel failed: %v", err)
}
@ -237,7 +242,10 @@ func TestRecordLastChannel(t *testing.T) {
}
func TestRecordLastChatID(t *testing.T) {
t.Parallel(
// Create temp workspace
)
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -263,7 +271,7 @@ func TestRecordLastChatID(t *testing.T) {
// Test RecordLastChatID
testChatID := "test-chat-id-123"
err = al.RecordLastChatID(context.Background(), testChatID)
err = al.RecordLastChatID(t.Context(), testChatID)
if err != nil {
t.Fatalf("RecordLastChatID failed: %v", err)
}
@ -282,7 +290,10 @@ func TestRecordLastChatID(t *testing.T) {
}
func TestNewAgentLoop_StateInitialized(t *testing.T) {
t.Parallel(
// Create temp workspace
)
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -313,6 +324,7 @@ func TestNewAgentLoop_StateInitialized(t *testing.T) {
}
func TestNewAgentLoop_UnifiedKernelDependenciesInitialized(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -344,6 +356,7 @@ func TestNewAgentLoop_UnifiedKernelDependenciesInitialized(t *testing.T) {
// TestToolRegistry_ToolRegistration verifies tools can be registered and retrieved
func TestToolRegistry_ToolRegistration(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -390,6 +403,7 @@ func TestToolRegistry_ToolRegistration(t *testing.T) {
// TestToolContext_Updates verifies tool context is updated with channel/chatID
func TestToolContext_Updates(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -421,6 +435,7 @@ func TestToolContext_Updates(t *testing.T) {
// TestToolRegistry_GetDefinitions verifies tool definitions can be retrieved
func TestToolRegistry_GetDefinitions(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -465,6 +480,7 @@ func TestToolRegistry_GetDefinitions(t *testing.T) {
// TestAgentLoop_GetStartupInfo verifies startup info contains tools
func TestAgentLoop_GetStartupInfo(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -512,6 +528,7 @@ func TestAgentLoop_GetStartupInfo(t *testing.T) {
// TestAgentLoop_Stop verifies Stop() sets running to false
func TestAgentLoop_Stop(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -619,6 +636,7 @@ const responseTimeout = 3 * time.Second
// TestToolResult_SilentToolDoesNotSendUserMessage verifies silent tools don't trigger outbound
func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -642,7 +660,7 @@ func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) {
helper := testHelper{al: al}
// ReadFileTool returns SilentResult, which should not send user message
ctx := context.Background()
ctx := t.Context()
msg := bus.InboundMessage{
Channel: "test",
SenderID: "user1",
@ -661,6 +679,7 @@ func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) {
// TestToolResult_UserFacingToolDoesSendMessage verifies user-facing tools trigger outbound
func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -684,7 +703,7 @@ func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) {
helper := testHelper{al: al}
// ExecTool returns UserResult, which should send user message
ctx := context.Background()
ctx := t.Context()
msg := bus.InboundMessage{
Channel: "test",
SenderID: "user1",
@ -702,6 +721,7 @@ func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) {
}
func TestResolveFinalContent_RecoversFromPriorStepText(t *testing.T) {
t.Parallel()
al := &AgentLoop{}
steps := []fantasy.StepResult{
{
@ -730,6 +750,7 @@ func TestResolveFinalContent_RecoversFromPriorStepText(t *testing.T) {
}
func TestResolveFinalContent_ErrorsWhenNoTextExists(t *testing.T) {
t.Parallel()
al := &AgentLoop{}
steps := []fantasy.StepResult{
{
@ -748,6 +769,7 @@ func TestResolveFinalContent_ErrorsWhenNoTextExists(t *testing.T) {
}
func TestResolveFinalContent_RecoversFromToolResultText(t *testing.T) {
t.Parallel()
al := &AgentLoop{}
steps := []fantasy.StepResult{
{
@ -776,6 +798,7 @@ func TestResolveFinalContent_RecoversFromToolResultText(t *testing.T) {
// TestForceCompression_PersistsProvenance verifies that emergency compression
// cycles persist provenance metadata to the audit log for postmortem.
func TestForceCompression_PersistsProvenance(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-provenance-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -827,7 +850,7 @@ func TestForceCompression_PersistsProvenance(t *testing.T) {
t.Fatalf("test precondition failed: token_estimate=%d threshold=%d", tokenEstimate, criticalThreshold)
}
ctx := context.Background()
ctx := t.Context()
al.forceCompression(ctx, sessionKey, "", "")
del := al.MemoryDelegate()
@ -872,6 +895,7 @@ func TestForceCompression_PersistsProvenance(t *testing.T) {
}
func TestPersistOversizedRecoveryRefs_CreatesRecoverableReferences(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "agent-recovery-ref-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -904,7 +928,7 @@ func TestPersistOversizedRecoveryRefs_CreatesRecoverableReferences(t *testing.T)
},
}
refs, err := al.persistOversizedRecoveryRefs(context.Background(), "recovery-session", omitted)
refs, err := al.persistOversizedRecoveryRefs(t.Context(), "recovery-session", omitted)
if err != nil {
t.Fatalf("persistOversizedRecoveryRefs failed: %v", err)
}
@ -919,7 +943,7 @@ func TestPersistOversizedRecoveryRefs_CreatesRecoverableReferences(t *testing.T)
return "recovery-session"
},
})
res := dagTool.Execute(context.Background(), map[string]interface{}{
res := dagTool.Execute(t.Context(), map[string]interface{}{
"node_id": refs[0],
"session_key": "recovery-session",
})

View file

@ -40,7 +40,7 @@ func makeOffloader(t *testing.T, base fantasy.ToolRuntime, threshold, chunkChars
q := db.delegate.Queries()
convID := newConversation(t, q)
s := agent.NewStateStore(q)
run, err := s.CreateRun(context.Background(), convID)
run, err := s.CreateRun(t.Context(), convID)
require.NoError(t, err)
kv := agent.NewDelegateKV(db.delegate, "offload-test")
@ -76,8 +76,9 @@ func makeCalls(n int) []fantasy.ToolCallContent {
// TestOffloading_SmallResult_KeptInline verifies that results below the
// threshold are stored in KV but the inline value is unchanged.
func TestOffloading_SmallResult_KeptInline(t *testing.T) {
t.Parallel()
r, _, _, kv := makeOffloader(t, nil, 1000, 500)
ctx := context.Background()
ctx := t.Context()
calls := makeCalls(1)
results, err := r.Execute(ctx, nil, calls, nil)
@ -95,6 +96,7 @@ func TestOffloading_SmallResult_KeptInline(t *testing.T) {
// TestOffloading_LargeResult_Truncated verifies that results above the
// threshold are truncated inline and chunked in KV.
func TestOffloading_LargeResult_Truncated(t *testing.T) {
t.Parallel()
longText := strings.Repeat("x", 200)
base := staticToolRuntime{results: []fantasy.ToolResultContent{
@ -106,7 +108,7 @@ func TestOffloading_LargeResult_Truncated(t *testing.T) {
}}
r, _, _, kv := makeOffloader(t, base, 50, 30)
ctx := context.Background()
ctx := t.Context()
calls := makeCalls(1)
calls[0].ToolCallID = "call-a"
@ -149,8 +151,9 @@ func TestOffloading_LargeResult_Truncated(t *testing.T) {
// TestOffloading_DBMetadata_Inserted verifies that the DB record is created.
func TestOffloading_DBMetadata_Inserted(t *testing.T) {
t.Parallel()
r, convIDPtr, runIDPtr, _ := makeOffloader(t, nil, 1000, 500)
ctx := context.Background()
ctx := t.Context()
calls := makeCalls(2)
_, err := r.Execute(ctx, nil, calls, nil)
@ -165,11 +168,12 @@ func TestOffloading_DBMetadata_Inserted(t *testing.T) {
// TestOffloading_NilKV_Errors verifies that a nil KVDelegate returns an error.
func TestOffloading_NilKV_Errors(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
convID := newConversation(t, q)
s := agent.NewStateStore(q)
run, err := s.CreateRun(context.Background(), convID)
run, err := s.CreateRun(t.Context(), convID)
require.NoError(t, err)
r := &agent.OffloadingToolRuntime{
@ -178,12 +182,13 @@ func TestOffloading_NilKV_Errors(t *testing.T) {
RunID: run.ID,
}
_, err = r.Execute(context.Background(), nil, makeCalls(1), nil)
_, err = r.Execute(t.Context(), nil, makeCalls(1), nil)
assert.Error(t, err, "nil KV should fail")
}
// TestOffloading_NilQueries_Errors verifies that a nil Queries returns an error.
func TestOffloading_NilQueries_Errors(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
kv := agent.NewDelegateKV(db.delegate, "a")
@ -193,15 +198,16 @@ func TestOffloading_NilQueries_Errors(t *testing.T) {
RunID: ids.New(),
}
_, err := r.Execute(context.Background(), nil, makeCalls(1), nil)
_, err := r.Execute(t.Context(), nil, makeCalls(1), nil)
assert.Error(t, err, "nil queries should fail")
}
// TestOffloading_EmptyToolCalls_ReturnsNil verifies no work is done when
// no tool calls are provided.
func TestOffloading_EmptyToolCalls_ReturnsNil(t *testing.T) {
t.Parallel()
r, _, _, _ := makeOffloader(t, nil, 1000, 500)
results, err := r.Execute(context.Background(), nil, nil, nil)
results, err := r.Execute(t.Context(), nil, nil, nil)
require.NoError(t, err)
assert.Nil(t, results, "no tool calls should produce no results")
}
@ -209,8 +215,9 @@ func TestOffloading_EmptyToolCalls_ReturnsNil(t *testing.T) {
// TestOffloading_MultipleCalls_AllStoredInKV verifies that each call gets
// its own full-result KV entry.
func TestOffloading_MultipleCalls_AllStoredInKV(t *testing.T) {
t.Parallel()
r, _, _, kv := makeOffloader(t, nil, 1000, 500)
ctx := context.Background()
ctx := t.Context()
calls := makeCalls(3)
_, err := r.Execute(ctx, nil, calls, nil)
@ -230,6 +237,7 @@ func TestOffloading_MultipleCalls_AllStoredInKV(t *testing.T) {
// TestChunkString verifies the internal chunking logic boundary conditions.
func TestChunkString(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
@ -268,10 +276,10 @@ func TestChunkString(t *testing.T) {
r, _, _, kv := makeOffloader(t, base, effectiveThreshold, tt.chunkSize)
calls := []fantasy.ToolCallContent{{ToolCallID: "c", ToolName: "t"}}
_, err := r.Execute(context.Background(), nil, calls, nil)
_, err := r.Execute(t.Context(), nil, calls, nil)
require.NoError(t, err)
keys, err := kv.Scan(context.Background(), "tool_results/")
keys, err := kv.Scan(t.Context(), "tool_results/")
require.NoError(t, err)
chunkCount := 0

View file

@ -1,11 +1,12 @@
package agent_test
import (
"context"
"testing"
"time"
"charm.land/fantasy"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -19,7 +20,7 @@ func newTestQueries(t *testing.T) *testDB {
t.Helper()
d, err := delegate.NewLibSQLInMemory()
require.NoError(t, err, "NewLibSQLInMemory")
require.NoError(t, d.Init(context.Background()), "Init")
require.NoError(t, d.Init(t.Context()), "Init")
t.Cleanup(func() { _ = d.Close() })
return &testDB{delegate: d}
}
@ -36,7 +37,7 @@ func newConversation(t *testing.T, q *sqlc.Queries) ids.UUID {
t.Helper()
id := ids.New()
title := "test-conv"
_, err := q.CreateAgentConversation(context.Background(), sqlc.CreateAgentConversationParams{
_, err := q.CreateAgentConversation(t.Context(), sqlc.CreateAgentConversationParams{
ID: id,
Title: &title,
})
@ -45,34 +46,39 @@ func newConversation(t *testing.T, q *sqlc.Queries) ids.UUID {
}
func TestStateStore_CreateRun(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
s := agent.NewStateStore(q)
ctx := context.Background()
ctx := t.Context()
convID := newConversation(t, q)
run, err := s.CreateRun(ctx, convID)
require.NoError(t, err)
assert.False(t, run.ID.IsZero(), "run ID must be set")
assert.Equal(t, convID, run.ConversationID)
assert.Equal(t, "running", run.Status)
assert.Empty(t, cmp.Diff(sqlc.AgentRun{
ConversationID: convID,
Status: "running",
}, run, cmpopts.IgnoreFields(sqlc.AgentRun{}, "ID", "MetadataJson", "CreatedAt", "UpdatedAt")))
}
func TestStateStore_CreateRun_ZeroConversationID(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
s := agent.NewStateStore(db.delegate.Queries())
ctx := context.Background()
ctx := t.Context()
_, err := s.CreateRun(ctx, ids.UUID{})
assert.Error(t, err, "zero conversation id should be rejected")
}
func TestStateStore_UpdateRunStatus(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
s := agent.NewStateStore(q)
ctx := context.Background()
ctx := t.Context()
convID := newConversation(t, q)
run, err := s.CreateRun(ctx, convID)
@ -81,15 +87,18 @@ func TestStateStore_UpdateRunStatus(t *testing.T) {
updated, err := s.UpdateRunStatus(ctx, run.ID, "completed", map[string]any{"steps": 3})
require.NoError(t, err)
assert.Equal(t, run.ID, updated.ID)
assert.Equal(t, "completed", updated.Status)
assert.Empty(t, cmp.Diff(sqlc.AgentRun{
ID: run.ID,
Status: "completed",
}, updated, cmpopts.IgnoreFields(sqlc.AgentRun{}, "ConversationID", "MetadataJson", "CreatedAt", "UpdatedAt")))
}
func TestStateStore_UpdateRunStatus_EmptyStatus(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
s := agent.NewStateStore(q)
ctx := context.Background()
ctx := t.Context()
convID := newConversation(t, q)
run, err := s.CreateRun(ctx, convID)
@ -100,10 +109,11 @@ func TestStateStore_UpdateRunStatus_EmptyStatus(t *testing.T) {
}
func TestStateStore_AddRunState(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
s := agent.NewStateStore(q)
ctx := context.Background()
ctx := t.Context()
convID := newConversation(t, q)
run, err := s.CreateRun(ctx, convID)
@ -112,17 +122,19 @@ func TestStateStore_AddRunState(t *testing.T) {
state, err := s.AddRunState(ctx, run.ID, 0, fantasy.ReActStateLLMCall, map[string]string{"model": "gpt-4o"})
require.NoError(t, err)
assert.False(t, state.ID.IsZero())
assert.Equal(t, run.ID, state.RunID)
assert.Equal(t, int64(0), state.StepIndex)
assert.Equal(t, string(fantasy.ReActStateLLMCall), state.State)
assert.Empty(t, cmp.Diff(sqlc.AgentRunState{
RunID: run.ID,
StepIndex: 0,
State: string(fantasy.ReActStateLLMCall),
}, state, cmpopts.IgnoreFields(sqlc.AgentRunState{}, "ID", "SnapshotJson", "CreatedAt", "UpdatedAt")))
}
func TestStateStore_AddRunState_NegativeStep(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
s := agent.NewStateStore(q)
ctx := context.Background()
ctx := t.Context()
convID := newConversation(t, q)
run, err := s.CreateRun(ctx, convID)
@ -133,10 +145,11 @@ func TestStateStore_AddRunState_NegativeStep(t *testing.T) {
}
func TestStateStore_AddTransition(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
s := agent.NewStateStore(q)
ctx := context.Background()
ctx := t.Context()
convID := newConversation(t, q)
run, err := s.CreateRun(ctx, convID)
@ -152,15 +165,19 @@ func TestStateStore_AddTransition(t *testing.T) {
row, err := s.AddTransition(ctx, run.ID, tr)
require.NoError(t, err)
assert.False(t, row.ID.IsZero())
assert.Equal(t, string(fantasy.ReActStateInit), row.FromState)
assert.Equal(t, string(fantasy.ReActStatePrepareStep), row.ToState)
assert.Equal(t, string(fantasy.ReActTriggerStart), row.Trigger)
assert.Empty(t, cmp.Diff(sqlc.AgentStateTransition{
RunID: run.ID,
StepIndex: 0,
FromState: string(fantasy.ReActStateInit),
ToState: string(fantasy.ReActStatePrepareStep),
Trigger: string(fantasy.ReActTriggerStart),
}, row, cmpopts.IgnoreFields(sqlc.AgentStateTransition{}, "ID", "At", "MetaJson", "Error", "CreatedAt", "UpdatedAt")))
}
func TestStateStore_NilStore(t *testing.T) {
t.Parallel()
var s *agent.StateStore
ctx := context.Background()
ctx := t.Context()
_, err := s.CreateRun(ctx, ids.New())
assert.Error(t, err, "nil store should error")
@ -169,11 +186,12 @@ func TestStateStore_NilStore(t *testing.T) {
// --- CheckpointStore ---
func TestCheckpointStore_CreateAndList(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
ss := agent.NewStateStore(q)
cs := agent.NewCheckpointStore(q)
ctx := context.Background()
ctx := t.Context()
convID := newConversation(t, q)
run, err := ss.CreateRun(ctx, convID)
@ -195,11 +213,12 @@ func TestCheckpointStore_CreateAndList(t *testing.T) {
}
func TestCheckpointStore_GetByName(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
ss := agent.NewStateStore(q)
cs := agent.NewCheckpointStore(q)
ctx := context.Background()
ctx := t.Context()
convID := newConversation(t, q)
run, err := ss.CreateRun(ctx, convID)
@ -217,10 +236,11 @@ func TestCheckpointStore_GetByName(t *testing.T) {
}
func TestCheckpointStore_EmptyName(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
cs := agent.NewCheckpointStore(q)
ctx := context.Background()
ctx := t.Context()
convID := newConversation(t, q)
_, err := cs.CreateCheckpoint(ctx, convID, " ", ids.New(), nil)

View file

@ -1,11 +1,11 @@
package agent_test
import (
"context"
jsonv2 "github.com/go-json-experiment/json"
"strings"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"charm.land/fantasy"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -21,7 +21,7 @@ func setupSearchFixture(t *testing.T) (fantasy.AgentTool, agent.KVDelegate, stri
q := db.delegate.Queries()
convID := newConversation(t, q)
s := agent.NewStateStore(q)
run, err := s.CreateRun(context.Background(), convID)
run, err := s.CreateRun(t.Context(), convID)
require.NoError(t, err)
kv := agent.NewDelegateKV(db.delegate, "search-test")
@ -41,7 +41,7 @@ func setupSearchFixture(t *testing.T) (fantasy.AgentTool, agent.KVDelegate, stri
{ToolCallID: "c2", ToolName: "beta"},
{ToolCallID: "c3", ToolName: "alpha"},
}
_, err = r.Execute(context.Background(), nil, calls, nil)
_, err = r.Execute(t.Context(), nil, calls, nil)
require.NoError(t, err)
tool := agent.NewToolResultSearchTool(q, kv)
@ -51,7 +51,7 @@ func setupSearchFixture(t *testing.T) (fantasy.AgentTool, agent.KVDelegate, stri
// invokeSearch calls the search tool with the given input and parses the JSON response.
func invokeSearch(t *testing.T, tool fantasy.AgentTool, input agent.ToolResultSearchInput) map[string]any {
t.Helper()
resp, err := tool.Run(context.Background(), fantasy.ToolCall{
resp, err := tool.Run(t.Context(), fantasy.ToolCall{
Input: marshalInput(t, input),
})
require.NoError(t, err)
@ -69,89 +69,102 @@ func marshalInput(t *testing.T, v any) string {
return string(b)
}
func TestToolResultSearch_ByConversationID(t *testing.T) {
tool, _, convID, _ := setupSearchFixture(t)
ctx := context.Background()
_ = ctx
func TestToolResultSearch_QueryFilters(t *testing.T) {
t.Parallel()
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
tests := []struct {
name string
build func(convID, runID string) agent.ToolResultSearchInput
wantTotal float64
wantTCID string
}{
{
name: "by conversation ID",
build: func(convID, _ string) agent.ToolResultSearchInput {
return agent.ToolResultSearchInput{
ConversationID: convID,
Limit: 10,
})
total, _ := out["total"].(float64)
assert.Equal(t, float64(3), total, "should find all 3 results by conversation_id")
}
func TestToolResultSearch_ByRunID(t *testing.T) {
tool, _, _, runID := setupSearchFixture(t)
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
}
},
wantTotal: 3,
},
{
name: "by run ID",
build: func(_, runID string) agent.ToolResultSearchInput {
return agent.ToolResultSearchInput{
RunID: runID,
Limit: 10,
})
total, _ := out["total"].(float64)
assert.Equal(t, float64(3), total)
}
func TestToolResultSearch_ByRunID_AndToolCallID(t *testing.T) {
tool, _, _, runID := setupSearchFixture(t)
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
}
},
wantTotal: 3,
},
{
name: "by run ID and tool_call_id",
build: func(_, runID string) agent.ToolResultSearchInput {
return agent.ToolResultSearchInput{
RunID: runID,
ToolCallID: "c2",
})
total, _ := out["total"].(float64)
assert.Equal(t, float64(1), total)
items := out["items"].([]any)
item := items[0].(map[string]any)
assert.Equal(t, "c2", item["tool_call_id"])
}
func TestToolResultSearch_FilterByToolName(t *testing.T) {
tool, _, convID, _ := setupSearchFixture(t)
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
}
},
wantTotal: 1,
wantTCID: "c2",
},
{
name: "filter by tool_name",
build: func(convID, _ string) agent.ToolResultSearchInput {
return agent.ToolResultSearchInput{
ConversationID: convID,
ToolName: "alpha",
Limit: 10,
})
total, _ := out["total"].(float64)
assert.Equal(t, float64(2), total, "filter by tool_name should return only 'alpha' results")
}
func TestToolResultSearch_FilterByQuery(t *testing.T) {
tool, _, convID, _ := setupSearchFixture(t)
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
}
},
wantTotal: 2,
},
{
name: "filter by query",
build: func(convID, _ string) agent.ToolResultSearchInput {
return agent.ToolResultSearchInput{
ConversationID: convID,
Query: "beta",
Limit: 10,
})
total, _ := out["total"].(float64)
assert.Equal(t, float64(1), total)
}
func TestToolResultSearch_DefaultLimit(t *testing.T) {
tool, _, convID, _ := setupSearchFixture(t)
// No limit specified -- default is 5, but we only have 3 items
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
}
},
wantTotal: 1,
},
{
name: "default limit",
build: func(convID, _ string) agent.ToolResultSearchInput {
return agent.ToolResultSearchInput{
ConversationID: convID,
})
}
},
wantTotal: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tool, _, convID, runID := setupSearchFixture(t)
input := tt.build(convID, runID)
out := invokeSearch(t, tool, input)
total, _ := out["total"].(float64)
assert.Equal(t, float64(3), total)
assert.Equal(t, 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"])
}
})
}
}
func TestToolResultSearch_MissingConvAndRunID_ErrorResponse(t *testing.T) {
t.Parallel()
tool, _, _, _ := setupSearchFixture(t)
resp, err := tool.Run(context.Background(), fantasy.ToolCall{
resp, err := tool.Run(t.Context(), fantasy.ToolCall{
Input: marshalInput(t, agent.ToolResultSearchInput{}),
})
require.NoError(t, err)
@ -163,11 +176,12 @@ func TestToolResultSearch_MissingConvAndRunID_ErrorResponse(t *testing.T) {
// TestToolResultSearch_LineView verifies that the KV full-result is sliced
// into the requested line range.
func TestToolResultSearch_LineView(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
convID := newConversation(t, q)
s := agent.NewStateStore(q)
run, err := s.CreateRun(context.Background(), convID)
run, err := s.CreateRun(t.Context(), convID)
require.NoError(t, err)
kv := agent.NewDelegateKV(db.delegate, "line-view-test")
@ -189,7 +203,7 @@ func TestToolResultSearch_LineView(t *testing.T) {
RunID: run.ID,
ThresholdChars: 100000,
}
_, err = r.Execute(context.Background(), nil, []fantasy.ToolCallContent{{ToolCallID: "lv1", ToolName: "liner"}}, nil)
_, err = r.Execute(t.Context(), nil, []fantasy.ToolCallContent{{ToolCallID: "lv1", ToolName: "liner"}}, nil)
require.NoError(t, err)
tool := agent.NewToolResultSearchTool(q, kv)
@ -220,11 +234,12 @@ func TestToolResultSearch_LineView(t *testing.T) {
// TestToolResultSearch_ChunkView verifies chunk-range retrieval from KV.
func TestToolResultSearch_ChunkView(t *testing.T) {
t.Parallel()
db := newTestQueries(t)
q := db.delegate.Queries()
convID := newConversation(t, q)
s := agent.NewStateStore(q)
run, err := s.CreateRun(context.Background(), convID)
run, err := s.CreateRun(t.Context(), convID)
require.NoError(t, err)
kv := agent.NewDelegateKV(db.delegate, "chunk-view-test")
@ -247,7 +262,7 @@ func TestToolResultSearch_ChunkView(t *testing.T) {
ThresholdChars: 10,
ChunkChars: 20,
}
_, err = r.Execute(context.Background(), nil, []fantasy.ToolCallContent{{ToolCallID: "cv1", ToolName: "chunker"}}, nil)
_, err = r.Execute(t.Context(), nil, []fantasy.ToolCallContent{{ToolCallID: "cv1", ToolName: "chunker"}}, nil)
require.NoError(t, err)
tool := agent.NewToolResultSearchTool(q, kv)

View file

@ -24,6 +24,7 @@ func makeJWTForClaims(t *testing.T, claims map[string]interface{}) string {
}
func TestBuildAuthorizeURL(t *testing.T) {
t.Parallel()
cfg := OAuthProviderConfig{
Issuer: "https://auth.example.com",
ClientID: "test-client-id",
@ -68,6 +69,7 @@ func TestBuildAuthorizeURL(t *testing.T) {
}
func TestBuildAuthorizeURLOpenAIExtras(t *testing.T) {
t.Parallel()
cfg := OpenAIOAuthConfig()
pkce := PKCECodes{CodeVerifier: "test-verifier", CodeChallenge: "test-challenge"}
@ -90,6 +92,7 @@ func TestBuildAuthorizeURLOpenAIExtras(t *testing.T) {
}
func TestParseTokenResponse(t *testing.T) {
t.Parallel()
resp := map[string]interface{}{
"access_token": "test-access-token",
"refresh_token": "test-refresh-token",
@ -121,6 +124,7 @@ func TestParseTokenResponse(t *testing.T) {
}
func TestParseTokenResponseExtractsAccountIDFromIDToken(t *testing.T) {
t.Parallel()
idToken := makeJWTForClaims(t, map[string]interface{}{"chatgpt_account_id": "acc-id-from-id-token"})
resp := map[string]interface{}{
"access_token": "opaque-access-token",
@ -140,6 +144,7 @@ func TestParseTokenResponseExtractsAccountIDFromIDToken(t *testing.T) {
}
func TestExtractAccountIDFromOrganizationsFallback(t *testing.T) {
t.Parallel()
token := makeJWTForClaims(t, map[string]interface{}{
"organizations": []interface{}{
map[string]interface{}{"id": "org_from_orgs"},
@ -152,6 +157,7 @@ func TestExtractAccountIDFromOrganizationsFallback(t *testing.T) {
}
func TestParseTokenResponseNoAccessToken(t *testing.T) {
t.Parallel()
body := []byte(`{"refresh_token": "test"}`)
_, err := parseTokenResponse(body, "openai")
if err == nil {
@ -160,6 +166,7 @@ func TestParseTokenResponseNoAccessToken(t *testing.T) {
}
func TestParseTokenResponseAccountIDFromIDToken(t *testing.T) {
t.Parallel()
idToken := makeJWTWithAccountID("acc-from-id")
resp := map[string]interface{}{
"access_token": "not-a-jwt",
@ -186,6 +193,7 @@ func makeJWTWithAccountID(accountID string) string {
}
func TestExchangeCodeForTokens(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/oauth/token" {
http.Error(w, "not found", http.StatusNotFound)
@ -229,6 +237,7 @@ func TestExchangeCodeForTokens(t *testing.T) {
}
func TestRefreshAccessToken(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/oauth/token" {
http.Error(w, "not found", http.StatusNotFound)
@ -276,6 +285,7 @@ func TestRefreshAccessToken(t *testing.T) {
}
func TestRefreshAccessTokenNoRefreshToken(t *testing.T) {
t.Parallel()
cfg := OpenAIOAuthConfig()
cred := &AuthCredential{
AccessToken: "old-token",
@ -290,6 +300,7 @@ func TestRefreshAccessTokenNoRefreshToken(t *testing.T) {
}
func TestRefreshAccessTokenPreservesRefreshAndAccountID(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := map[string]interface{}{
"access_token": "new-access-token-only",
@ -321,6 +332,7 @@ func TestRefreshAccessTokenPreservesRefreshAndAccountID(t *testing.T) {
}
func TestOpenAIOAuthConfig(t *testing.T) {
t.Parallel()
cfg := OpenAIOAuthConfig()
if cfg.Issuer != "https://auth.openai.com" {
t.Errorf("Issuer = %q, want %q", cfg.Issuer, "https://auth.openai.com")
@ -334,6 +346,7 @@ func TestOpenAIOAuthConfig(t *testing.T) {
}
func TestParseDeviceCodeResponseIntervalAsNumber(t *testing.T) {
t.Parallel()
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":5}`)
resp, err := parseDeviceCodeResponse(body)
@ -353,6 +366,7 @@ func TestParseDeviceCodeResponseIntervalAsNumber(t *testing.T) {
}
func TestParseDeviceCodeResponseIntervalAsString(t *testing.T) {
t.Parallel()
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":"5"}`)
resp, err := parseDeviceCodeResponse(body)
@ -366,6 +380,7 @@ func TestParseDeviceCodeResponseIntervalAsString(t *testing.T) {
}
func TestParseDeviceCodeResponseInvalidInterval(t *testing.T) {
t.Parallel()
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":"abc"}`)
if _, err := parseDeviceCodeResponse(body); err == nil {

View file

@ -7,6 +7,7 @@ import (
)
func TestGeneratePKCE(t *testing.T) {
t.Parallel()
codes, err := GeneratePKCE()
if err != nil {
t.Fatalf("GeneratePKCE() error: %v", err)
@ -35,6 +36,7 @@ func TestGeneratePKCE(t *testing.T) {
}
func TestGeneratePKCEUniqueness(t *testing.T) {
t.Parallel()
codes1, err := GeneratePKCE()
if err != nil {
t.Fatalf("GeneratePKCE() error: %v", err)

View file

@ -8,6 +8,7 @@ import (
)
func TestAuthCredentialIsExpired(t *testing.T) {
t.Parallel()
tests := []struct {
name string
expiresAt time.Time
@ -29,6 +30,7 @@ func TestAuthCredentialIsExpired(t *testing.T) {
}
func TestAuthCredentialNeedsRefresh(t *testing.T) {
t.Parallel()
tests := []struct {
name string
expiresAt time.Time
@ -51,6 +53,7 @@ func TestAuthCredentialNeedsRefresh(t *testing.T) {
}
func TestStoreRoundtrip(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
origHome := os.Getenv("HOME")
t.Setenv("HOME", tmpDir)
@ -114,6 +117,7 @@ func TestStoreFilePermissions(t *testing.T) {
}
func TestStoreMultiProvider(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
origHome := os.Getenv("HOME")
t.Setenv("HOME", tmpDir)
@ -147,6 +151,7 @@ func TestStoreMultiProvider(t *testing.T) {
}
func TestDeleteCredential(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
origHome := os.Getenv("HOME")
t.Setenv("HOME", tmpDir)
@ -171,6 +176,7 @@ func TestDeleteCredential(t *testing.T) {
}
func TestLoadStoreEmpty(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
origHome := os.Getenv("HOME")
t.Setenv("HOME", tmpDir)

22
pkg/cache/lru_test.go vendored
View file

@ -33,6 +33,7 @@ func (c *mockClock) Advance(d time.Duration) {
// --- Basic LRU Tests ---
func TestLRU_SetAndGet(t *testing.T) {
t.Parallel()
c := New[string, int](Options[string, int]{MaxSize: 10})
c.Set("a", 1)
@ -55,6 +56,7 @@ func TestLRU_SetAndGet(t *testing.T) {
}
func TestLRU_Update(t *testing.T) {
t.Parallel()
c := New[string, string](Options[string, string]{MaxSize: 10})
c.Set("k", "v1")
@ -71,6 +73,7 @@ func TestLRU_Update(t *testing.T) {
}
func TestLRU_EvictionOrder(t *testing.T) {
t.Parallel()
var evicted []string
c := New[string, int](Options[string, int]{
MaxSize: 3,
@ -110,6 +113,7 @@ func TestLRU_EvictionOrder(t *testing.T) {
}
func TestLRU_MaxSizeZero_Unlimited(t *testing.T) {
t.Parallel()
c := New[int, int](Options[int, int]{})
for i := 0; i < 1000; i++ {
@ -122,6 +126,7 @@ func TestLRU_MaxSizeZero_Unlimited(t *testing.T) {
}
func TestLRU_Delete(t *testing.T) {
t.Parallel()
var evictCalled bool
c := New[string, int](Options[string, int]{
MaxSize: 10,
@ -148,6 +153,7 @@ func TestLRU_Delete(t *testing.T) {
}
func TestLRU_Clear(t *testing.T) {
t.Parallel()
var evictCount int
c := New[string, int](Options[string, int]{
MaxSize: 10,
@ -171,6 +177,7 @@ func TestLRU_Clear(t *testing.T) {
}
func TestLRU_Peek_DoesNotPromote(t *testing.T) {
t.Parallel()
c := New[string, int](Options[string, int]{MaxSize: 3})
c.Set("a", 1)
@ -196,6 +203,7 @@ func TestLRU_Peek_DoesNotPromote(t *testing.T) {
// --- TTL Tests ---
func TestLRU_TTL_Expiry(t *testing.T) {
t.Parallel()
clock := newMockClock(time.Now())
c := New[string, int](Options[string, int]{
MaxSize: 10,
@ -225,6 +233,7 @@ func TestLRU_TTL_Expiry(t *testing.T) {
}
func TestLRU_TTL_PerEntry(t *testing.T) {
t.Parallel()
clock := newMockClock(time.Now())
c := New[string, int](Options[string, int]{
MaxSize: 10,
@ -251,6 +260,7 @@ func TestLRU_TTL_PerEntry(t *testing.T) {
}
func TestLRU_Peek_ExpiresEntries(t *testing.T) {
t.Parallel()
clock := newMockClock(time.Now())
c := New[string, int](Options[string, int]{
MaxSize: 10,
@ -268,6 +278,7 @@ func TestLRU_Peek_ExpiresEntries(t *testing.T) {
}
func TestLRU_Purge(t *testing.T) {
t.Parallel()
clock := newMockClock(time.Now())
c := New[string, int](Options[string, int]{
MaxSize: 10,
@ -300,6 +311,7 @@ func TestLRU_Purge(t *testing.T) {
// --- Stale-While-Revalidate Tests ---
func TestLRU_SWR_ReturnsStaleAndRefreshes(t *testing.T) {
t.Parallel()
clock := newMockClock(time.Now())
var fetchCalls atomic.Int32
refreshDone := make(chan struct{}, 1)
@ -347,6 +359,7 @@ func TestLRU_SWR_ReturnsStaleAndRefreshes(t *testing.T) {
}
func TestLRU_SWR_HardExpiry(t *testing.T) {
t.Parallel()
clock := newMockClock(time.Now())
c := New[string, int](Options[string, int]{
MaxSize: 10,
@ -367,6 +380,7 @@ func TestLRU_SWR_HardExpiry(t *testing.T) {
}
func TestLRU_SWR_NoFetchFunc_StaleStillReturned(t *testing.T) {
t.Parallel()
clock := newMockClock(time.Now())
c := New[string, int](Options[string, int]{
MaxSize: 10,
@ -388,6 +402,7 @@ func TestLRU_SWR_NoFetchFunc_StaleStillReturned(t *testing.T) {
// --- Tag-Based Invalidation Tests ---
func TestLRU_Tags_InvalidateByTag(t *testing.T) {
t.Parallel()
c := New[string, int](Options[string, int]{MaxSize: 10})
c.SetWithTags("user:1", 1, []string{"users"})
@ -423,6 +438,7 @@ func TestLRU_Tags_InvalidateByTag(t *testing.T) {
}
func TestLRU_Tags_InvalidateNonexistentTag(t *testing.T) {
t.Parallel()
c := New[string, int](Options[string, int]{MaxSize: 10})
c.Set("a", 1)
@ -435,6 +451,7 @@ func TestLRU_Tags_InvalidateNonexistentTag(t *testing.T) {
}
func TestLRU_Tags_UpdateRemovesOldTags(t *testing.T) {
t.Parallel()
c := New[string, int](Options[string, int]{MaxSize: 10})
c.SetWithTags("k", 1, []string{"tag-a"})
@ -458,6 +475,7 @@ func TestLRU_Tags_UpdateRemovesOldTags(t *testing.T) {
// --- Concurrent Access Tests ---
func TestLRU_ConcurrentAccess(t *testing.T) {
t.Parallel()
c := New[int, int](Options[int, int]{MaxSize: 100})
var wg sync.WaitGroup
@ -493,6 +511,7 @@ func TestLRU_ConcurrentAccess(t *testing.T) {
}
func TestLRU_ConcurrentTags(t *testing.T) {
t.Parallel()
c := New[string, int](Options[string, int]{MaxSize: 100})
var wg sync.WaitGroup
@ -524,6 +543,7 @@ func TestLRU_ConcurrentTags(t *testing.T) {
// --- Edge Cases ---
func TestLRU_ZeroTTL_NoExpiry(t *testing.T) {
t.Parallel()
clock := newMockClock(time.Now())
c := New[string, int](Options[string, int]{
MaxSize: 10,
@ -540,6 +560,7 @@ func TestLRU_ZeroTTL_NoExpiry(t *testing.T) {
}
func TestLRU_MaxSizeOne(t *testing.T) {
t.Parallel()
c := New[string, int](Options[string, int]{MaxSize: 1})
c.Set("a", 1)
@ -557,6 +578,7 @@ func TestLRU_MaxSizeOne(t *testing.T) {
}
func TestLRU_Keys_Order(t *testing.T) {
t.Parallel()
c := New[string, int](Options[string, int]{MaxSize: 10})
c.Set("a", 1)

View file

@ -3,6 +3,7 @@ package channels
import "testing"
func TestBaseChannelIsAllowed(t *testing.T) {
t.Parallel()
tests := []struct {
name string
allowList []string

View file

@ -8,6 +8,7 @@ import (
)
func TestParseSlackChatID(t *testing.T) {
t.Parallel()
tests := []struct {
name string
chatID string
@ -54,6 +55,7 @@ func TestParseSlackChatID(t *testing.T) {
}
func TestStripBotMention(t *testing.T) {
t.Parallel()
ch := &SlackChannel{botUserID: "U12345BOT"}
tests := []struct {
@ -99,6 +101,7 @@ func TestStripBotMention(t *testing.T) {
}
func TestNewSlackChannel(t *testing.T) {
t.Parallel()
msgBus := bus.NewMessageBus()
t.Run("missing bot token", func(t *testing.T) {
@ -143,6 +146,7 @@ func TestNewSlackChannel(t *testing.T) {
}
func TestSlackChannelIsAllowed(t *testing.T) {
t.Parallel()
msgBus := bus.NewMessageBus()
t.Run("empty allowlist allows all", func(t *testing.T) {

View file

@ -10,6 +10,7 @@ import (
// TestDefaultConfig_HeartbeatEnabled verifies heartbeat is enabled by default
func TestDefaultConfig_HeartbeatEnabled(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
if !cfg.Heartbeat.Enabled {
@ -19,6 +20,7 @@ func TestDefaultConfig_HeartbeatEnabled(t *testing.T) {
// TestDefaultConfig_SandboxPath verifies sandbox path is resolvable
func TestDefaultConfig_SandboxPath(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
path := cfg.SandboxPath()
if path == "" {
@ -28,6 +30,7 @@ func TestDefaultConfig_SandboxPath(t *testing.T) {
// TestDefaultConfig_Model verifies model is set
func TestDefaultConfig_Model(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
if cfg.Agents.Defaults.Model == "" {
@ -37,6 +40,7 @@ func TestDefaultConfig_Model(t *testing.T) {
// TestDefaultConfig_MaxTokens verifies max tokens has default value
func TestDefaultConfig_MaxTokens(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
if cfg.Agents.Defaults.MaxTokens == 0 {
@ -46,6 +50,7 @@ func TestDefaultConfig_MaxTokens(t *testing.T) {
// TestDefaultConfig_MaxToolIterations verifies max tool iterations has default value
func TestDefaultConfig_MaxToolIterations(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
if cfg.Agents.Defaults.MaxToolIterations == 0 {
@ -54,6 +59,7 @@ func TestDefaultConfig_MaxToolIterations(t *testing.T) {
}
func TestDefaultConfig_ContinuityRetention(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
if cfg.Agents.Defaults.ContinuityRetention.MinMessages <= 0 {
@ -72,6 +78,7 @@ func TestDefaultConfig_ContinuityRetention(t *testing.T) {
// TestDefaultConfig_Temperature verifies temperature has default value
func TestDefaultConfig_Temperature(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
if cfg.Agents.Defaults.Temperature == 0 {
@ -81,6 +88,7 @@ func TestDefaultConfig_Temperature(t *testing.T) {
// TestDefaultConfig_Gateway verifies gateway defaults
func TestDefaultConfig_Gateway(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
if cfg.Gateway.Host != "0.0.0.0" {
@ -93,6 +101,7 @@ func TestDefaultConfig_Gateway(t *testing.T) {
// TestDefaultConfig_Providers verifies provider structure
func TestDefaultConfig_Providers(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
// Verify all providers are empty by default
@ -121,6 +130,7 @@ func TestDefaultConfig_Providers(t *testing.T) {
// TestDefaultConfig_Channels verifies channels are disabled by default
func TestDefaultConfig_Channels(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
// Verify all channels are disabled by default
@ -152,6 +162,7 @@ func TestDefaultConfig_Channels(t *testing.T) {
// TestDefaultConfig_WebTools verifies web tools config
func TestDefaultConfig_WebTools(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
// Verify web tools defaults
@ -167,6 +178,7 @@ func TestDefaultConfig_WebTools(t *testing.T) {
}
func TestSaveConfig_FilePermissions(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
t.Skip("file permission bits are not enforced on Windows")
}
@ -192,6 +204,7 @@ func TestSaveConfig_FilePermissions(t *testing.T) {
// TestConfig_Complete verifies all config fields are set
func TestConfig_Complete(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
// Verify complete config structure
@ -222,6 +235,7 @@ func TestConfig_Complete(t *testing.T) {
}
func TestDefaultConfig_OpenAIWebSearchEnabled(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
if !cfg.Providers.OpenAI.WebSearch {
t.Fatal("DefaultConfig().Providers.OpenAI.WebSearch should be true")
@ -229,6 +243,7 @@ func TestDefaultConfig_OpenAIWebSearchEnabled(t *testing.T) {
}
func TestLoadConfig_OpenAIWebSearchDefaultsTrueWhenUnset(t *testing.T) {
t.Parallel()
dir := t.TempDir()
configPath := filepath.Join(dir, "config.json")
if err := os.WriteFile(configPath, []byte(`{"providers":{"openai":{"api_base":""}}}`), 0o600); err != nil {
@ -245,6 +260,7 @@ func TestLoadConfig_OpenAIWebSearchDefaultsTrueWhenUnset(t *testing.T) {
}
func TestValidate_MemoryConfig(t *testing.T) {
t.Parallel()
tests := []struct {
name string
mutate func(*Config)
@ -318,6 +334,7 @@ func TestValidate_MemoryConfig(t *testing.T) {
}
func TestValidate_ContinuityRetentionConfig(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
cfg.Agents.Defaults.ContinuityRetention.MinMessages = 8
cfg.Agents.Defaults.ContinuityRetention.MaxMessages = 4
@ -342,6 +359,7 @@ func containsMemoryWarning(s string) bool {
}
func TestLoadConfig_OpenAIWebSearchCanBeDisabled(t *testing.T) {
t.Parallel()
dir := t.TempDir()
configPath := filepath.Join(dir, "config.json")
if err := os.WriteFile(configPath, []byte(`{"providers":{"openai":{"web_search":false}}}`), 0o600); err != nil {

View file

@ -8,6 +8,7 @@ import (
)
func TestSaveStore_FilePermissions(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
t.Skip("file permission bits are not enforced on Windows")
}

View file

@ -103,6 +103,7 @@ func (t *mockNilResultTool) Execute(_ context.Context, _ map[string]interface{})
// --- PicoToolAdapter.Info() Tests ---
func TestAdapter_Info(t *testing.T) {
t.Parallel()
adapter := &PicoToolAdapter{inner: &mockDualChannelTool{}}
info := adapter.Info()
@ -127,6 +128,7 @@ func TestAdapter_Info(t *testing.T) {
}
func TestAdapter_Info_UnwrapsSchemaWithRequired(t *testing.T) {
t.Parallel()
mock := &mockToolWithRequired{}
adapter := &PicoToolAdapter{inner: mock}
info := adapter.Info()
@ -159,6 +161,7 @@ func (t *mockToolWithRequired) Execute(_ context.Context, _ map[string]interface
// --- PicoToolAdapter.Run() Tests ---
func TestAdapter_Run_SilentTool_NoPublish(t *testing.T) {
t.Parallel()
msgBus := bus.NewMessageBus()
adapter := &PicoToolAdapter{
inner: &mockSilentTool{},
@ -173,7 +176,7 @@ func TestAdapter_Run_SilentTool_NoPublish(t *testing.T) {
Input: "{}",
}
resp, err := adapter.Run(context.Background(), call)
resp, err := adapter.Run(t.Context(), call)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -187,6 +190,7 @@ func TestAdapter_Run_SilentTool_NoPublish(t *testing.T) {
}
func TestAdapter_Run_DualChannel_PublishesForUser(t *testing.T) {
t.Parallel()
msgBus := bus.NewMessageBus()
adapter := &PicoToolAdapter{
inner: &mockDualChannelTool{},
@ -201,7 +205,7 @@ func TestAdapter_Run_DualChannel_PublishesForUser(t *testing.T) {
Input: `{"input": "hello"}`,
}
resp, err := adapter.Run(context.Background(), call)
resp, err := adapter.Run(t.Context(), call)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -216,6 +220,7 @@ func TestAdapter_Run_DualChannel_PublishesForUser(t *testing.T) {
}
func TestAdapter_Run_ErrorTool_ReturnsErrorResponse(t *testing.T) {
t.Parallel()
adapter := &PicoToolAdapter{
inner: &mockErrorTool{},
}
@ -226,7 +231,7 @@ func TestAdapter_Run_ErrorTool_ReturnsErrorResponse(t *testing.T) {
Input: "{}",
}
resp, err := adapter.Run(context.Background(), call)
resp, err := adapter.Run(t.Context(), call)
if err != nil {
t.Fatalf("Unexpected error (adapter should not return Go errors): %v", err)
}
@ -240,6 +245,7 @@ func TestAdapter_Run_ErrorTool_ReturnsErrorResponse(t *testing.T) {
}
func TestAdapter_Run_NilResult_ReturnsError(t *testing.T) {
t.Parallel()
adapter := &PicoToolAdapter{
inner: &mockNilResultTool{},
}
@ -250,7 +256,7 @@ func TestAdapter_Run_NilResult_ReturnsError(t *testing.T) {
Input: "{}",
}
resp, err := adapter.Run(context.Background(), call)
resp, err := adapter.Run(t.Context(), call)
if err != nil {
t.Fatalf("Unexpected Go error: %v", err)
}
@ -264,6 +270,7 @@ func TestAdapter_Run_NilResult_ReturnsError(t *testing.T) {
}
func TestAdapter_Run_ContextualTool_SetsContext(t *testing.T) {
t.Parallel()
ctxTool := &mockContextualTool{}
adapter := &PicoToolAdapter{
inner: ctxTool,
@ -277,7 +284,7 @@ func TestAdapter_Run_ContextualTool_SetsContext(t *testing.T) {
Input: "{}",
}
resp, err := adapter.Run(context.Background(), call)
resp, err := adapter.Run(t.Context(), call)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -289,6 +296,7 @@ func TestAdapter_Run_ContextualTool_SetsContext(t *testing.T) {
}
func TestAdapter_Run_InvalidJSON_ReturnsError(t *testing.T) {
t.Parallel()
adapter := &PicoToolAdapter{
inner: &mockSilentTool{},
}
@ -299,7 +307,7 @@ func TestAdapter_Run_InvalidJSON_ReturnsError(t *testing.T) {
Input: "not valid json{{{",
}
resp, err := adapter.Run(context.Background(), call)
resp, err := adapter.Run(t.Context(), call)
if err != nil {
t.Fatalf("Unexpected Go error: %v", err)
}
@ -312,6 +320,7 @@ func TestAdapter_Run_InvalidJSON_ReturnsError(t *testing.T) {
// --- BuildAdaptedTools Tests ---
func TestBuildAdaptedTools_NilRegistry(t *testing.T) {
t.Parallel()
result := BuildAdaptedTools(nil, nil, "", "")
if result != nil {
t.Error("Expected nil for nil registry")
@ -319,6 +328,7 @@ func TestBuildAdaptedTools_NilRegistry(t *testing.T) {
}
func TestBuildAdaptedTools_WrapsAllTools(t *testing.T) {
t.Parallel()
registry := tools.NewToolRegistry()
registry.Register(&mockSilentTool{})
registry.Register(&mockDualChannelTool{})
@ -348,6 +358,7 @@ func TestBuildAdaptedTools_WrapsAllTools(t *testing.T) {
// --- parseToolArgs Tests ---
func TestParseToolArgs_EmptyInput(t *testing.T) {
t.Parallel()
args, err := parseToolArgs("")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -358,6 +369,7 @@ func TestParseToolArgs_EmptyInput(t *testing.T) {
}
func TestParseToolArgs_EmptyObject(t *testing.T) {
t.Parallel()
args, err := parseToolArgs("{}")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -368,6 +380,7 @@ func TestParseToolArgs_EmptyObject(t *testing.T) {
}
func TestParseToolArgs_ValidJSON(t *testing.T) {
t.Parallel()
args, err := parseToolArgs(`{"key": "value", "num": 42}`)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -378,6 +391,7 @@ func TestParseToolArgs_ValidJSON(t *testing.T) {
}
func TestParseToolArgs_InvalidJSON(t *testing.T) {
t.Parallel()
_, err := parseToolArgs("not json")
if err == nil {
t.Error("Expected error for invalid JSON")

View file

@ -11,6 +11,7 @@ import (
// --- MessagesToFantasy Tests ---
func TestMessagesToFantasy_EmptySlice(t *testing.T) {
t.Parallel()
result := MessagesToFantasy(nil)
if len(result) != 0 {
t.Errorf("Expected empty slice for nil input, got %d", len(result))
@ -23,6 +24,7 @@ func TestMessagesToFantasy_EmptySlice(t *testing.T) {
}
func TestMessageToFantasy_SimpleTextMessage(t *testing.T) {
t.Parallel()
msg := messages.Message{
Role: "user",
Content: "Hello, world",
@ -47,6 +49,7 @@ func TestMessageToFantasy_SimpleTextMessage(t *testing.T) {
}
func TestMessageToFantasy_AssistantWithMultipleToolCalls(t *testing.T) {
t.Parallel()
msg := messages.Message{
Role: "assistant",
Content: "Let me run both tools.",
@ -113,6 +116,7 @@ func TestMessageToFantasy_AssistantWithMultipleToolCalls(t *testing.T) {
}
func TestMessageToFantasy_ToolCallWithMapArgsFallback(t *testing.T) {
t.Parallel()
msg := messages.Message{
Role: "assistant",
ToolCalls: []messages.ToolCall{
@ -145,6 +149,7 @@ func TestMessageToFantasy_ToolCallWithMapArgsFallback(t *testing.T) {
}
func TestMessageToFantasy_ToolResultMessage(t *testing.T) {
t.Parallel()
msg := messages.Message{
Role: "tool",
Content: "file contents here",
@ -177,6 +182,7 @@ func TestMessageToFantasy_ToolResultMessage(t *testing.T) {
}
func TestMessageToFantasy_EmptyContent(t *testing.T) {
t.Parallel()
msg := messages.Message{
Role: "assistant",
Content: "",
@ -193,6 +199,7 @@ func TestMessageToFantasy_EmptyContent(t *testing.T) {
// --- StepToMessages Tests ---
func TestStepToMessages_TextOnly(t *testing.T) {
t.Parallel()
step := fantasy.StepResult{
Response: fantasy.Response{
Content: fantasy.ResponseContent{
@ -216,6 +223,7 @@ func TestStepToMessages_TextOnly(t *testing.T) {
}
func TestStepToMessages_MultipleToolCalls(t *testing.T) {
t.Parallel()
step := fantasy.StepResult{
Response: fantasy.Response{
Content: fantasy.ResponseContent{
@ -280,6 +288,7 @@ func TestStepToMessages_MultipleToolCalls(t *testing.T) {
}
func TestStepToMessages_ErrorToolResult(t *testing.T) {
t.Parallel()
testErr := errors.New("permission denied")
step := fantasy.StepResult{
Response: fantasy.Response{
@ -315,6 +324,7 @@ func TestStepToMessages_ErrorToolResult(t *testing.T) {
}
func TestStepToMessages_ErrorToolResult_NilError(t *testing.T) {
t.Parallel()
step := fantasy.StepResult{
Response: fantasy.Response{
Content: fantasy.ResponseContent{
@ -339,6 +349,7 @@ func TestStepToMessages_ErrorToolResult_NilError(t *testing.T) {
}
func TestStepToMessages_ToolCallWithoutText(t *testing.T) {
t.Parallel()
step := fantasy.StepResult{
Response: fantasy.Response{
Content: fantasy.ResponseContent{
@ -372,6 +383,7 @@ func TestStepToMessages_ToolCallWithoutText(t *testing.T) {
// --- AgentResultToMessages Tests ---
func TestAgentResultToMessages_MultipleSteps(t *testing.T) {
t.Parallel()
result := &fantasy.AgentResult{
Steps: []fantasy.StepResult{
{
@ -417,7 +429,10 @@ func TestAgentResultToMessages_MultipleSteps(t *testing.T) {
// --- Round-trip fidelity test ---
func TestRoundTrip_PicoClawToFantasyAndBack(t *testing.T) {
t.Parallel(
// Start with DragonScale messages representing a typical conversation
)
original := []messages.Message{
{Role: "user", Content: "Read the file"},
{

View file

@ -11,6 +11,7 @@ import (
)
func TestExecuteHeartbeat_Async(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -49,6 +50,7 @@ func TestExecuteHeartbeat_Async(t *testing.T) {
}
func TestExecuteHeartbeat_Error(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -87,6 +89,7 @@ func TestExecuteHeartbeat_Error(t *testing.T) {
}
func TestExecuteHeartbeat_Silent(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -125,6 +128,7 @@ func TestExecuteHeartbeat_Silent(t *testing.T) {
}
func TestHeartbeatService_StartStop(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -144,6 +148,7 @@ func TestHeartbeatService_StartStop(t *testing.T) {
}
func TestHeartbeatService_Disabled(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -161,6 +166,7 @@ func TestHeartbeatService_Disabled(t *testing.T) {
}
func TestExecuteHeartbeat_NilResult(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -183,6 +189,7 @@ func TestExecuteHeartbeat_NilResult(t *testing.T) {
// TestLogPath verifies heartbeat log is written to workspace directory
func TestLogPath(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -203,6 +210,7 @@ func TestLogPath(t *testing.T) {
// TestHeartbeatFilePath verifies HEARTBEAT.md is at workspace root
func TestHeartbeatFilePath(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -222,6 +230,7 @@ func TestHeartbeatFilePath(t *testing.T) {
}
func TestExecuteHeartbeat_UsesDueContextWithoutHeartbeatFile(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)

View file

@ -7,6 +7,7 @@ import (
)
func TestNew_IsV7(t *testing.T) {
t.Parallel()
u := New()
if u.IsZero() {
@ -24,6 +25,7 @@ func TestNew_IsV7(t *testing.T) {
}
func TestNew_Unique(t *testing.T) {
t.Parallel()
seen := make(map[UUID]bool, 1000)
for i := 0; i < 1000; i++ {
u := New()
@ -35,6 +37,7 @@ func TestNew_Unique(t *testing.T) {
}
func TestNew_Monotonic(t *testing.T) {
t.Parallel()
a := New()
b := New()
// UUIDv7 embeds ms timestamp in first 6 bytes. b >= a in timestamp.
@ -50,6 +53,7 @@ func TestNew_Monotonic(t *testing.T) {
}
func TestParse_RoundTrip(t *testing.T) {
t.Parallel()
u := New()
s := u.String()
@ -63,6 +67,7 @@ func TestParse_RoundTrip(t *testing.T) {
}
func TestParse_Errors(t *testing.T) {
t.Parallel()
cases := []string{
"",
"not-a-uuid",
@ -78,6 +83,7 @@ func TestParse_Errors(t *testing.T) {
}
func TestIsZero(t *testing.T) {
t.Parallel()
var zero UUID
if !zero.IsZero() {
t.Fatal("zero UUID should be zero")
@ -89,6 +95,7 @@ func TestIsZero(t *testing.T) {
}
func TestValue_BlobRoundTrip(t *testing.T) {
t.Parallel()
u := New()
v, err := u.Value()
if err != nil {
@ -112,6 +119,7 @@ func TestValue_BlobRoundTrip(t *testing.T) {
}
func TestValue_ZeroIsNil(t *testing.T) {
t.Parallel()
var zero UUID
v, err := zero.Value()
if err != nil {
@ -123,6 +131,7 @@ func TestValue_ZeroIsNil(t *testing.T) {
}
func TestScan_NilLeavesZero(t *testing.T) {
t.Parallel()
var u UUID
if err := u.Scan(nil); err != nil {
t.Fatalf("Scan(nil): %v", err)
@ -133,6 +142,7 @@ func TestScan_NilLeavesZero(t *testing.T) {
}
func TestScan_String(t *testing.T) {
t.Parallel()
orig := New()
var u UUID
if err := u.Scan(orig.String()); err != nil {
@ -144,6 +154,7 @@ func TestScan_String(t *testing.T) {
}
func TestScan_InvalidBlob(t *testing.T) {
t.Parallel()
var u UUID
if err := u.Scan([]byte{1, 2, 3}); err == nil {
t.Fatal("Scan(3-byte blob) should fail")
@ -151,6 +162,7 @@ func TestScan_InvalidBlob(t *testing.T) {
}
func TestScan_InvalidType(t *testing.T) {
t.Parallel()
var u UUID
if err := u.Scan(42); err == nil {
t.Fatal("Scan(int) should fail")
@ -158,6 +170,7 @@ func TestScan_InvalidType(t *testing.T) {
}
func TestJSON_RoundTrip(t *testing.T) {
t.Parallel()
u := New()
b, err := jsonv2.Marshal(u)
@ -184,6 +197,7 @@ func TestJSON_RoundTrip(t *testing.T) {
}
func TestJSON_ZeroUUID(t *testing.T) {
t.Parallel()
var zero UUID
b, err := jsonv2.Marshal(zero)
if err != nil {
@ -196,6 +210,7 @@ func TestJSON_ZeroUUID(t *testing.T) {
}
func TestJSON_InStruct(t *testing.T) {
t.Parallel()
type record struct {
ID UUID `json:"id"`
Name string `json:"name"`
@ -217,6 +232,7 @@ func TestJSON_InStruct(t *testing.T) {
}
func TestFromBytes(t *testing.T) {
t.Parallel()
u := New()
b := u.Bytes()
restored := FromBytes(b)
@ -226,6 +242,7 @@ func TestFromBytes(t *testing.T) {
}
func TestMustParse_Panics(t *testing.T) {
t.Parallel()
defer func() {
if r := recover(); r == nil {
t.Fatal("MustParse should panic on bad input")
@ -235,6 +252,7 @@ func TestMustParse_Panics(t *testing.T) {
}
func TestString_Format(t *testing.T) {
t.Parallel()
u := New()
s := u.String()
if len(s) != 36 {
@ -246,6 +264,7 @@ func TestString_Format(t *testing.T) {
}
func TestUUIDComparable(t *testing.T) {
t.Parallel()
a := New()
b := a // copy
if a != b {

View file

@ -1,14 +1,17 @@
package itr
import (
jsonv2 "github.com/go-json-experiment/json"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestToolRequestMarshalRoundTrip(t *testing.T) {
t.Parallel()
tests := []struct {
name string
req ToolRequest
@ -68,16 +71,13 @@ func TestToolRequestMarshalRoundTrip(t *testing.T) {
decoded, err := UnmarshalRequest(data)
require.NoError(t, err)
assert.Equal(t, tt.req.ID, decoded.ID)
assert.Equal(t, tt.req.Type, decoded.Type)
assert.Equal(t, tt.req.SessionKey, decoded.SessionKey)
assert.Equal(t, tt.req.Depth, decoded.Depth)
assert.Equal(t, tt.req.ToolCallID, decoded.ToolCallID)
assert.Empty(t, cmp.Diff(tt.req, decoded))
})
}
}
func TestToolExecPayloadPreservation(t *testing.T) {
t.Parallel()
req := NewToolExecRequest("id-1", "s", "tc", "shell", `{"cmd":"ls -la"}`)
data, err := req.Marshal()
require.NoError(t, err)
@ -92,6 +92,7 @@ func TestToolExecPayloadPreservation(t *testing.T) {
}
func TestGrepPayloadPreservation(t *testing.T) {
t.Parallel()
req := NewGrepRequest("g1", "s", 2, "error.*fatal", 25, true)
data, err := req.Marshal()
require.NoError(t, err)
@ -107,6 +108,7 @@ func TestGrepPayloadPreservation(t *testing.T) {
}
func TestDAGPlanPayloadPreservation(t *testing.T) {
t.Parallel()
plan := DAGPlan{
Nodes: []DAGNode{
{ID: "a", Type: CmdToolSearch, Payload: ToolSearch{Query: "files", MaxResults: 5}, DependsOn: nil},
@ -143,6 +145,7 @@ func TestDAGPlanPayloadPreservation(t *testing.T) {
}
func TestToolResponseMarshalRoundTrip(t *testing.T) {
t.Parallel()
tests := []struct {
name string
resp ToolResponse
@ -169,17 +172,13 @@ func TestToolResponseMarshalRoundTrip(t *testing.T) {
decoded, err := UnmarshalResponse(data)
require.NoError(t, err)
assert.Equal(t, tt.resp.ID, decoded.ID)
assert.Equal(t, tt.resp.Result, decoded.Result)
assert.Equal(t, tt.resp.IsError, decoded.IsError)
assert.Equal(t, tt.resp.LeakDetected, decoded.LeakDetected)
assert.Equal(t, tt.resp.CostTokens, decoded.CostTokens)
assert.Equal(t, tt.resp.RedactedKeys, decoded.RedactedKeys)
assert.Empty(t, cmp.Diff(tt.resp, decoded))
})
}
}
func TestUnmarshalRequestJSON_UnknownType(t *testing.T) {
t.Parallel()
data, _ := jsonv2.Marshal(map[string]interface{}{
"id": "bad",
"type": "nonexistent_command",
@ -190,21 +189,25 @@ func TestUnmarshalRequestJSON_UnknownType(t *testing.T) {
}
func TestUnmarshalRequestJSON_InvalidJSON(t *testing.T) {
t.Parallel()
_, err := UnmarshalRequestJSON([]byte(`{invalid`))
assert.Error(t, err)
}
func TestMarshalRequestFB_UnknownType(t *testing.T) {
t.Parallel()
_, err := MarshalRequestFB(ToolRequest{ID: "bad", Type: CommandType("bogus")})
assert.Error(t, err)
}
func TestUnmarshalRequestFB_Garbage(t *testing.T) {
t.Parallel()
_, err := UnmarshalRequestFB([]byte{0, 0, 0, 0})
assert.Error(t, err)
}
func TestRequestJSON_Roundtrip(t *testing.T) {
t.Parallel()
orig := NewToolExecRequest("j1", "s", "tc", "shell", `{"cmd":"ls"}`)
data, err := jsonv2.Marshal(orig)
require.NoError(t, err)
@ -217,6 +220,7 @@ func TestRequestJSON_Roundtrip(t *testing.T) {
}
func TestResponseJSON_Roundtrip(t *testing.T) {
t.Parallel()
orig := NewSuccessResponse("j2", "ok", 10)
data, err := jsonv2.Marshal(orig)
require.NoError(t, err)

View file

@ -3,9 +3,10 @@ package dag_test
import (
"context"
"fmt"
jsonv2 "github.com/go-json-experiment/json"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
"github.com/ZanzyTHEbar/dragonscale/pkg/itr/dag"
"github.com/ZanzyTHEbar/dragonscale/pkg/security/securebus"
@ -51,6 +52,7 @@ func (s *staticTool) Execute(_ context.Context, args map[string]interface{}) *to
}
func TestExecutor_LinearDependencyChain(t *testing.T) {
t.Parallel()
toolMap := map[string]tools.Tool{
"step1": &staticTool{name: "step1", result: "r1"},
"step2": &staticTool{name: "step2", result: "r2"},
@ -67,13 +69,14 @@ func TestExecutor_LinearDependencyChain(t *testing.T) {
},
}
result, err := executor.Execute(context.Background(), "test-sess", plan)
result, err := executor.Execute(t.Context(), "test-sess", plan)
require.NoError(t, err)
assert.Contains(t, result.NodeResults["a"], "r1")
assert.Contains(t, result.NodeResults["b"], "r2")
}
func TestExecutor_ParallelNodes(t *testing.T) {
t.Parallel()
toolMap := map[string]tools.Tool{
"alpha": &staticTool{name: "alpha", result: "a-result"},
"beta": &staticTool{name: "beta", result: "b-result"},
@ -90,13 +93,14 @@ func TestExecutor_ParallelNodes(t *testing.T) {
},
}
result, err := executor.Execute(context.Background(), "test-sess", plan)
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"])
}
func TestExecutor_CycleDetection(t *testing.T) {
t.Parallel()
bus := makeBus(t, map[string]tools.Tool{})
defer bus.Close()
@ -109,12 +113,13 @@ func TestExecutor_CycleDetection(t *testing.T) {
},
}
_, err := executor.Execute(context.Background(), "test-sess", plan)
_, err := executor.Execute(t.Context(), "test-sess", plan)
require.Error(t, err)
assert.Contains(t, err.Error(), "cycle")
}
func TestExecutor_WithJoiner(t *testing.T) {
t.Parallel()
toolMap := map[string]tools.Tool{
"tool1": &staticTool{name: "tool1", result: "data-A"},
"tool2": &staticTool{name: "tool2", result: "data-B"},
@ -140,24 +145,26 @@ func TestExecutor_WithJoiner(t *testing.T) {
JoinerQuery: "Combine the results into a summary",
}
result, err := executor.Execute(context.Background(), "test-sess", plan)
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)
}
func TestExecutor_EmptyPlan(t *testing.T) {
t.Parallel()
bus := makeBus(t, map[string]tools.Tool{})
defer bus.Close()
executor := dag.NewExecutor(bus, nil)
result, err := executor.Execute(context.Background(), "test-sess", &itr.DAGPlan{})
result, err := executor.Execute(t.Context(), "test-sess", &itr.DAGPlan{})
require.NoError(t, err)
assert.Empty(t, result.NodeResults)
}
func TestResolver_NodeRefSubstitution(t *testing.T) {
t.Parallel()
argsJSON := `{"query": "search for #nodeprev results"}`
toolMap := map[string]tools.Tool{
"search": &staticTool{name: "search", result: "found"},
@ -175,31 +182,35 @@ func TestResolver_NodeRefSubstitution(t *testing.T) {
},
}
result, err := executor.Execute(context.Background(), "test-sess", plan)
result, err := executor.Execute(t.Context(), "test-sess", plan)
require.NoError(t, err)
assert.Contains(t, result.NodeResults["prev"], "previous-output")
assert.Contains(t, result.NodeResults["search"], "found")
}
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)
}
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)
}
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)
}
func TestPlanner_ValidatePlan(t *testing.T) {
t.Parallel()
tests := []struct {
name string
plan string
@ -238,7 +249,7 @@ func TestPlanner_ValidatePlan(t *testing.T) {
return tt.plan, 10, nil
}
planner := dag.NewPlanner(mockModel, nil, dag.DefaultPlannerConfig())
_, _, planErr := planner.Plan(context.Background(), "test query", nil)
_, _, planErr := planner.Plan(t.Context(), "test query", nil)
if tt.wantErr {
assert.Error(t, planErr)
} else {

View file

@ -2,35 +2,41 @@ package dag
import (
"context"
jsonv2 "github.com/go-json-experiment/json"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestExtractJSON_PlainJSON(t *testing.T) {
t.Parallel()
input := `{"nodes": [{"id": "n1"}]}`
assert.Equal(t, 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))
}
func TestExtractJSON_GenericFenced(t *testing.T) {
t.Parallel()
input := "```\n{\"nodes\": []}\n```"
assert.Equal(t, `{"nodes": []}`, extractJSON(input))
}
func TestExtractJSON_LeadingText(t *testing.T) {
t.Parallel()
input := "The plan is: {\"nodes\":[]}"
assert.Equal(t, `{"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"))
@ -39,6 +45,7 @@ func TestFindIndex(t *testing.T) {
}
func TestValidatePlan_Valid(t *testing.T) {
t.Parallel()
plan := &itr.DAGPlan{
Nodes: []itr.DAGNode{
{ID: "a", Type: itr.CmdToolExec},
@ -49,6 +56,7 @@ func TestValidatePlan_Valid(t *testing.T) {
}
func TestValidatePlan_Empty(t *testing.T) {
t.Parallel()
plan := &itr.DAGPlan{Nodes: nil}
err := validatePlan(plan)
require.Error(t, err)
@ -56,6 +64,7 @@ func TestValidatePlan_Empty(t *testing.T) {
}
func TestValidatePlan_DuplicateID(t *testing.T) {
t.Parallel()
plan := &itr.DAGPlan{
Nodes: []itr.DAGNode{
{ID: "x", Type: itr.CmdToolExec},
@ -68,6 +77,7 @@ func TestValidatePlan_DuplicateID(t *testing.T) {
}
func TestValidatePlan_EmptyID(t *testing.T) {
t.Parallel()
plan := &itr.DAGPlan{
Nodes: []itr.DAGNode{{ID: "", Type: itr.CmdToolExec}},
}
@ -77,6 +87,7 @@ func TestValidatePlan_EmptyID(t *testing.T) {
}
func TestValidatePlan_UnknownDependency(t *testing.T) {
t.Parallel()
plan := &itr.DAGPlan{
Nodes: []itr.DAGNode{
{ID: "a", Type: itr.CmdToolExec, DependsOn: []string{"missing"}},
@ -88,6 +99,7 @@ func TestValidatePlan_UnknownDependency(t *testing.T) {
}
func TestValidatePlan_SelfDependency(t *testing.T) {
t.Parallel()
plan := &itr.DAGPlan{
Nodes: []itr.DAGNode{
{ID: "a", Type: itr.CmdToolExec, DependsOn: []string{"a"}},
@ -99,6 +111,7 @@ func TestValidatePlan_SelfDependency(t *testing.T) {
}
func TestValidatePlan_CyclicDependency(t *testing.T) {
t.Parallel()
plan := &itr.DAGPlan{
Nodes: []itr.DAGNode{
{ID: "a", Type: itr.CmdToolExec, DependsOn: []string{"b"}},
@ -111,6 +124,7 @@ func TestValidatePlan_CyclicDependency(t *testing.T) {
}
func TestParsePlanResponse_ValidJSON(t *testing.T) {
t.Parallel()
input := `{
"nodes": [
{"id": "n1", "type": "tool_exec", "payload": {"tool_name": "read_file", "args_json": "{\"path\":\"/tmp/a\"}"}}
@ -131,6 +145,7 @@ func TestParsePlanResponse_ValidJSON(t *testing.T) {
}
func TestParsePlanResponse_WithMarkdownFence(t *testing.T) {
t.Parallel()
input := "```json\n" + `{"nodes": [{"id": "x", "type": "tool_search", "payload": {"query": "files"}}]}` + "\n```"
plan, err := parsePlanResponse(input)
@ -143,11 +158,13 @@ func TestParsePlanResponse_WithMarkdownFence(t *testing.T) {
}
func TestParsePlanResponse_InvalidJSON(t *testing.T) {
t.Parallel()
_, err := parsePlanResponse("not json at all")
assert.Error(t, err)
}
func TestPlannerPlanE2E(t *testing.T) {
t.Parallel()
mockLLM := func(ctx context.Context, systemPrompt, userQuery string) (string, uint32, error) {
plan := itr.DAGPlan{
Nodes: []itr.DAGNode{
@ -164,7 +181,7 @@ func TestPlannerPlanE2E(t *testing.T) {
}
planner := NewPlanner(mockLLM, nil, DefaultPlannerConfig())
plan, tokens, err := planner.Plan(context.Background(), "search and read", nil)
plan, tokens, err := planner.Plan(t.Context(), "search and read", nil)
require.NoError(t, err)
assert.Equal(t, uint32(100), tokens)
require.Len(t, plan.Nodes, 2)
@ -173,12 +190,13 @@ func TestPlannerPlanE2E(t *testing.T) {
}
func TestPlannerPlanLLMError(t *testing.T) {
t.Parallel()
mockLLM := func(ctx context.Context, systemPrompt, userQuery string) (string, uint32, error) {
return "", 50, assert.AnError
}
planner := NewPlanner(mockLLM, nil, DefaultPlannerConfig())
_, tokens, err := planner.Plan(context.Background(), "anything", nil)
_, tokens, err := planner.Plan(t.Context(), "anything", nil)
assert.Error(t, err)
assert.Equal(t, uint32(50), tokens)
}

View file

@ -7,6 +7,7 @@ import (
)
func TestNeedsReplan(t *testing.T) {
t.Parallel()
assert.True(t, needsReplan("The task is incomplete [NEEDS_MORE_STEPS]"))
assert.True(t, needsReplan("[NEEDS_MORE_STEPS]"))
assert.False(t, needsReplan("Task complete. Here is the answer."))
@ -14,5 +15,6 @@ func TestNeedsReplan(t *testing.T) {
}
func TestReplanSentinelIsConsistent(t *testing.T) {
t.Parallel()
assert.Equal(t, "[NEEDS_MORE_STEPS]", replanSentinel)
}

View file

@ -8,6 +8,7 @@ import (
)
func TestTopologicalOrderLinear(t *testing.T) {
t.Parallel()
states := map[string]*nodeState{
"a": newNodeState("a", nil),
"b": newNodeState("b", []string{"a"}),
@ -23,6 +24,7 @@ func TestTopologicalOrderLinear(t *testing.T) {
}
func TestTopologicalOrderParallel(t *testing.T) {
t.Parallel()
states := map[string]*nodeState{
"a": newNodeState("a", nil),
"b": newNodeState("b", nil),
@ -40,6 +42,7 @@ func TestTopologicalOrderParallel(t *testing.T) {
}
func TestTopologicalOrderCycleDetection(t *testing.T) {
t.Parallel()
states := map[string]*nodeState{
"a": newNodeState("a", []string{"c"}),
"b": newNodeState("b", []string{"a"}),
@ -52,6 +55,7 @@ func TestTopologicalOrderCycleDetection(t *testing.T) {
}
func TestTopologicalOrderSingleNode(t *testing.T) {
t.Parallel()
states := map[string]*nodeState{
"only": newNodeState("only", nil),
}
@ -63,6 +67,7 @@ func TestTopologicalOrderSingleNode(t *testing.T) {
}
func TestResolveRefs(t *testing.T) {
t.Parallel()
states := map[string]*nodeState{
"search": newNodeState("search", nil),
}
@ -75,6 +80,7 @@ func TestResolveRefs(t *testing.T) {
}
func TestResolveRefsNoMatch(t *testing.T) {
t.Parallel()
states := map[string]*nodeState{}
input := `{"path":"#nodemissing"}`
result := resolveRefs(input, states)
@ -82,6 +88,7 @@ func TestResolveRefsNoMatch(t *testing.T) {
}
func TestResolveToolExecArgsNoRefs(t *testing.T) {
t.Parallel()
states := map[string]*nodeState{}
input := `{"path":"/tmp/plain.txt"}`
result := resolveToolExecArgs(input, states)
@ -89,6 +96,7 @@ func TestResolveToolExecArgsNoRefs(t *testing.T) {
}
func TestEscapeForJSON(t *testing.T) {
t.Parallel()
tests := []struct {
input string
expected string
@ -106,6 +114,7 @@ func TestEscapeForJSON(t *testing.T) {
}
func TestNodeStateSetAndGetResult(t *testing.T) {
t.Parallel()
ns := newNodeState("test", nil)
go func() {

View file

@ -8,6 +8,7 @@ import (
)
func TestRouteExplicitModes(t *testing.T) {
t.Parallel()
cfg := DefaultRouterConfig()
assert.Equal(t, ModeReAct, Route(ModeReAct, "anything", cfg))
@ -15,17 +16,20 @@ func TestRouteExplicitModes(t *testing.T) {
}
func TestRouteAutoSimpleQuery(t *testing.T) {
t.Parallel()
cfg := DefaultRouterConfig()
assert.Equal(t, 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))
}
func TestRouteAutoParallelKeywords(t *testing.T) {
t.Parallel()
cfg := DefaultRouterConfig()
keywords := []string{
@ -42,12 +46,14 @@ func TestRouteAutoParallelKeywords(t *testing.T) {
}
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))
}
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())
@ -55,6 +61,7 @@ func TestToolLoopModeString(t *testing.T) {
}
func TestClassifyQueryDefault(t *testing.T) {
t.Parallel()
cfg := DefaultRouterConfig()
assert.Equal(t, ModeReAct, classifyQuery("hello", cfg))
}

View file

@ -5,165 +5,63 @@ import (
"time"
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFBCodec_RequestRoundtrip_Peek(t *testing.T) {
orig := itr.NewPeekRequest("req-1", "sess-A", 2, 1024, 4096)
func TestFBCodec_RequestRoundtrip(t *testing.T) {
t.Parallel()
data, err := itr.MarshalRequestFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
assert.Equal(t, orig.ID, got.ID)
assert.Equal(t, orig.Type, got.Type)
assert.Equal(t, orig.Depth, got.Depth)
assert.Equal(t, orig.SessionKey, got.SessionKey)
origP, ok := orig.Payload.(itr.Peek)
require.True(t, ok, "orig payload should be Peek")
gotP, ok := got.Payload.(itr.Peek)
require.True(t, ok, "got payload should be Peek")
assert.Equal(t, origP, gotP)
}
func TestFBCodec_RequestRoundtrip_Grep(t *testing.T) {
orig := itr.NewGrepRequest("req-2", "sess-B", 1, "error.*fatal", 25, true)
data, err := itr.MarshalRequestFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
assert.Equal(t, orig.Type, got.Type)
p, ok := got.Payload.(itr.Grep)
require.True(t, ok, "payload should be Grep")
assert.Equal(t, "error.*fatal", p.Pattern)
assert.Equal(t, uint32(25), p.MaxMatches)
assert.True(t, p.CaseInsensitive)
}
func TestFBCodec_RequestRoundtrip_Partition(t *testing.T) {
orig := itr.NewPartitionRequest("req-3", "sess-C", 0, 8, "semantic", 100, true)
data, err := itr.MarshalRequestFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
p, ok := got.Payload.(itr.Partition)
require.True(t, ok, "payload should be Partition")
assert.Equal(t, uint32(8), p.K)
assert.Equal(t, "semantic", p.Method)
assert.Equal(t, uint32(100), p.Overlap)
assert.True(t, p.Semantic)
}
func TestFBCodec_RequestRoundtrip_Recurse(t *testing.T) {
orig := itr.NewRecurseRequest("req-4", "sess-D", 3, "summarize this", "ctx-key-7", 5)
data, err := itr.MarshalRequestFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
p, ok := got.Payload.(itr.Recurse)
require.True(t, ok, "payload should be Recurse")
assert.Equal(t, "summarize this", p.SubQuery)
assert.Equal(t, "ctx-key-7", p.ContextKey)
assert.Equal(t, uint8(5), p.DepthHint)
}
func TestFBCodec_RequestRoundtrip_ToolExec(t *testing.T) {
orig := itr.NewToolExecRequest("req-5", "sess-E", "tc-1", "read_file", `{"path":"/etc/hosts"}`)
data, err := itr.MarshalRequestFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
p, ok := got.Payload.(itr.ToolExec)
require.True(t, ok, "payload should be ToolExec")
assert.Equal(t, "read_file", p.ToolName)
assert.Equal(t, `{"path":"/etc/hosts"}`, p.ArgsJSON)
assert.Equal(t, "tc-1", got.ToolCallID)
}
func TestFBCodec_RequestRoundtrip_ExecWasm(t *testing.T) {
orig := itr.ToolRequest{
tests := []struct {
name string
req itr.ToolRequest
}{
{
name: "peek",
req: itr.NewPeekRequest("req-1", "sess-A", 2, 1024, 4096),
},
{
name: "grep",
req: itr.NewGrepRequest("req-2", "sess-B", 1, "error.*fatal", 25, true),
},
{
name: "partition",
req: itr.NewPartitionRequest("req-3", "sess-C", 0, 8, "semantic", 100, true),
},
{
name: "recurse",
req: itr.NewRecurseRequest("req-4", "sess-D", 3, "summarize this", "ctx-key-7", 5),
},
{
name: "tool exec",
req: itr.NewToolExecRequest("req-5", "sess-E", "tc-1", "read_file", `{"path":"/etc/hosts"}`),
},
{
name: "exec wasm",
req: itr.ToolRequest{
ID: "req-6",
Type: itr.CmdExecWasm,
Payload: itr.ExecWasm{ModuleKey: "mod-1", Entry: "main", InputJSON: `{"x":1}`},
Timestamp: time.Now().UnixNano(),
SessionKey: "sess-F",
}
data, err := itr.MarshalRequestFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
p, ok := got.Payload.(itr.ExecWasm)
require.True(t, ok, "payload should be ExecWasm")
assert.Equal(t, "mod-1", p.ModuleKey)
assert.Equal(t, "main", p.Entry)
assert.Equal(t, `{"x":1}`, p.InputJSON)
}
func TestFBCodec_RequestRoundtrip_Final(t *testing.T) {
orig := itr.NewFinalRequest("req-7", "sess-G", 2, "The answer is 42", "ans_var")
data, err := itr.MarshalRequestFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
p, ok := got.Payload.(itr.Final)
require.True(t, ok, "payload should be Final")
assert.Equal(t, "The answer is 42", p.Answer)
assert.Equal(t, "ans_var", p.VarName)
}
func TestFBCodec_RequestRoundtrip_ToolSearch(t *testing.T) {
orig := itr.NewToolSearchRequest("req-8", "sess-H", "find file tools", 5)
data, err := itr.MarshalRequestFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
p, ok := got.Payload.(itr.ToolSearch)
require.True(t, ok, "payload should be ToolSearch")
assert.Equal(t, "find file tools", p.Query)
assert.Equal(t, uint8(5), p.MaxResults)
}
func TestFBCodec_RequestRoundtrip_CodeExec(t *testing.T) {
orig := itr.NewCodeExecRequest("req-9", "sess-I", "console.log('hi')", "javascript")
data, err := itr.MarshalRequestFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
p, ok := got.Payload.(itr.CodeExec)
require.True(t, ok, "payload should be CodeExec")
assert.Equal(t, "console.log('hi')", p.Code)
assert.Equal(t, "javascript", p.Language)
}
func TestFBCodec_RequestRoundtrip_DAGPlan(t *testing.T) {
plan := itr.DAGPlan{
},
},
{
name: "final",
req: itr.NewFinalRequest("req-7", "sess-G", 2, "The answer is 42", "ans_var"),
},
{
name: "tool search",
req: itr.NewToolSearchRequest("req-8", "sess-H", "find file tools", 5),
},
{
name: "code exec",
req: itr.NewCodeExecRequest("req-9", "sess-I", "console.log('hi')", "javascript"),
},
{
name: "dag plan",
req: itr.NewDAGPlanRequest("req-10", "sess-J", itr.DAGPlan{
Nodes: []itr.DAGNode{
{
ID: "a",
@ -180,106 +78,75 @@ func TestFBCodec_RequestRoundtrip_DAGPlan(t *testing.T) {
MaxParallel: 4,
TokenBudget: 10000,
JoinerQuery: "Summarize the results",
}
orig := itr.NewDAGPlanRequest("req-10", "sess-J", plan)
data, err := itr.MarshalRequestFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
assert.Equal(t, itr.CmdDAGPlan, got.Type)
gotPlan, ok := got.Payload.(itr.DAGPlan)
require.True(t, ok)
assert.Equal(t, uint8(4), gotPlan.MaxParallel)
assert.Equal(t, uint32(10000), gotPlan.TokenBudget)
assert.Equal(t, "Summarize the results", gotPlan.JoinerQuery)
require.Len(t, gotPlan.Nodes, 2)
nodeA := gotPlan.Nodes[0]
assert.Equal(t, "a", nodeA.ID)
assert.Equal(t, itr.CmdToolExec, nodeA.Type)
te, ok := nodeA.Payload.(itr.ToolExec)
require.True(t, ok)
assert.Equal(t, "read_file", te.ToolName)
assert.Equal(t, `{"path":"x.txt"}`, te.ArgsJSON)
nodeB := gotPlan.Nodes[1]
assert.Equal(t, "b", nodeB.ID)
assert.Equal(t, itr.CmdToolSearch, nodeB.Type)
ts, ok := nodeB.Payload.(itr.ToolSearch)
require.True(t, ok)
assert.Equal(t, "search tools", ts.Query)
assert.Equal(t, uint8(3), ts.MaxResults)
assert.Equal(t, []string{"a"}, nodeB.DependsOn)
}
func TestFBCodec_ResponseRoundtrip_Success(t *testing.T) {
orig := itr.NewSuccessResponse("resp-1", "file contents here", 150)
data, err := itr.MarshalResponseFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalResponseFB(data)
require.NoError(t, err)
assert.Equal(t, "resp-1", got.ID)
assert.Equal(t, "file contents here", got.Result)
assert.False(t, got.IsError)
assert.Equal(t, uint32(150), got.CostTokens)
}
func TestFBCodec_ResponseRoundtrip_Error(t *testing.T) {
orig := itr.NewErrorResponse("resp-2", "tool not found")
data, err := itr.MarshalResponseFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalResponseFB(data)
require.NoError(t, err)
assert.True(t, got.IsError)
assert.Equal(t, "tool not found", got.Result)
}
func TestFBCodec_ResponseRoundtrip_Leak(t *testing.T) {
orig := itr.NewLeakResponse("resp-3", "redacted output", []string{"api_key", "password"})
data, err := itr.MarshalResponseFB(orig)
require.NoError(t, err)
got, err := itr.UnmarshalResponseFB(data)
require.NoError(t, err)
assert.True(t, got.LeakDetected)
assert.Equal(t, []string{"api_key", "password"}, got.RedactedKeys)
}
func TestFBCodec_TimestampPreserved(t *testing.T) {
ts := time.Now().UnixNano()
orig := itr.ToolRequest{
}),
},
{
name: "timestamp preserved",
req: itr.ToolRequest{
ID: "ts-test",
Type: itr.CmdPeek,
Payload: itr.Peek{Start: 0, Length: 10},
Timestamp: ts,
Timestamp: time.Now().UnixNano(),
},
},
}
data, err := itr.MarshalRequestFB(orig)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
data, err := itr.MarshalRequestFB(tt.req)
require.NoError(t, err)
got, err := itr.UnmarshalRequestFB(data)
require.NoError(t, err)
assert.Equal(t, ts, got.Timestamp)
assert.Empty(t, cmp.Diff(tt.req, got))
})
}
}
func TestFBCodec_ResponseRoundtrip(t *testing.T) {
t.Parallel()
tests := []struct {
name string
res itr.ToolResponse
}{
{
name: "success",
res: itr.NewSuccessResponse("resp-1", "file contents here", 150),
},
{
name: "error",
res: itr.NewErrorResponse("resp-2", "tool not found"),
},
{
name: "leak",
res: itr.NewLeakResponse("resp-3", "redacted output", []string{"api_key", "password"}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
data, err := itr.MarshalResponseFB(tt.res)
require.NoError(t, err)
got, err := itr.UnmarshalResponseFB(data)
require.NoError(t, err)
assert.Empty(t, cmp.Diff(tt.res, got))
})
}
}
func TestFBCodec_UnknownCommandType(t *testing.T) {
t.Parallel()
_, err := itr.MarshalRequestFB(itr.ToolRequest{
ID: "bad",
Type: itr.CommandType("nonexistent"),
})
assert.Error(t, err)
require.Error(t, err)
}

View file

@ -1,7 +1,6 @@
package wasm
import (
"context"
"testing"
"time"
@ -47,14 +46,16 @@ var minimalWASM = []byte{
}
func TestNewRuntime(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
require.NoError(t, err)
defer rt.Close(ctx)
}
func TestExecuteMinimalModule(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
require.NoError(t, err)
defer rt.Close(ctx)
@ -67,7 +68,8 @@ func TestExecuteMinimalModule(t *testing.T) {
}
func TestExecuteTimeout(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
cfg := DefaultRuntimeConfig()
cfg.ExecTimeout = 1 * time.Millisecond
rt, err := NewRuntime(ctx, cfg)
@ -83,7 +85,8 @@ func TestExecuteTimeout(t *testing.T) {
}
func TestExecuteInvalidWASM(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
require.NoError(t, err)
defer rt.Close(ctx)
@ -94,7 +97,8 @@ func TestExecuteInvalidWASM(t *testing.T) {
}
func TestRuntimeCloseIdempotent(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
require.NoError(t, err)
@ -103,7 +107,8 @@ func TestRuntimeCloseIdempotent(t *testing.T) {
}
func TestExecuteAfterClose(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
require.NoError(t, err)
rt.Close(ctx)
@ -114,6 +119,7 @@ func TestExecuteAfterClose(t *testing.T) {
}
func TestLimitedBuffer(t *testing.T) {
t.Parallel()
lb := &limitedBuffer{max: 5}
n, err := lb.Write([]byte("hello world"))
assert.NoError(t, err)
@ -122,6 +128,7 @@ func TestLimitedBuffer(t *testing.T) {
}
func TestLimitedBufferExactFit(t *testing.T) {
t.Parallel()
lb := &limitedBuffer{max: 5}
n, err := lb.Write([]byte("hello"))
assert.NoError(t, err)

View file

@ -10,9 +10,10 @@ import (
)
func TestTransportNonCodeExecForwarded(t *testing.T) {
rt, err := NewRuntime(context.Background(), DefaultRuntimeConfig())
t.Parallel()
rt, err := NewRuntime(t.Context(), DefaultRuntimeConfig())
require.NoError(t, err)
defer rt.Close(context.Background())
defer rt.Close(t.Context())
forwarded := false
transport := NewTransport(rt, func(ctx context.Context, req itr.ToolRequest) (itr.ToolResponse, error) {
@ -21,7 +22,7 @@ func TestTransportNonCodeExecForwarded(t *testing.T) {
})
req := itr.NewToolExecRequest("id-1", "sess", "tc", "read_file", `{"path":"/tmp"}`)
resp, err := transport.Send(context.Background(), req)
resp, err := transport.Send(t.Context(), req)
require.NoError(t, err)
assert.True(t, forwarded)
assert.Equal(t, "forwarded", resp.Result)
@ -29,35 +30,38 @@ func TestTransportNonCodeExecForwarded(t *testing.T) {
}
func TestTransportNonCodeExecNoFallback(t *testing.T) {
rt, err := NewRuntime(context.Background(), DefaultRuntimeConfig())
t.Parallel()
rt, err := NewRuntime(t.Context(), DefaultRuntimeConfig())
require.NoError(t, err)
defer rt.Close(context.Background())
defer rt.Close(t.Context())
transport := NewTransport(rt, nil)
req := itr.NewToolExecRequest("id-1", "sess", "tc", "read_file", `{"path":"/tmp"}`)
resp, err := transport.Send(context.Background(), req)
resp, err := transport.Send(t.Context(), req)
require.NoError(t, err)
assert.True(t, resp.IsError)
assert.Contains(t, resp.Result, "unsupported command type")
}
func TestTransportCodeExecEmptyCode(t *testing.T) {
rt, err := NewRuntime(context.Background(), DefaultRuntimeConfig())
t.Parallel()
rt, err := NewRuntime(t.Context(), DefaultRuntimeConfig())
require.NoError(t, err)
defer rt.Close(context.Background())
defer rt.Close(t.Context())
transport := NewTransport(rt, nil)
req := itr.NewCodeExecRequest("id-1", "sess", "", "javascript")
resp, err := transport.Send(context.Background(), req)
resp, err := transport.Send(t.Context(), req)
require.NoError(t, err)
assert.True(t, resp.IsError)
assert.Contains(t, resp.Result, "empty code")
}
func TestTransportClose(t *testing.T) {
rt, err := NewRuntime(context.Background(), DefaultRuntimeConfig())
t.Parallel()
rt, err := NewRuntime(t.Context(), DefaultRuntimeConfig())
require.NoError(t, err)
transport := NewTransport(rt, nil)

View file

@ -5,6 +5,7 @@ import (
)
func TestLogLevelFiltering(t *testing.T) {
t.Parallel()
initialLevel := GetLevel()
defer SetLevel(initialLevel)
@ -45,6 +46,7 @@ func TestLogLevelFiltering(t *testing.T) {
}
func TestLoggerWithComponent(t *testing.T) {
t.Parallel()
initialLevel := GetLevel()
defer SetLevel(initialLevel)
@ -81,6 +83,7 @@ func TestLoggerWithComponent(t *testing.T) {
}
func TestLogLevels(t *testing.T) {
t.Parallel()
tests := []struct {
name string
level LogLevel
@ -103,6 +106,7 @@ func TestLogLevels(t *testing.T) {
}
func TestSetGetLevel(t *testing.T) {
t.Parallel()
initialLevel := GetLevel()
defer SetLevel(initialLevel)
@ -117,6 +121,7 @@ func TestSetGetLevel(t *testing.T) {
}
func TestLoggerHelperFunctions(t *testing.T) {
t.Parallel()
initialLevel := GetLevel()
defer SetLevel(initialLevel)

View file

@ -1,7 +1,6 @@
package dag_test
import (
"context"
"fmt"
"testing"
"time"
@ -18,7 +17,8 @@ import (
)
func TestBackfillMissingSessionDAGs_CreatesSnapshotsAndPersistsStatus(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
d, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
require.NoError(t, d.Init(ctx))

View file

@ -4,6 +4,8 @@ import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -24,6 +26,7 @@ func makeMessages(n int) []Message {
}
func TestCompressor_EmptyInput(t *testing.T) {
t.Parallel()
c := NewCompressor(DefaultCompressorConfig())
d := c.Compress(nil)
assert.Empty(t, d.Nodes)
@ -31,6 +34,7 @@ func TestCompressor_EmptyInput(t *testing.T) {
}
func TestCompressor_SmallInput(t *testing.T) {
t.Parallel()
c := NewCompressor(DefaultCompressorConfig())
msgs := []Message{
{Role: "user", Content: "Hello, how are you?"},
@ -48,6 +52,7 @@ func TestCompressor_SmallInput(t *testing.T) {
}
func TestCompressor_ChunkSplitting(t *testing.T) {
t.Parallel()
cfg := DefaultCompressorConfig()
cfg.ChunkSize = 4
c := NewCompressor(cfg)
@ -67,6 +72,7 @@ func TestCompressor_ChunkSplitting(t *testing.T) {
}
func TestCompressor_SectionBuilding(t *testing.T) {
t.Parallel()
cfg := DefaultCompressorConfig()
cfg.ChunkSize = 4
cfg.SectionSize = 2
@ -89,6 +95,7 @@ func TestCompressor_SectionBuilding(t *testing.T) {
}
func TestCompressor_SessionSummary(t *testing.T) {
t.Parallel()
cfg := DefaultCompressorConfig()
cfg.ChunkSize = 4
cfg.SectionSize = 2
@ -109,6 +116,7 @@ func TestCompressor_SessionSummary(t *testing.T) {
}
func TestExtractSentences(t *testing.T) {
t.Parallel()
tests := []struct {
name string
text string
@ -133,6 +141,7 @@ func TestExtractSentences(t *testing.T) {
}
func TestExtractSentences_Truncation(t *testing.T) {
t.Parallel()
long := strings.Repeat("This is a very long sentence with many words. ", 20)
result := extractSentences(long, 5)
assert.LessOrEqual(t, len([]rune(result)), 210)
@ -140,6 +149,7 @@ func TestExtractSentences_Truncation(t *testing.T) {
}
func TestNode_FormatForPrompt(t *testing.T) {
t.Parallel()
n := &Node{
ID: "chunk-1",
Level: LevelChunk,
@ -153,6 +163,7 @@ func TestNode_FormatForPrompt(t *testing.T) {
}
func TestDAG_FormatLevel(t *testing.T) {
t.Parallel()
cfg := DefaultCompressorConfig()
cfg.ChunkSize = 4
c := NewCompressor(cfg)
@ -167,6 +178,7 @@ func TestDAG_FormatLevel(t *testing.T) {
}
func TestDAG_TotalTokens(t *testing.T) {
t.Parallel()
cfg := DefaultCompressorConfig()
cfg.ChunkSize = 4
c := NewCompressor(cfg)
@ -179,25 +191,30 @@ func TestDAG_TotalTokens(t *testing.T) {
}
func TestComputeBudget(t *testing.T) {
t.Parallel()
cfg := DefaultBudgetConfig()
b := ComputeBudget(100000, cfg)
assert.Equal(t, 100000, b.Total)
assert.Equal(t, 20000, b.SystemPrompt)
assert.Equal(t, 10000, b.Observations)
assert.Equal(t, 5000, b.Knowledge)
assert.Equal(t, 25000, b.DAGSummaries)
assert.Equal(t, 30000, b.RawTail)
assert.Equal(t, 10000, b.ToolResults)
assert.Empty(t, cmp.Diff(Budget{
Total: 100000,
SystemPrompt: 20000,
Observations: 10000,
Knowledge: 5000,
DAGSummaries: 25000,
RawTail: 30000,
ToolResults: 10000,
}, b))
}
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))
}
func TestSelectDAGLevel(t *testing.T) {
t.Parallel()
cfg := DefaultCompressorConfig()
cfg.ChunkSize = 4
cfg.SectionSize = 2
@ -219,6 +236,7 @@ func TestSelectDAGLevel(t *testing.T) {
}
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
@ -226,6 +244,7 @@ func TestTailMessageCount(t *testing.T) {
}
func TestRenderDAGForBudget(t *testing.T) {
t.Parallel()
assert.Empty(t, RenderDAGForBudget(nil, 1000))
cfg := DefaultCompressorConfig()

View file

@ -1,7 +1,6 @@
package delegate
import (
"context"
"os"
"path/filepath"
"testing"
@ -12,6 +11,7 @@ import (
)
func TestNewFromConfig_LocalOnly_DefaultPath(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
defaultPath := filepath.Join(tmpDir, "test.db")
@ -23,7 +23,7 @@ func TestNewFromConfig_LocalOnly_DefaultPath(t *testing.T) {
}
defer d.Close()
if err := d.Init(context.Background()); err != nil {
if err := d.Init(t.Context()); err != nil {
t.Fatalf("Init: %v", err)
}
@ -32,13 +32,14 @@ func TestNewFromConfig_LocalOnly_DefaultPath(t *testing.T) {
}
// Verify it's functional
err = d.UpsertWorkingContext(context.Background(), "agent", "sess", "test content")
err = d.UpsertWorkingContext(t.Context(), "agent", "sess", "test content")
if err != nil {
t.Fatalf("UpsertWorkingContext: %v", err)
}
}
func TestNewFromConfig_LocalOnly_CustomPath(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
customPath := filepath.Join(tmpDir, "custom.db")
@ -52,7 +53,7 @@ func TestNewFromConfig_LocalOnly_CustomPath(t *testing.T) {
}
defer d.Close()
if err := d.Init(context.Background()); err != nil {
if err := d.Init(t.Context()); err != nil {
t.Fatalf("Init: %v", err)
}
@ -63,6 +64,7 @@ func TestNewFromConfig_LocalOnly_CustomPath(t *testing.T) {
}
func TestNewFromConfig_CustomDims(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "test.db")
@ -82,6 +84,7 @@ func TestNewFromConfig_CustomDims(t *testing.T) {
}
func TestNewFromConfig_DefaultDims(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "test.db")
@ -99,6 +102,7 @@ func TestNewFromConfig_DefaultDims(t *testing.T) {
}
func TestNewFromConfig_ReplicaFallback(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "test.db")
@ -121,12 +125,13 @@ func TestNewFromConfig_ReplicaFallback(t *testing.T) {
}
// Should still be functional in local mode
if err := d.Init(context.Background()); err != nil {
if err := d.Init(t.Context()); err != nil {
t.Fatalf("Init: %v", err)
}
}
func TestNewFromConfig_LocalFullRoundTrip(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "roundtrip.db")
@ -140,11 +145,11 @@ func TestNewFromConfig_LocalFullRoundTrip(t *testing.T) {
}
defer d.Close()
if err := d.Init(context.Background()); err != nil {
if err := d.Init(t.Context()); err != nil {
t.Fatalf("Init: %v", err)
}
ctx := context.Background()
ctx := t.Context()
// Working context round-trip
if err := d.UpsertWorkingContext(ctx, "a1", "s1", "hello"); err != nil {
@ -180,6 +185,7 @@ func TestNewFromConfig_LocalFullRoundTrip(t *testing.T) {
}
func TestSyncConfig_Defaults(t *testing.T) {
t.Parallel()
cfg := config.DefaultConfig()
// Memory is always enabled -- no Enabled field to check.
if cfg.Memory.EmbeddingDims != 768 {

View file

@ -25,6 +25,7 @@ func makeAuditEntry(agentID, sessionKey, action, target string) *memory.AuditEnt
}
func TestLibSQLDelegate_InsertAuditEntry(t *testing.T) {
t.Parallel()
tests := []struct {
name string
entry *memory.AuditEntry
@ -54,7 +55,7 @@ func TestLibSQLDelegate_InsertAuditEntry(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
require.NoError(t, d.InsertAuditEntry(ctx, tt.entry))
count, err := d.CountAuditEntries(ctx, tt.entry.AgentID)
@ -65,6 +66,7 @@ func TestLibSQLDelegate_InsertAuditEntry(t *testing.T) {
}
func TestLibSQLDelegate_ListAuditEntries(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -114,7 +116,7 @@ func TestLibSQLDelegate_ListAuditEntries(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}
@ -126,6 +128,7 @@ func TestLibSQLDelegate_ListAuditEntries(t *testing.T) {
}
func TestLibSQLDelegate_ListAuditEntriesByAction(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -160,7 +163,7 @@ func TestLibSQLDelegate_ListAuditEntriesByAction(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}
@ -177,6 +180,7 @@ func TestLibSQLDelegate_ListAuditEntriesByAction(t *testing.T) {
}
func TestLibSQLDelegate_ListAuditEntriesBySession(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -209,7 +213,7 @@ func TestLibSQLDelegate_ListAuditEntriesBySession(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}
@ -221,8 +225,9 @@ func TestLibSQLDelegate_ListAuditEntriesBySession(t *testing.T) {
}
func TestLibSQLDelegate_PruneOldAuditEntries(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
require.NoError(t, d.InsertAuditEntry(ctx, makeAuditEntry("a1", "s1", "tool_call", "t1")))
require.NoError(t, d.InsertAuditEntry(ctx, makeAuditEntry("a1", "s1", "tool_call", "t2")))
@ -240,6 +245,7 @@ func TestLibSQLDelegate_PruneOldAuditEntries(t *testing.T) {
}
func TestLibSQLDelegate_CountAuditEntriesByAction(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -269,7 +275,7 @@ func TestLibSQLDelegate_CountAuditEntriesByAction(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}

View file

@ -1,7 +1,6 @@
package delegate
import (
"context"
"testing"
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
@ -10,7 +9,7 @@ import (
func BenchmarkListRecallItems(b *testing.B) {
d := newBenchDelegate(b)
ctx := context.Background()
ctx := b.Context()
agent := "bench-agent"
session := "bench-sess"
@ -36,7 +35,7 @@ func BenchmarkListRecallItems(b *testing.B) {
func BenchmarkGetWorkingContext(b *testing.B) {
d := newBenchDelegate(b)
ctx := context.Background()
ctx := b.Context()
agent := "bench-agent"
session := "bench-sess"
@ -51,7 +50,7 @@ func BenchmarkGetWorkingContext(b *testing.B) {
func BenchmarkUpsertKV(b *testing.B) {
d := newBenchDelegate(b)
ctx := context.Background()
ctx := b.Context()
b.ReportAllocs()
b.ResetTimer()
@ -62,7 +61,7 @@ func BenchmarkUpsertKV(b *testing.B) {
func BenchmarkGetKV(b *testing.B) {
d := newBenchDelegate(b)
ctx := context.Background()
ctx := b.Context()
_ = d.UpsertKV(ctx, "bench-agent", "bench-key", "bench-value")
b.ReportAllocs()
@ -74,7 +73,7 @@ func BenchmarkGetKV(b *testing.B) {
func BenchmarkInsertAuditEntry(b *testing.B) {
d := newBenchDelegate(b)
ctx := context.Background()
ctx := b.Context()
b.ReportAllocs()
b.ResetTimer()
@ -93,7 +92,7 @@ func BenchmarkInsertAuditEntry(b *testing.B) {
// BenchmarkInsertRecallItems_Sequential measures sequential single-insert performance.
func BenchmarkInsertRecallItems_Sequential(b *testing.B) {
d := newBenchDelegate(b)
ctx := context.Background()
ctx := b.Context()
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
@ -116,7 +115,7 @@ func BenchmarkInsertRecallItems_Sequential(b *testing.B) {
// Compare with BenchmarkInsertRecallItems_Sequential to quantify WAL savings.
func BenchmarkInsertRecallItems_Batch(b *testing.B) {
d := newBenchDelegate(b)
ctx := context.Background()
ctx := b.Context()
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
@ -140,7 +139,7 @@ func BenchmarkInsertRecallItems_Batch(b *testing.B) {
// BenchmarkInsertArchivalChunks_Sequential measures sequential chunk inserts.
func BenchmarkInsertArchivalChunks_Sequential(b *testing.B) {
d := newBenchDelegate(b)
ctx := context.Background()
ctx := b.Context()
recallID := ids.New()
_ = d.InsertRecallItem(ctx, &memory.RecallItem{
ID: recallID, AgentID: "bench-agent", SessionKey: "s",
@ -162,7 +161,7 @@ func BenchmarkInsertArchivalChunks_Sequential(b *testing.B) {
// BenchmarkInsertArchivalChunks_Batch measures batch-tx chunk inserts for 5 chunks.
func BenchmarkInsertArchivalChunks_Batch(b *testing.B) {
d := newBenchDelegate(b)
ctx := context.Background()
ctx := b.Context()
recallID := ids.New()
_ = d.InsertRecallItem(ctx, &memory.RecallItem{
ID: recallID, AgentID: "bench-agent", SessionKey: "s",
@ -185,11 +184,12 @@ func BenchmarkInsertArchivalChunks_Batch(b *testing.B) {
func newBenchDelegate(b *testing.B) *LibSQLDelegate {
b.Helper()
ctx := b.Context()
d, err := NewLibSQLInMemory()
if err != nil {
b.Fatal(err)
}
if err := d.Init(context.Background()); err != nil {
if err := d.Init(ctx); err != nil {
b.Fatal(err)
}
b.Cleanup(func() { d.Close() })

View file

@ -1,7 +1,6 @@
package delegate
import (
"context"
"testing"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory/dag"
@ -10,8 +9,9 @@ import (
)
func TestLibSQLDelegate_PersistDAG(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
compressor := dag.NewCompressor(dag.DefaultCompressorConfig())
msgs := make([]dag.Message, 16)

View file

@ -21,6 +21,7 @@ func makeDoc(agentID, name, category, content string) *memory.AgentDocument {
}
func TestLibSQLDelegate_GetDocument(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -67,7 +68,7 @@ func TestLibSQLDelegate_GetDocument(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}
@ -86,6 +87,7 @@ func TestLibSQLDelegate_GetDocument(t *testing.T) {
}
func TestLibSQLDelegate_UpsertDocument(t *testing.T) {
t.Parallel()
tests := []struct {
name string
ops []*memory.AgentDocument
@ -139,7 +141,7 @@ func TestLibSQLDelegate_UpsertDocument(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
for _, doc := range tt.ops {
require.NoError(t, d.UpsertDocument(ctx, doc))
}
@ -152,6 +154,7 @@ func TestLibSQLDelegate_UpsertDocument(t *testing.T) {
}
func TestLibSQLDelegate_DeleteDocument(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -185,7 +188,7 @@ func TestLibSQLDelegate_DeleteDocument(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}
@ -206,6 +209,7 @@ func TestLibSQLDelegate_DeleteDocument(t *testing.T) {
}
func TestLibSQLDelegate_ListDocumentsByCategory(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -256,7 +260,7 @@ func TestLibSQLDelegate_ListDocumentsByCategory(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}
@ -271,6 +275,7 @@ func TestLibSQLDelegate_ListDocumentsByCategory(t *testing.T) {
}
func TestLibSQLDelegate_ListAllDocuments(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -318,7 +323,7 @@ func TestLibSQLDelegate_ListAllDocuments(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}

View file

@ -1,7 +1,6 @@
package delegate
import (
"context"
"testing"
jsonv2 "github.com/go-json-experiment/json"
@ -14,8 +13,9 @@ import (
)
func TestCronKVBackend_Roundtrip(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
agentID := pkg.NAME
kvKey := "cron:store"
@ -61,8 +61,9 @@ func TestCronKVBackend_Roundtrip(t *testing.T) {
}
func TestCronKVBackend_UpdatePreservesShape(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
agentID := pkg.NAME
kvKey := "cron:store"
@ -78,8 +79,9 @@ func TestCronKVBackend_UpdatePreservesShape(t *testing.T) {
}
func TestCronKVBackend_PrefixScan(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
agentID := pkg.NAME
require.NoError(t, d.UpsertKV(ctx, agentID, "cron:store", "{}"))
@ -94,8 +96,9 @@ func TestCronKVBackend_PrefixScan(t *testing.T) {
}
func TestEndToEnd_SessionAndAuditFlow(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
agentID := "a1"
sessionKey := "sess-integration"
@ -171,8 +174,9 @@ func TestEndToEnd_SessionAndAuditFlow(t *testing.T) {
}
func TestEndToEnd_WorkingContextAndRecallRoundtrip(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
agentID := "a1"
sessionKey := "sess-wc"

View file

@ -9,6 +9,7 @@ import (
)
func TestLibSQLDelegate_GetKV(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -73,7 +74,7 @@ func TestLibSQLDelegate_GetKV(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}
@ -89,6 +90,7 @@ func TestLibSQLDelegate_GetKV(t *testing.T) {
}
func TestLibSQLDelegate_UpsertKV(t *testing.T) {
t.Parallel()
tests := []struct {
name string
ops []kvOp
@ -147,7 +149,7 @@ func TestLibSQLDelegate_UpsertKV(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
for _, op := range tt.ops {
require.NoError(t, d.UpsertKV(ctx, op.agent, op.key, op.val))
}
@ -159,6 +161,7 @@ func TestLibSQLDelegate_UpsertKV(t *testing.T) {
}
func TestLibSQLDelegate_DeleteKV(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -193,7 +196,7 @@ func TestLibSQLDelegate_DeleteKV(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}
@ -218,6 +221,7 @@ func TestLibSQLDelegate_DeleteKV(t *testing.T) {
}
func TestLibSQLDelegate_ListKVByPrefix(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -286,7 +290,7 @@ func TestLibSQLDelegate_ListKVByPrefix(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}

View file

@ -30,6 +30,7 @@ func makeRecallItem(agentID, sessionKey, role, content, tags string) *memory.Rec
}
func TestLibSQLDelegate_InsertSessionMessage(t *testing.T) {
t.Parallel()
tests := []struct {
name string
agentID string
@ -70,7 +71,7 @@ func TestLibSQLDelegate_InsertSessionMessage(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
require.NoError(t, d.InsertSessionMessage(ctx, tt.agentID, tt.sessionKey, tt.role, tt.content))
@ -82,6 +83,7 @@ func TestLibSQLDelegate_InsertSessionMessage(t *testing.T) {
}
func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -182,7 +184,7 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}
@ -206,6 +208,7 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
}
func TestLibSQLDelegate_CountSessionMessages(t *testing.T) {
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
@ -257,7 +260,7 @@ func TestLibSQLDelegate_CountSessionMessages(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
if tt.setup != nil {
tt.setup(t, d, ctx)
}

View file

@ -1,7 +1,6 @@
package delegate
import (
"context"
"fmt"
"testing"
@ -15,7 +14,7 @@ func newTestDelegate(t *testing.T) *LibSQLDelegate {
if err != nil {
t.Fatalf("NewLibSQLInMemory: %v", err)
}
if err := d.Init(context.Background()); err != nil {
if err := d.Init(t.Context()); err != nil {
t.Fatalf("Init: %v", err)
}
t.Cleanup(func() { d.Close() })
@ -23,8 +22,9 @@ func newTestDelegate(t *testing.T) *LibSQLDelegate {
}
func TestLibSQLDelegate_WorkingContext(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
// Initially nil
wc, err := d.GetWorkingContext(ctx, "agent-1", "sess-1")
@ -63,8 +63,9 @@ func TestLibSQLDelegate_WorkingContext(t *testing.T) {
}
func TestLibSQLDelegate_RecallItemCRUD(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
item := &memory.RecallItem{
ID: ids.New(),
@ -156,8 +157,9 @@ func testEmbedding768(seed ...float32) []float32 {
}
func TestLibSQLDelegate_ArchivalChunkCRUD(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
parentRecall := &memory.RecallItem{
ID: ids.New(), AgentID: "agent-1", SessionKey: "sess-1",
@ -237,8 +239,9 @@ func TestLibSQLDelegate_ArchivalChunkCRUD(t *testing.T) {
}
func TestLibSQLDelegate_SummaryCRUD(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
summary := &memory.MemorySummary{
ID: ids.New(),
@ -266,8 +269,9 @@ func TestLibSQLDelegate_SummaryCRUD(t *testing.T) {
}
func TestLibSQLDelegate_KeywordSearch(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
items := []*memory.RecallItem{
{ID: ids.New(), AgentID: "agent-1", SessionKey: "s1", Role: "user", Sector: memory.SectorSemantic, Importance: 0.9, Content: "Go programming language is fast"},
@ -291,8 +295,9 @@ func TestLibSQLDelegate_KeywordSearch(t *testing.T) {
}
func TestLibSQLDelegate_Counts(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
// Initial counts should be zero
rc, err := d.CountRecallItems(ctx, "agent-1", "")
@ -328,8 +333,9 @@ func TestLibSQLDelegate_Counts(t *testing.T) {
}
func TestLibSQLDelegate_FTSSearch(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
// Insert recall items with searchable content
items := []*memory.RecallItem{
@ -382,8 +388,9 @@ func TestLibSQLDelegate_FTSSearch(t *testing.T) {
}
func TestLibSQLDelegate_VectorSearch(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
// Insert archival chunks with 768-dim embeddings (schema requires F32_BLOB(768))
embData := [][]float32{
@ -439,6 +446,7 @@ func TestLibSQLDelegate_VectorSearch(t *testing.T) {
}
func TestBuildFTSMatchExpr(t *testing.T) {
t.Parallel()
tests := []struct {
input string
expected string
@ -465,6 +473,7 @@ func TestBuildFTSMatchExpr(t *testing.T) {
}
func TestVectorToString(t *testing.T) {
t.Parallel()
tests := []struct {
input memory.Embedding
expected string
@ -485,7 +494,10 @@ func TestVectorToString(t *testing.T) {
}
func TestExtractVector(t *testing.T) {
t.Parallel(
// Round-trip test: Embedding.Value() -> blob -> extractVector
)
original := memory.Embedding{0.1, -0.2, 0.3, 0.99, -0.01}
dv, err := original.Value()
if err != nil {
@ -510,6 +522,7 @@ func TestExtractVector(t *testing.T) {
}
func TestLibSQLDelegate_Capabilities(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
// After Init(), capabilities should have been probed
@ -526,13 +539,14 @@ func TestLibSQLDelegate_Capabilities(t *testing.T) {
}
// Calling detect again should be a no-op (idempotent)
d.detectCapabilities(context.Background())
d.detectCapabilities(t.Context())
if d.HasFTS() != hasFTS || d.HasVectorSearch() != hasVec {
t.Error("detectCapabilities changed results on second call — not idempotent")
}
}
func TestEmbeddingValueScanRoundTrip(t *testing.T) {
t.Parallel()
vectors := []memory.Embedding{
{0.0, 1.0, -1.0, 0.5, -0.5},
{3.4028235e+38, -3.4028235e+38}, // max float32
@ -578,8 +592,9 @@ func TestEmbeddingValueScanRoundTrip(t *testing.T) {
}
func TestIntegration_FullStackNoDisk(t *testing.T) {
t.Parallel()
d := newTestDelegate(t)
ctx := context.Background()
ctx := t.Context()
agentID := "integration-agent"
t.Run("KV_Store", func(t *testing.T) {

View file

@ -3,7 +3,6 @@
package memory_test
import (
"context"
"testing"
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
@ -23,7 +22,7 @@ func setupFullStack(t *testing.T) (*memstore.MemoryStore, *delegate.LibSQLDelega
t.Helper()
del, err := delegate.NewLibSQLInMemory()
require.NoError(t, err, "create in-memory delegate")
require.NoError(t, del.Init(context.Background()), "run goose migrations")
require.NoError(t, del.Init(t.Context()), "run goose migrations")
t.Cleanup(func() { del.Close() })
chunker := memstore.NewMarkdownChunker(memstore.DefaultMarkdownChunkerConfig())
@ -33,21 +32,23 @@ func setupFullStack(t *testing.T) (*memstore.MemoryStore, *delegate.LibSQLDelega
}
func TestIntegration_GooseMigrationIdempotent(t *testing.T) {
t.Parallel()
del, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
defer del.Close()
ctx := context.Background()
ctx := t.Context()
require.NoError(t, del.Init(ctx), "first migration up")
require.NoError(t, del.Init(ctx), "idempotent re-Init")
}
func TestIntegration_GooseMigration_DownUpRoundTrip(t *testing.T) {
t.Parallel()
del, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
defer del.Close()
ctx := context.Background()
ctx := t.Context()
require.NoError(t, del.Init(ctx), "initial up")
@ -83,8 +84,9 @@ func TestIntegration_GooseMigration_DownUpRoundTrip(t *testing.T) {
}
func TestIntegration_BlobPK_RoundTrip(t *testing.T) {
t.Parallel()
store, _ := setupFullStack(t)
ctx := context.Background()
ctx := t.Context()
item := &memory.RecallItem{
AgentID: testAgent,
@ -106,8 +108,9 @@ func TestIntegration_BlobPK_RoundTrip(t *testing.T) {
}
func TestIntegration_ArchivalChunking_EndToEnd(t *testing.T) {
t.Parallel()
store, del := setupFullStack(t)
ctx := context.Background()
ctx := t.Context()
longContent := "# Architecture Notes\n\nThe system uses hexagonal architecture with ports and adapters.\n\n"
longContent += "## Database Layer\n\nWe use libSQL with BLOB primary keys for storage efficiency.\n\n"
@ -131,8 +134,9 @@ func TestIntegration_ArchivalChunking_EndToEnd(t *testing.T) {
}
func TestIntegration_FTS5Search(t *testing.T) {
t.Parallel()
store, _ := setupFullStack(t)
ctx := context.Background()
ctx := t.Context()
items := []struct {
content string
@ -172,8 +176,9 @@ func TestIntegration_FTS5Search(t *testing.T) {
}
func TestIntegration_CascadeDelete(t *testing.T) {
t.Parallel()
store, del := setupFullStack(t)
ctx := context.Background()
ctx := t.Context()
recallID, err := store.StoreArchival(ctx, "Archival content to be cascade deleted", testAgent, map[string]string{
"agent_id": testAgent,
@ -195,8 +200,9 @@ func TestIntegration_CascadeDelete(t *testing.T) {
}
func TestIntegration_ToolOffload(t *testing.T) {
t.Parallel()
store, _ := setupFullStack(t)
ctx := context.Background()
ctx := t.Context()
largeResult := ""
for i := 0; i < 500; i++ {
@ -211,8 +217,9 @@ func TestIntegration_ToolOffload(t *testing.T) {
}
func TestIntegration_WorkingContext_Persistence(t *testing.T) {
t.Parallel()
_, del := setupFullStack(t)
ctx := context.Background()
ctx := t.Context()
require.NoError(t, del.UpsertWorkingContext(ctx, testAgent, testSession, "initial state"))
@ -228,8 +235,9 @@ func TestIntegration_WorkingContext_Persistence(t *testing.T) {
}
func TestIntegration_Summary_CRUD(t *testing.T) {
t.Parallel()
store, del := setupFullStack(t)
ctx := context.Background()
ctx := t.Context()
summary := &memory.MemorySummary{
AgentID: testAgent,
@ -249,8 +257,9 @@ func TestIntegration_Summary_CRUD(t *testing.T) {
}
func TestIntegration_IDUniqueness_AcrossEntities(t *testing.T) {
t.Parallel()
store, _ := setupFullStack(t)
ctx := context.Background()
ctx := t.Context()
seenIDs := make(map[ids.UUID]string)
@ -284,8 +293,9 @@ func TestIntegration_IDUniqueness_AcrossEntities(t *testing.T) {
}
func TestIntegration_ContextPressure(t *testing.T) {
t.Parallel()
store, _ := setupFullStack(t)
ctx := context.Background()
ctx := t.Context()
pressure, err := store.ContextUsage(ctx, testAgent, testSession)
require.NoError(t, err)

View file

@ -9,6 +9,7 @@ import (
)
func TestActiveContextProjection_TotalTokens(t *testing.T) {
t.Parallel()
p := &ActiveContextProjection{
Segments: []ProjectionSegment{
{Tokens: 120},
@ -20,6 +21,7 @@ func TestActiveContextProjection_TotalTokens(t *testing.T) {
}
func TestActiveContextProjection_HasLosslessRefs(t *testing.T) {
t.Parallel()
now := time.Now().UTC()
p := &ActiveContextProjection{
Segments: []ProjectionSegment{

View file

@ -115,6 +115,7 @@ func writeSessionFile(t *testing.T, dir, name string, sess SessionFile) {
}
func TestMigrateFileSessions_Basic(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()
@ -182,6 +183,7 @@ func TestMigrateFileSessions_Basic(t *testing.T) {
}
func TestMigrateFileSessions_Idempotent(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()
@ -217,6 +219,7 @@ func TestMigrateFileSessions_Idempotent(t *testing.T) {
}
func TestMigrateFileSessions_EmptyDir(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()
@ -230,6 +233,7 @@ func TestMigrateFileSessions_EmptyDir(t *testing.T) {
}
func TestMigrateFileSessions_NonexistentDir(t *testing.T) {
t.Parallel()
del := newMockDelegate()
result, err := MigrateFileSessions(t.Context(), del, pkg.NAME, "/nonexistent/path")
@ -242,6 +246,7 @@ func TestMigrateFileSessions_NonexistentDir(t *testing.T) {
}
func TestMigrateFileSessions_SkipsEmptyMessages(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()
@ -264,6 +269,7 @@ func TestMigrateFileSessions_SkipsEmptyMessages(t *testing.T) {
}
func TestMigrateFileSessions_FallbackKey(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()
@ -288,6 +294,7 @@ func TestMigrateFileSessions_FallbackKey(t *testing.T) {
}
func TestMigrateFileSessions_MalformedJSON(t *testing.T) {
t.Parallel()
sessDir := t.TempDir()
del := newMockDelegate()

View file

@ -7,24 +7,30 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewObservation_ThreeDateModel(t *testing.T) {
t.Parallel()
ref := time.Date(2026, 2, 16, 10, 0, 0, 0, time.UTC)
obs := time.Date(2026, 2, 18, 14, 30, 0, 0, time.UTC)
o := NewObservation("User prefers Go over Rust", PriorityNotable, ref, obs)
assert.Equal(t, "User prefers Go over Rust", o.Content)
assert.Equal(t, PriorityNotable, o.Priority)
assert.Equal(t, ref.Unix(), o.ReferencedAt)
assert.Equal(t, obs.Unix(), o.ObservedAt)
assert.Equal(t, "2 days ago", o.RelativeDate)
assert.Empty(t, cmp.Diff(Observation{
Content: "User prefers Go over Rust",
Priority: PriorityNotable,
ObservedAt: obs.Unix(),
ReferencedAt: ref.Unix(),
RelativeDate: "2 days ago",
}, o))
}
func TestRelativeDate(t *testing.T) {
t.Parallel()
now := time.Date(2026, 2, 18, 14, 0, 0, 0, time.UTC)
tests := []struct {
@ -53,6 +59,7 @@ func TestRelativeDate(t *testing.T) {
}
func TestPriorityEmoji(t *testing.T) {
t.Parallel()
assert.Equal(t, "🔴", PriorityCritical.Emoji())
assert.Equal(t, "🟡", PriorityNotable.Emoji())
assert.Equal(t, "🔵", PriorityInformational.Emoji())
@ -60,6 +67,7 @@ func TestPriorityEmoji(t *testing.T) {
}
func TestFormatBlock(t *testing.T) {
t.Parallel()
now := time.Date(2026, 2, 18, 14, 30, 0, 0, time.UTC)
obs := []Observation{
NewObservation("Decision: use SQLite", PriorityCritical, now, now),
@ -75,11 +83,13 @@ func TestFormatBlock(t *testing.T) {
}
func TestFormatBlock_Empty(t *testing.T) {
t.Parallel()
assert.Equal(t, "", FormatBlock(nil))
assert.Equal(t, "", FormatBlock([]Observation{}))
}
func TestMarshalUnmarshalRoundTrip(t *testing.T) {
t.Parallel()
now := time.Now()
obs := []Observation{
NewObservation("Fact A", PriorityCritical, now, now),
@ -93,23 +103,25 @@ func TestMarshalUnmarshalRoundTrip(t *testing.T) {
parsed, err := UnmarshalObservations(data)
require.NoError(t, err)
assert.Len(t, parsed, 2)
assert.Equal(t, "Fact A", parsed[0].Content)
assert.Equal(t, PriorityCritical, parsed[0].Priority)
assert.Empty(t, cmp.Diff(obs[0], parsed[0]))
}
func TestUnmarshalObservations_Empty(t *testing.T) {
t.Parallel()
obs, err := UnmarshalObservations("")
assert.NoError(t, err)
assert.Nil(t, obs)
}
func TestEstimateTokens(t *testing.T) {
t.Parallel()
tokens := EstimateTokens("Hello, world!")
assert.True(t, tokens > 0)
assert.True(t, tokens < 20)
}
func TestParseObservations(t *testing.T) {
t.Parallel()
now := time.Now()
response := `critical|User decided to migrate to SQLite
notable|Prefers hexagonal architecture
@ -119,13 +131,31 @@ notable|`
obs := parseObservations(response, now)
assert.Len(t, obs, 3)
assert.Equal(t, PriorityCritical, obs[0].Priority)
assert.Equal(t, "User decided to migrate to SQLite", obs[0].Content)
assert.Equal(t, PriorityNotable, obs[1].Priority)
assert.Equal(t, PriorityInformational, obs[2].Priority)
assert.Empty(t, cmp.Diff(Observation{
Content: "User decided to migrate to SQLite",
Priority: PriorityCritical,
ObservedAt: now.Unix(),
ReferencedAt: now.Unix(),
RelativeDate: relativeDate(now, now),
}, obs[0]))
assert.Empty(t, cmp.Diff(Observation{
Content: "Prefers hexagonal architecture",
Priority: PriorityNotable,
ObservedAt: now.Unix(),
ReferencedAt: now.Unix(),
RelativeDate: relativeDate(now, now),
}, obs[1]))
assert.Empty(t, cmp.Diff(Observation{
Content: "Uses VS Code as primary editor",
Priority: PriorityInformational,
ObservedAt: now.Unix(),
ReferencedAt: now.Unix(),
RelativeDate: relativeDate(now, now),
}, obs[2]))
}
func TestObserver_ShouldObserve(t *testing.T) {
t.Parallel()
mockModel := func(_ context.Context, _ string) (string, error) {
return "", nil
}
@ -140,6 +170,7 @@ func TestObserver_ShouldObserve(t *testing.T) {
}
func TestObserver_Observe(t *testing.T) {
t.Parallel()
mockModel := func(_ context.Context, prompt string) (string, error) {
return "critical|Important decision made\nnotable|User preference noted", nil
}
@ -151,13 +182,14 @@ func TestObserver_Observe(t *testing.T) {
{Role: "assistant", Content: "Good choice for embedded use cases"},
}
obs, err := o.Observe(context.Background(), msgs, nil)
obs, err := o.Observe(t.Context(), msgs, nil)
require.NoError(t, err)
assert.Len(t, obs, 2)
assert.Equal(t, PriorityCritical, obs[0].Priority)
}
func TestReflector_ShouldReflect(t *testing.T) {
t.Parallel()
mockModel := func(_ context.Context, _ string) (string, error) {
return "", nil
}
@ -175,6 +207,7 @@ func TestReflector_ShouldReflect(t *testing.T) {
}
func TestReflector_Reflect(t *testing.T) {
t.Parallel()
mockModel := func(_ context.Context, prompt string) (string, error) {
return "KEEP 0\nDROP 1\nKEEP 2", nil
}
@ -188,7 +221,7 @@ func TestReflector_Reflect(t *testing.T) {
NewObservation("Notable thing", PriorityNotable, now, now),
}
kept, err := r.Reflect(context.Background(), obs)
kept, err := r.Reflect(t.Context(), obs)
require.NoError(t, err)
assert.Len(t, kept, 2)
assert.Equal(t, "Critical fact", kept[0].Content)
@ -196,6 +229,7 @@ func TestReflector_Reflect(t *testing.T) {
}
func TestReflector_ReflectFallbackKeepsCritical(t *testing.T) {
t.Parallel()
mockModel := func(_ context.Context, _ string) (string, error) {
return "garbage output", nil
}
@ -208,13 +242,14 @@ func TestReflector_ReflectFallbackKeepsCritical(t *testing.T) {
NewObservation("Can drop", PriorityInformational, now, now),
}
kept, err := r.Reflect(context.Background(), obs)
kept, err := r.Reflect(t.Context(), obs)
require.NoError(t, err)
assert.Len(t, kept, 1)
assert.Equal(t, "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"))
@ -223,6 +258,7 @@ func TestParsePriority(t *testing.T) {
}
func TestParseKeptIndices(t *testing.T) {
t.Parallel()
now := time.Now()
obs := []Observation{
NewObservation("A", PriorityCritical, now, now),

View file

@ -42,9 +42,10 @@ func (e *countingEmbedder) Dimensions() int { return e.dims }
func (e *countingEmbedder) Model() string { return "test-model" }
func TestCachedEmbedder_CachesIdenticalText(t *testing.T) {
t.Parallel()
inner := &countingEmbedder{dims: 8}
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
ctx := context.Background()
ctx := t.Context()
// First call — should hit inner
vec1, err := cached.Embed(ctx, "hello world")
@ -76,9 +77,10 @@ func TestCachedEmbedder_CachesIdenticalText(t *testing.T) {
}
func TestCachedEmbedder_DifferentTextHitsInner(t *testing.T) {
t.Parallel()
inner := &countingEmbedder{dims: 4}
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
ctx := context.Background()
ctx := t.Context()
cached.Embed(ctx, "text A")
cached.Embed(ctx, "text B")
@ -93,9 +95,10 @@ func TestCachedEmbedder_DifferentTextHitsInner(t *testing.T) {
}
func TestCachedEmbedder_BatchPartialCache(t *testing.T) {
t.Parallel()
inner := &countingEmbedder{dims: 4}
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
ctx := context.Background()
ctx := t.Context()
// Pre-cache one text
cached.Embed(ctx, "cached text")
@ -126,9 +129,10 @@ func TestCachedEmbedder_BatchPartialCache(t *testing.T) {
}
func TestCachedEmbedder_BatchAllCached(t *testing.T) {
t.Parallel()
inner := &countingEmbedder{dims: 4}
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
ctx := context.Background()
ctx := t.Context()
// Pre-cache all texts
cached.Embed(ctx, "A")
@ -148,6 +152,7 @@ func TestCachedEmbedder_BatchAllCached(t *testing.T) {
}
func TestCachedEmbedder_Dimensions(t *testing.T) {
t.Parallel()
inner := &countingEmbedder{dims: 768}
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
if cached.Dimensions() != 768 {
@ -156,6 +161,7 @@ func TestCachedEmbedder_Dimensions(t *testing.T) {
}
func TestCachedEmbedder_Model(t *testing.T) {
t.Parallel()
inner := &countingEmbedder{dims: 4}
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
if cached.Model() != "test-model" {
@ -164,12 +170,13 @@ func TestCachedEmbedder_Model(t *testing.T) {
}
func TestCachedEmbedder_MaxEntries(t *testing.T) {
t.Parallel()
inner := &countingEmbedder{dims: 4}
cached := NewCachedEmbedder(inner, CachedEmbedderConfig{
MaxEntries: 3,
TTL: time.Hour,
})
ctx := context.Background()
ctx := t.Context()
// Fill cache
cached.Embed(ctx, "A")

View file

@ -9,6 +9,7 @@ import (
)
func TestMarkdownChunker_BasicSplit(t *testing.T) {
t.Parallel()
chunker := NewMarkdownChunker(MarkdownChunkerConfig{
ChunkSize: 100,
ChunkOverlap: 20,
@ -26,6 +27,7 @@ func TestMarkdownChunker_BasicSplit(t *testing.T) {
}
func TestMarkdownChunker_SmallContent(t *testing.T) {
t.Parallel()
chunker := NewMarkdownChunker(DefaultMarkdownChunkerConfig())
chunks, err := chunker.Chunk("Short text.")
@ -35,6 +37,7 @@ func TestMarkdownChunker_SmallContent(t *testing.T) {
}
func TestMarkdownChunker_PreservesMarkdownStructure(t *testing.T) {
t.Parallel()
chunker := NewMarkdownChunker(MarkdownChunkerConfig{
ChunkSize: 200,
ChunkOverlap: 40,
@ -81,6 +84,7 @@ Even more content follows here with additional details and explanations that mak
}
func TestMarkdownChunker_EmptyContent(t *testing.T) {
t.Parallel()
chunker := NewMarkdownChunker(DefaultMarkdownChunkerConfig())
chunks, err := chunker.Chunk("")
require.NoError(t, err)
@ -88,6 +92,7 @@ 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)

View file

@ -1,15 +1,17 @@
package store
import (
jsonv2 "github.com/go-json-experiment/json"
"net/http"
"net/http/httptest"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/ZanzyTHEbar/dragonscale/pkg/config"
)
func TestNewEmbedderFromConfig_EmptyProvider(t *testing.T) {
t.Parallel()
emb, err := NewEmbedderFromConfig(config.EmbeddingConfig{}, config.ProvidersConfig{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
@ -20,6 +22,7 @@ func TestNewEmbedderFromConfig_EmptyProvider(t *testing.T) {
}
func TestNewEmbedderFromConfig_UnknownProvider(t *testing.T) {
t.Parallel()
_, err := NewEmbedderFromConfig(config.EmbeddingConfig{Provider: "nonexistent"}, config.ProvidersConfig{})
if err == nil {
t.Error("expected error for unknown provider")
@ -27,6 +30,7 @@ func TestNewEmbedderFromConfig_UnknownProvider(t *testing.T) {
}
func TestNewEmbedderFromConfig_OpenAI_NoKey(t *testing.T) {
t.Parallel()
_, err := NewEmbedderFromConfig(
config.EmbeddingConfig{Provider: "openai"},
config.ProvidersConfig{},
@ -37,6 +41,7 @@ func TestNewEmbedderFromConfig_OpenAI_NoKey(t *testing.T) {
}
func TestNewEmbedderFromConfig_OpenAI_FallbackKey(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
respData, _ := jsonv2.Marshal(map[string]interface{}{
"data": []map[string]interface{}{
@ -70,6 +75,7 @@ func TestNewEmbedderFromConfig_OpenAI_FallbackKey(t *testing.T) {
}
func TestNewEmbedderFromConfig_Ollama(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
respData, _ := jsonv2.Marshal(map[string]interface{}{
"embeddings": [][]float32{{0.1, 0.2, 0.3}},
@ -98,6 +104,7 @@ func TestNewEmbedderFromConfig_Ollama(t *testing.T) {
}
func TestNewEmbedderFromConfig_Ollama_FallbackBase(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
respData, _ := jsonv2.Marshal(map[string]interface{}{
"embeddings": [][]float32{{0.5}},
@ -121,6 +128,7 @@ func TestNewEmbedderFromConfig_Ollama_FallbackBase(t *testing.T) {
}
func TestNewEmbedderFromConfig_CaseInsensitive(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
respData, _ := jsonv2.Marshal(map[string]interface{}{
"embeddings": [][]float32{{0.1}},

View file

@ -1,14 +1,15 @@
package store
import (
"context"
jsonv2 "github.com/go-json-experiment/json"
"net/http"
"net/http/httptest"
"testing"
jsonv2 "github.com/go-json-experiment/json"
)
func TestOllamaEmbedder_Embed(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/embed" {
t.Errorf("expected /api/embed, got %s", r.URL.Path)
@ -35,7 +36,7 @@ func TestOllamaEmbedder_Embed(t *testing.T) {
Model: "test-model",
})
vec, err := e.Embed(context.Background(), "hello world")
vec, err := e.Embed(t.Context(), "hello world")
if err != nil {
t.Fatalf("Embed: %v", err)
}
@ -53,6 +54,7 @@ func TestOllamaEmbedder_Embed(t *testing.T) {
}
func TestOllamaEmbedder_EmbedBatch(t *testing.T) {
t.Parallel()
callCount := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
callCount++
@ -65,7 +67,7 @@ func TestOllamaEmbedder_EmbedBatch(t *testing.T) {
e := NewOllamaEmbedder(OllamaEmbedderConfig{Base: srv.URL})
vecs, err := e.EmbedBatch(context.Background(), []string{"a", "b", "c"})
vecs, err := e.EmbedBatch(t.Context(), []string{"a", "b", "c"})
if err != nil {
t.Fatalf("EmbedBatch: %v", err)
}
@ -78,6 +80,7 @@ func TestOllamaEmbedder_EmbedBatch(t *testing.T) {
}
func TestOllamaEmbedder_ServerError(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("model not found"))
@ -85,13 +88,14 @@ func TestOllamaEmbedder_ServerError(t *testing.T) {
defer srv.Close()
e := NewOllamaEmbedder(OllamaEmbedderConfig{Base: srv.URL})
_, err := e.Embed(context.Background(), "test")
_, err := e.Embed(t.Context(), "test")
if err == nil {
t.Error("expected error for server error response")
}
}
func TestOllamaEmbedder_EmptyResponse(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
respData, _ := jsonv2.Marshal(ollamaEmbedResponse{Embeddings: [][]float32{}})
w.Write(respData)
@ -99,13 +103,14 @@ func TestOllamaEmbedder_EmptyResponse(t *testing.T) {
defer srv.Close()
e := NewOllamaEmbedder(OllamaEmbedderConfig{Base: srv.URL})
_, err := e.Embed(context.Background(), "test")
_, err := e.Embed(t.Context(), "test")
if err == nil {
t.Error("expected error for empty embeddings")
}
}
func TestOllamaEmbedder_DefaultModel(t *testing.T) {
t.Parallel()
e := NewOllamaEmbedder(OllamaEmbedderConfig{})
if e.Model() != defaultOllamaModel {
t.Errorf("expected %q, got %q", defaultOllamaModel, e.Model())

View file

@ -1,14 +1,15 @@
package store
import (
"context"
jsonv2 "github.com/go-json-experiment/json"
"net/http"
"net/http/httptest"
"testing"
jsonv2 "github.com/go-json-experiment/json"
)
func TestOpenAIEmbedder_Embed(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/embeddings" {
t.Errorf("expected /embeddings, got %s", r.URL.Path)
@ -40,7 +41,7 @@ func TestOpenAIEmbedder_Embed(t *testing.T) {
APIKey: "test-key",
})
vec, err := e.Embed(context.Background(), "hello")
vec, err := e.Embed(t.Context(), "hello")
if err != nil {
t.Fatalf("Embed: %v", err)
}
@ -56,6 +57,7 @@ func TestOpenAIEmbedder_Embed(t *testing.T) {
}
func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req openAIEmbedRequest
jsonv2.UnmarshalRead(r.Body, &req)
@ -84,7 +86,7 @@ func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
APIKey: "k",
})
vecs, err := e.EmbedBatch(context.Background(), []string{"a", "b", "c"})
vecs, err := e.EmbedBatch(t.Context(), []string{"a", "b", "c"})
if err != nil {
t.Fatalf("EmbedBatch: %v", err)
}
@ -94,6 +96,7 @@ func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
}
func TestOpenAIEmbedder_SingleTextNotArray(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req openAIEmbedRequest
jsonv2.UnmarshalRead(r.Body, &req)
@ -111,13 +114,14 @@ func TestOpenAIEmbedder_SingleTextNotArray(t *testing.T) {
defer srv.Close()
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{Base: srv.URL, APIKey: "k"})
_, err := e.Embed(context.Background(), "single")
_, err := e.Embed(t.Context(), "single")
if err != nil {
t.Fatalf("Embed: %v", err)
}
}
func TestOpenAIEmbedder_ServerError(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"error":{"message":"invalid api key"}}`))
@ -125,13 +129,14 @@ func TestOpenAIEmbedder_ServerError(t *testing.T) {
defer srv.Close()
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{Base: srv.URL, APIKey: "bad"})
_, err := e.Embed(context.Background(), "test")
_, err := e.Embed(t.Context(), "test")
if err == nil {
t.Error("expected error for 401 response")
}
}
func TestOpenAIEmbedder_DefaultModel(t *testing.T) {
t.Parallel()
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{APIKey: "k"})
if e.Model() != defaultOpenAIModel {
t.Errorf("expected %q, got %q", defaultOpenAIModel, e.Model())
@ -139,6 +144,7 @@ func TestOpenAIEmbedder_DefaultModel(t *testing.T) {
}
func TestOpenAIEmbedder_NoAuth(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "" {
t.Error("expected no auth header when key is empty")
@ -151,7 +157,7 @@ func TestOpenAIEmbedder_NoAuth(t *testing.T) {
defer srv.Close()
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{Base: srv.URL})
_, err := e.Embed(context.Background(), "test")
_, err := e.Embed(t.Context(), "test")
if err != nil {
t.Fatalf("Embed: %v", err)
}

View file

@ -67,7 +67,7 @@ func deterministicVec(text string, dim int) memory.Embedding {
func newTestStore(t *testing.T, withEmbedder bool) *MemoryStore {
t.Helper()
ctx := context.Background()
ctx := t.Context()
del, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
@ -95,7 +95,8 @@ func newTestStore(t *testing.T, withEmbedder bool) *MemoryStore {
}
func TestWorkingContext_SetAndGet(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
// Initially empty
@ -122,7 +123,8 @@ func TestWorkingContext_SetAndGet(t *testing.T) {
}
func TestWorkingContext_IsolatedBySessions(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
err := store.SetWorkingContext(ctx, "agent-1", "session-a", "Context A")
@ -140,7 +142,8 @@ func TestWorkingContext_IsolatedBySessions(t *testing.T) {
}
func TestRecall_CRUD(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
item := &memory.RecallItem{
@ -187,7 +190,8 @@ func TestRecall_CRUD(t *testing.T) {
}
func TestArchival_StoreAndRetrieve(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, true)
// Store a multi-chunk document
@ -209,7 +213,8 @@ func TestArchival_StoreAndRetrieve(t *testing.T) {
}
func TestArchival_WithoutEmbedder(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false) // no embedder
content := "Short archival content for testing without embeddings."
@ -226,7 +231,8 @@ func TestArchival_WithoutEmbedder(t *testing.T) {
}
func TestSearch_KeywordOnly(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
// Seed some recall items
@ -251,7 +257,8 @@ func TestSearch_KeywordOnly(t *testing.T) {
}
func TestSearch_HybridWithEmbeddings(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, true)
// Seed recall items
@ -283,7 +290,8 @@ func TestSearch_HybridWithEmbeddings(t *testing.T) {
}
func TestSearch_ShadowModeUsesBaselineAndTracksParity(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
// Baseline recall hit.
@ -319,7 +327,8 @@ func TestSearch_ShadowModeUsesBaselineAndTracksParity(t *testing.T) {
}
func TestSearch_DoesNotPromoteWithoutAugmentedSignals(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
gates := retrievalPromotionGates{
@ -361,7 +370,8 @@ func TestSearch_DoesNotPromoteWithoutAugmentedSignals(t *testing.T) {
}
func TestSearch_PromoteOnlyOnGateWin(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
gates := retrievalPromotionGates{
@ -406,7 +416,8 @@ func TestSearch_PromoteOnlyOnGateWin(t *testing.T) {
}
func TestSearch_FastRollbackPreservesBaselinePath(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
del, ok := store.delegate.(*delegate.LibSQLDelegate)
@ -492,7 +503,8 @@ func TestSearch_FastRollbackPreservesBaselinePath(t *testing.T) {
}
func TestUpdateRetrievalPolicy_PersistFailuresDoNotBlockTransitions(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, true)
baseDelegate := store.delegate
@ -530,7 +542,8 @@ func TestUpdateRetrievalPolicy_PersistFailuresDoNotBlockTransitions(t *testing.T
}
func TestSearch_ConcurrentRetrievalPolicyUpdates(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, true)
require.NoError(t, store.StoreRecall(ctx, &memory.RecallItem{
@ -576,7 +589,8 @@ func TestSearch_ConcurrentRetrievalPolicyUpdates(t *testing.T) {
}
func TestContextUsage(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
// Empty system — should be normal pressure
@ -597,7 +611,8 @@ func TestContextUsage(t *testing.T) {
}
func TestContextUsage_RecallTokenEstimateUsesContent(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
contentA := strings.Repeat("schedule follow-up reminder ", 80)
@ -631,7 +646,8 @@ func TestContextUsage_RecallTokenEstimateUsesContent(t *testing.T) {
}
func TestContextUsage_PressureLevels(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
del, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
@ -656,6 +672,7 @@ func TestContextUsage_PressureLevels(t *testing.T) {
}
func TestShouldOffload(t *testing.T) {
t.Parallel()
store := &MemoryStore{cfg: Config{OffloadThresholdTokens: 100}}
assert.False(t, store.ShouldOffload("short"))
@ -663,7 +680,8 @@ func TestShouldOffload(t *testing.T) {
}
func TestOffloadToolResult(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
largeContent := strings.Repeat("This is a large tool result that should be offloaded. ", 20)
@ -682,7 +700,8 @@ func TestOffloadToolResult(t *testing.T) {
}
func TestStoreSummary(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, false)
summary := &memory.MemorySummary{
@ -699,7 +718,8 @@ func TestStoreSummary(t *testing.T) {
}
func TestDeleteRecall_CascadesArchival(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
store := newTestStore(t, true)
// Store archival content (creates recall item + archival chunks)
@ -726,6 +746,7 @@ func TestDeleteRecall_CascadesArchival(t *testing.T) {
// --- Retrieval pipeline unit tests ---
func TestCosineSimilarity(t *testing.T) {
t.Parallel()
tests := []struct {
name string
a, b memory.Embedding
@ -747,6 +768,7 @@ func TestCosineSimilarity(t *testing.T) {
}
func TestRRF_MergesTwoSets(t *testing.T) {
t.Parallel()
idA, idB, idC := ids.New(), ids.New(), ids.New()
set1 := []memory.SearchResult{
{ID: idA, Content: "a", Score: 1.0},
@ -764,7 +786,10 @@ func TestRRF_MergesTwoSets(t *testing.T) {
}
func TestRecencyDecay(t *testing.T) {
t.Parallel(
// 0 hours age → decay = 1.0
)
assert.InDelta(t, 1.0, RecencyDecay(0, 168), 0.001)
// 168 hours (1 half-life) → decay = 0.5
@ -775,6 +800,7 @@ func TestRecencyDecay(t *testing.T) {
}
func TestApplyRecencyDecay_ReordersByAge(t *testing.T) {
t.Parallel()
now := time.Now()
idOld, idNew := ids.New(), ids.New()

View file

@ -1,10 +1,10 @@
package store
import (
"context"
jsonv2 "github.com/go-json-experiment/json"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -17,7 +17,7 @@ func newTestMemoryTool(t *testing.T) *MemoryTool {
func executeAndParse(t *testing.T, tool *MemoryTool, input string) *MemoryToolResponse {
t.Helper()
ctx := context.Background()
ctx := t.Context()
raw, err := tool.Execute(ctx, input)
require.NoError(t, err)
@ -27,6 +27,7 @@ func executeAndParse(t *testing.T, tool *MemoryTool, input string) *MemoryToolRe
}
func TestMemoryTool_WriteAndRead(t *testing.T) {
t.Parallel()
tool := newTestMemoryTool(t)
// Write
@ -52,6 +53,7 @@ func TestMemoryTool_WriteAndRead(t *testing.T) {
}
func TestMemoryTool_WriteArchival(t *testing.T) {
t.Parallel()
tool := newTestMemoryTool(t)
resp := executeAndParse(t, tool, `{"action":"write","content":"Large document content for archival.","tier":"archival","source":"test"}`)
@ -60,6 +62,7 @@ func TestMemoryTool_WriteArchival(t *testing.T) {
}
func TestMemoryTool_Search(t *testing.T) {
t.Parallel()
tool := newTestMemoryTool(t)
// Seed data
@ -74,6 +77,7 @@ func TestMemoryTool_Search(t *testing.T) {
}
func TestMemoryTool_Update(t *testing.T) {
t.Parallel()
tool := newTestMemoryTool(t)
// Write
@ -91,6 +95,7 @@ func TestMemoryTool_Update(t *testing.T) {
}
func TestMemoryTool_Delete(t *testing.T) {
t.Parallel()
tool := newTestMemoryTool(t)
// Write
@ -108,6 +113,7 @@ func TestMemoryTool_Delete(t *testing.T) {
}
func TestMemoryTool_Status(t *testing.T) {
t.Parallel()
tool := newTestMemoryTool(t)
resp := executeAndParse(t, tool, `{"action":"status"}`)
@ -118,6 +124,7 @@ func TestMemoryTool_Status(t *testing.T) {
}
func TestMemoryTool_InvalidAction(t *testing.T) {
t.Parallel()
tool := newTestMemoryTool(t)
resp := executeAndParse(t, tool, `{"action":"explode"}`)
@ -126,6 +133,7 @@ func TestMemoryTool_InvalidAction(t *testing.T) {
}
func TestMemoryTool_InvalidJSON(t *testing.T) {
t.Parallel()
tool := newTestMemoryTool(t)
resp := executeAndParse(t, tool, `not json`)
@ -134,6 +142,7 @@ func TestMemoryTool_InvalidJSON(t *testing.T) {
}
func TestMemoryTool_MissingRequiredFields(t *testing.T) {
t.Parallel()
tool := newTestMemoryTool(t)
tests := []struct {

View file

@ -1,7 +1,6 @@
package store
import (
"context"
"strings"
"testing"
@ -13,7 +12,7 @@ import (
func newTestQueueManager(t *testing.T, contextWindow int) (*QueueManager, *MemoryStore) {
t.Helper()
ctx := context.Background()
ctx := t.Context()
del, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
@ -32,7 +31,8 @@ func newTestQueueManager(t *testing.T, contextWindow int) (*QueueManager, *Memor
}
func TestQueueManager_NormalPressure(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
qm, _ := newTestQueueManager(t, 100000)
decision, err := qm.Evaluate(ctx, "agent-1", "session-1")
@ -42,7 +42,8 @@ func TestQueueManager_NormalPressure(t *testing.T) {
}
func TestQueueManager_WarnPressure(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
qm, store := newTestQueueManager(t, 100) // tiny window
// Fill working context to ~75% of context window.
@ -56,7 +57,8 @@ func TestQueueManager_WarnPressure(t *testing.T) {
}
func TestQueueManager_OffloadPressure(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
qm, store := newTestQueueManager(t, 100) // tiny window
// Fill to ~82% of context window.
@ -70,7 +72,8 @@ func TestQueueManager_OffloadPressure(t *testing.T) {
}
func TestQueueManager_FlushPressure(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
qm, store := newTestQueueManager(t, 100) // tiny window
// Fill to ~88% of context window.
@ -84,7 +87,8 @@ func TestQueueManager_FlushPressure(t *testing.T) {
}
func TestQueueManager_EvictOldest(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
qm, store := newTestQueueManager(t, 100)
// Seed recall items
@ -108,7 +112,8 @@ func TestQueueManager_EvictOldest(t *testing.T) {
}
func TestQueueManager_EvictEmpty(t *testing.T) {
ctx := context.Background()
t.Parallel()
ctx := t.Context()
qm, _ := newTestQueueManager(t, 100)
evicted, summary, err := qm.EvictOldest(ctx, "agent-1", "empty-session")
@ -118,6 +123,7 @@ func TestQueueManager_EvictEmpty(t *testing.T) {
}
func TestDefaultQueueManagerConfig(t *testing.T) {
t.Parallel()
cfg := DefaultQueueManagerConfig()
assert.InDelta(t, 0.70, cfg.WarnThreshold, 0.001)
assert.InDelta(t, 0.80, cfg.OffloadThreshold, 0.001)

View file

@ -1,7 +1,6 @@
package store
import (
"context"
"testing"
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
@ -10,8 +9,9 @@ import (
)
func TestHeuristicScorer_BasicScoring(t *testing.T) {
t.Parallel()
scorer := NewHeuristicScorer()
ctx := context.Background()
ctx := t.Context()
tests := []struct {
name string
@ -38,8 +38,9 @@ func TestHeuristicScorer_BasicScoring(t *testing.T) {
}
func TestHeuristicScorer_SectorClassification(t *testing.T) {
t.Parallel()
scorer := NewHeuristicScorer()
ctx := context.Background()
ctx := t.Context()
tests := []struct {
name string
@ -78,6 +79,7 @@ func TestHeuristicScorer_SectorClassification(t *testing.T) {
}
func TestParseScoringResponse_ValidJSON(t *testing.T) {
t.Parallel()
input := `{"importance": 0.85, "salience": 0.6, "sector": "semantic"}`
result, err := parseScoringResponse(input)
require.NoError(t, err)
@ -87,6 +89,7 @@ func TestParseScoringResponse_ValidJSON(t *testing.T) {
}
func TestParseScoringResponse_WithCodeFences(t *testing.T) {
t.Parallel()
input := "```json\n{\"importance\": 0.9, \"salience\": 0.3, \"sector\": \"procedural\"}\n```"
result, err := parseScoringResponse(input)
require.NoError(t, err)
@ -95,6 +98,7 @@ func TestParseScoringResponse_WithCodeFences(t *testing.T) {
}
func TestParseScoringResponse_ClampsValues(t *testing.T) {
t.Parallel()
input := `{"importance": 1.5, "salience": -0.3, "sector": "episodic"}`
result, err := parseScoringResponse(input)
require.NoError(t, err)
@ -103,6 +107,7 @@ func TestParseScoringResponse_ClampsValues(t *testing.T) {
}
func TestParseScoringResponse_UnknownSector(t *testing.T) {
t.Parallel()
input := `{"importance": 0.5, "salience": 0.5, "sector": "unknown_sector"}`
result, err := parseScoringResponse(input)
require.NoError(t, err)
@ -110,6 +115,7 @@ func TestParseScoringResponse_UnknownSector(t *testing.T) {
}
func TestNormalizeSector(t *testing.T) {
t.Parallel()
tests := []struct {
input string
expected memory.Sector

View file

@ -11,6 +11,7 @@ import (
)
func TestCamelToSnake(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
@ -41,6 +42,7 @@ func TestCamelToSnake(t *testing.T) {
}
func TestConvertKeysToSnake(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"apiKey": "test-key",
"apiBase": "https://example.com",
@ -87,6 +89,7 @@ func TestConvertKeysToSnake(t *testing.T) {
}
func TestLoadOpenClawConfig(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
configPath := filepath.Join(tmpDir, "openclaw.json")
@ -144,6 +147,7 @@ func TestLoadOpenClawConfig(t *testing.T) {
}
func TestConvertConfig(t *testing.T) {
t.Parallel()
t.Run("providers mapping", func(t *testing.T) {
data := map[string]interface{}{
"providers": map[string]interface{}{
@ -301,6 +305,7 @@ func TestConvertConfig(t *testing.T) {
}
func TestMergeConfig(t *testing.T) {
t.Parallel()
t.Run("fills empty fields", func(t *testing.T) {
existing := config.DefaultConfig()
incoming := config.DefaultConfig()
@ -365,6 +370,7 @@ func TestMergeConfig(t *testing.T) {
}
func TestPlanWorkspaceMigration(t *testing.T) {
t.Parallel()
t.Run("copies available files", func(t *testing.T) {
srcDir := t.TempDir()
dstDir := t.TempDir()
@ -495,6 +501,7 @@ func TestPlanWorkspaceMigration(t *testing.T) {
}
func TestFindOpenClawConfig(t *testing.T) {
t.Parallel()
t.Run("finds openclaw.json", func(t *testing.T) {
tmpDir := t.TempDir()
configPath := filepath.Join(tmpDir, "openclaw.json")
@ -549,6 +556,7 @@ func TestFindOpenClawConfig(t *testing.T) {
}
func TestRewriteWorkspacePath(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
@ -569,6 +577,7 @@ func TestRewriteWorkspacePath(t *testing.T) {
}
func TestRunDryRun(t *testing.T) {
t.Parallel()
openclawHome := t.TempDir()
picoClawHome := t.TempDir()
@ -610,6 +619,7 @@ func TestRunDryRun(t *testing.T) {
}
func TestRunFullMigration(t *testing.T) {
t.Parallel()
openclawHome := t.TempDir()
picoClawHome := t.TempDir()
@ -708,6 +718,7 @@ func TestRunFullMigration(t *testing.T) {
}
func TestRunOpenClawNotFound(t *testing.T) {
t.Parallel()
opts := Options{
OpenClawHome: "/nonexistent/path/to/openclaw",
PicoClawHome: t.TempDir(),
@ -720,6 +731,7 @@ func TestRunOpenClawNotFound(t *testing.T) {
}
func TestRunMutuallyExclusiveFlags(t *testing.T) {
t.Parallel()
opts := Options{
ConfigOnly: true,
WorkspaceOnly: true,
@ -732,6 +744,7 @@ func TestRunMutuallyExclusiveFlags(t *testing.T) {
}
func TestBackupFile(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
filePath := filepath.Join(tmpDir, "test.md")
os.WriteFile(filePath, []byte("original content"), 0644)
@ -751,6 +764,7 @@ func TestBackupFile(t *testing.T) {
}
func TestCopyFile(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
srcPath := filepath.Join(tmpDir, "src.md")
dstPath := filepath.Join(tmpDir, "dst.md")
@ -771,6 +785,7 @@ func TestCopyFile(t *testing.T) {
}
func TestRunConfigOnly(t *testing.T) {
t.Parallel()
openclawHome := t.TempDir()
picoClawHome := t.TempDir()
@ -811,6 +826,7 @@ func TestRunConfigOnly(t *testing.T) {
}
func TestRunWorkspaceOnly(t *testing.T) {
t.Parallel()
openclawHome := t.TempDir()
picoClawHome := t.TempDir()

View file

@ -27,18 +27,22 @@ func errorModel(_ context.Context, _, _ string) (string, uint32, error) {
}
func TestEngine_SmallContext_AnswerDirectly(t *testing.T) {
t.Parallel()
cfg := rlm.DefaultEngineConfig()
cfg.Strategy.DirectThreshold = 10000 // larger than our test context
engine := rlm.NewEngine(cfg, nil, echoModel)
answer, tokens, err := engine.Answer(context.Background(), "sess", "what?", "short context")
answer, tokens, err := engine.Answer(t.Context(), "sess", "what?", "short context")
require.NoError(t, err)
assert.NotEmpty(t, answer)
assert.Greater(t, tokens, uint32(0))
}
func TestEngine_LargeContext_Partitions(t *testing.T) {
t.Parallel(
// Force partitioning by setting DirectThreshold very low.
)
cfg := rlm.DefaultEngineConfig()
cfg.Strategy.DirectThreshold = 10
cfg.Strategy.DefaultPartitionK = 2
@ -48,13 +52,14 @@ func TestEngine_LargeContext_Partitions(t *testing.T) {
largeCtx := strings.Repeat("hello world ", 100) // ~1200 bytes
engine := rlm.NewEngine(cfg, nil, echoModel)
answer, tokens, err := engine.Answer(context.Background(), "sess", "summarise", largeCtx)
answer, tokens, err := engine.Answer(t.Context(), "sess", "summarise", largeCtx)
require.NoError(t, err)
assert.NotEmpty(t, answer)
assert.Greater(t, tokens, uint32(0))
}
func TestEngine_MaxDepthTerminates(t *testing.T) {
t.Parallel()
cfg := rlm.DefaultEngineConfig()
cfg.Strategy.DirectThreshold = 0 // always partition
cfg.Strategy.MaxDepth = 3
@ -64,20 +69,22 @@ func TestEngine_MaxDepthTerminates(t *testing.T) {
engine := rlm.NewEngine(cfg, nil, echoModel)
// Should terminate without stack overflow.
_, _, err := engine.Answer(context.Background(), "sess", "any", content)
_, _, err := engine.Answer(t.Context(), "sess", "any", content)
assert.NoError(t, err)
}
func TestEngine_ModelError_Propagates(t *testing.T) {
t.Parallel()
cfg := rlm.DefaultEngineConfig()
cfg.Strategy.DirectThreshold = 10000
engine := rlm.NewEngine(cfg, nil, errorModel)
_, _, err := engine.Answer(context.Background(), "sess", "q", "ctx")
_, _, err := engine.Answer(t.Context(), "sess", "q", "ctx")
assert.Error(t, err)
}
func TestEngine_GrepQuery_NarrowsContext(t *testing.T) {
t.Parallel()
cfg := rlm.DefaultEngineConfig()
cfg.Strategy.DirectThreshold = 10
cfg.Strategy.DefaultPartitionK = 2
@ -88,14 +95,15 @@ func TestEngine_GrepQuery_NarrowsContext(t *testing.T) {
content := "foo\nfunc myFunction() {}\nbar\nbaz"
engine := rlm.NewEngine(cfg, nil, echoModel)
answer, _, err := engine.Answer(context.Background(), "sess", "find func definition", content)
answer, _, err := engine.Answer(t.Context(), "sess", "find func definition", content)
require.NoError(t, err)
assert.NotEmpty(t, answer)
}
func TestFanOut_AllPartitions_Processed(t *testing.T) {
t.Parallel()
partitions := []string{"A", "B", "C", "D"}
results := rlm.FanOut(context.Background(), partitions, 2,
results := rlm.FanOut(t.Context(), partitions, 2,
func(_ context.Context, idx int, key, part string) rlm.PartitionResult {
return rlm.PartitionResult{PartitionIdx: idx, ContextKey: key, Answer: "ans-" + part, Tokens: 1}
})
@ -108,11 +116,12 @@ func TestFanOut_AllPartitions_Processed(t *testing.T) {
}
func TestFanOut_UnboundedConcurrency(t *testing.T) {
t.Parallel()
parts := make([]string, 20)
for i := range parts {
parts[i] = fmt.Sprintf("part-%d", i)
}
results := rlm.FanOut(context.Background(), parts, 0,
results := rlm.FanOut(t.Context(), parts, 0,
func(_ context.Context, idx int, key, part string) rlm.PartitionResult {
return rlm.PartitionResult{PartitionIdx: idx, Answer: part, Tokens: 2}
})
@ -121,7 +130,8 @@ func TestFanOut_UnboundedConcurrency(t *testing.T) {
}
func TestFanOut_Empty(t *testing.T) {
results := rlm.FanOut(context.Background(), nil, 4,
t.Parallel()
results := rlm.FanOut(t.Context(), nil, 4,
func(_ context.Context, _ int, _, _ string) rlm.PartitionResult {
return rlm.PartitionResult{}
})
@ -129,6 +139,7 @@ func TestFanOut_Empty(t *testing.T) {
}
func TestMergeResults_Deduplication(t *testing.T) {
t.Parallel()
results := []rlm.PartitionResult{
{Answer: "alpha"},
{Answer: "beta"},
@ -140,6 +151,7 @@ func TestMergeResults_Deduplication(t *testing.T) {
}
func TestMergeResults_WithErrors(t *testing.T) {
t.Parallel()
results := []rlm.PartitionResult{
{Answer: "good"},
{Err: fmt.Errorf("failed"), Answer: "should be skipped"},
@ -149,6 +161,7 @@ func TestMergeResults_WithErrors(t *testing.T) {
}
func TestStrategyPlanner_SmallContext_OpFinal(t *testing.T) {
t.Parallel()
cfg := rlm.DefaultStrategyConfig()
cfg.DirectThreshold = 1000
planner := rlm.NewStrategyPlanner(cfg)
@ -158,6 +171,7 @@ func TestStrategyPlanner_SmallContext_OpFinal(t *testing.T) {
}
func TestStrategyPlanner_MaxDepth_OpFinal(t *testing.T) {
t.Parallel()
cfg := rlm.DefaultStrategyConfig()
cfg.MaxDepth = 3
planner := rlm.NewStrategyPlanner(cfg)
@ -167,6 +181,7 @@ func TestStrategyPlanner_MaxDepth_OpFinal(t *testing.T) {
}
func TestStrategyPlanner_KeywordQuery_OpGrep(t *testing.T) {
t.Parallel()
planner := rlm.NewStrategyPlanner(rlm.DefaultStrategyConfig())
// "find" prefix should trigger grep.
@ -176,6 +191,7 @@ func TestStrategyPlanner_KeywordQuery_OpGrep(t *testing.T) {
}
func TestStrategyPlanner_LargeContext_OpPartition(t *testing.T) {
t.Parallel()
planner := rlm.NewStrategyPlanner(rlm.DefaultStrategyConfig())
op := planner.PlanNext(100000, "summarise everything", 0)

View file

@ -12,14 +12,16 @@ import (
)
func TestFanOutEmpty(t *testing.T) {
results := FanOut(context.Background(), nil, 0, nil)
t.Parallel()
results := FanOut(t.Context(), nil, 0, nil)
assert.Nil(t, results)
}
func TestFanOutUnbounded(t *testing.T) {
t.Parallel()
partitions := []string{"part-0", "part-1", "part-2"}
results := FanOut(context.Background(), partitions, 0, func(ctx context.Context, idx int, key, partition string) PartitionResult {
results := FanOut(t.Context(), partitions, 0, func(ctx context.Context, idx int, key, partition string) PartitionResult {
return PartitionResult{
PartitionIdx: idx,
ContextKey: key,
@ -37,6 +39,7 @@ func TestFanOutUnbounded(t *testing.T) {
}
func TestFanOutBounded(t *testing.T) {
t.Parallel()
partitions := make([]string, 10)
for i := range partitions {
partitions[i] = fmt.Sprintf("chunk-%d", i)
@ -45,7 +48,7 @@ func TestFanOutBounded(t *testing.T) {
var maxConcurrent int64
var current int64
results := FanOut(context.Background(), partitions, 3, func(ctx context.Context, idx int, key, partition string) PartitionResult {
results := FanOut(t.Context(), partitions, 3, func(ctx context.Context, idx int, key, partition string) PartitionResult {
c := atomic.AddInt64(&current, 1)
for {
old := atomic.LoadInt64(&maxConcurrent)
@ -71,9 +74,10 @@ func TestFanOutBounded(t *testing.T) {
}
func TestFanOutPreservesOrder(t *testing.T) {
t.Parallel()
partitions := []string{"A", "B", "C", "D"}
results := FanOut(context.Background(), partitions, 2, func(ctx context.Context, idx int, key, partition string) PartitionResult {
results := FanOut(t.Context(), partitions, 2, func(ctx context.Context, idx int, key, partition string) PartitionResult {
return PartitionResult{
PartitionIdx: idx,
Answer: partition,
@ -88,6 +92,7 @@ func TestFanOutPreservesOrder(t *testing.T) {
}
func TestMergeResultsDeduplication(t *testing.T) {
t.Parallel()
results := []PartitionResult{
{Answer: " answer one "},
{Answer: "answer one"},
@ -101,6 +106,7 @@ func TestMergeResultsDeduplication(t *testing.T) {
}
func TestMergeResultsAllErrors(t *testing.T) {
t.Parallel()
results := []PartitionResult{
{Err: fmt.Errorf("e1")},
{Err: fmt.Errorf("e2")},
@ -109,6 +115,7 @@ func TestMergeResultsAllErrors(t *testing.T) {
}
func TestMergeResultsAllEmpty(t *testing.T) {
t.Parallel()
results := []PartitionResult{
{Answer: ""},
{Answer: " "},
@ -117,6 +124,7 @@ func TestMergeResultsAllEmpty(t *testing.T) {
}
func TestTotalTokens(t *testing.T) {
t.Parallel()
results := []PartitionResult{
{Tokens: 100},
{Tokens: 250},
@ -126,5 +134,6 @@ func TestTotalTokens(t *testing.T) {
}
func TestTotalTokensEmpty(t *testing.T) {
t.Parallel()
assert.Equal(t, uint32(0), TotalTokens(nil))
}

View file

@ -10,12 +10,14 @@ import (
)
func TestRope_EmptyRope(t *testing.T) {
t.Parallel()
r := rlm.NewRope("")
assert.Equal(t, 0, r.Len())
assert.Equal(t, "", r.String())
}
func TestRope_BasicAppendAndString(t *testing.T) {
t.Parallel()
r := rlm.NewRope("hello")
r.Append(" world")
assert.Equal(t, 11, r.Len())
@ -23,6 +25,7 @@ func TestRope_BasicAppendAndString(t *testing.T) {
}
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())
@ -30,6 +33,7 @@ func TestRope_LargeContent(t *testing.T) {
}
func TestRope_Slice_ValidRange(t *testing.T) {
t.Parallel()
r := rlm.NewRope("hello world")
s, err := r.Slice(6, 11)
require.NoError(t, err)
@ -37,6 +41,7 @@ func TestRope_Slice_ValidRange(t *testing.T) {
}
func TestRope_Slice_ZeroLength(t *testing.T) {
t.Parallel()
r := rlm.NewRope("hello")
s, err := r.Slice(2, 2)
require.NoError(t, err)
@ -44,12 +49,14 @@ func TestRope_Slice_ZeroLength(t *testing.T) {
}
func TestRope_Slice_OutOfRange(t *testing.T) {
t.Parallel()
r := rlm.NewRope("hello")
_, err := r.Slice(3, 10)
assert.Error(t, err)
}
func TestRope_Slice_AcrossAppendBoundary(t *testing.T) {
t.Parallel()
r := rlm.NewRope("hello")
r.Append(" world")
s, err := r.Slice(3, 8)
@ -58,12 +65,14 @@ func TestRope_Slice_AcrossAppendBoundary(t *testing.T) {
}
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)
}
func TestRope_GrepLines_CaseSensitive(t *testing.T) {
t.Parallel()
r := rlm.NewRope("apple\nBanana\napricot\ncherry")
matches := r.GrepLines("ap", 0, false)
require.Len(t, matches, 2)
@ -72,6 +81,7 @@ func TestRope_GrepLines_CaseSensitive(t *testing.T) {
}
func TestRope_GrepLines_CaseInsensitive(t *testing.T) {
t.Parallel()
r := rlm.NewRope("Apple\nbanana\nAPRICOT")
matches := r.GrepLines("apple", 0, true)
require.Len(t, matches, 1)
@ -79,18 +89,21 @@ func TestRope_GrepLines_CaseInsensitive(t *testing.T) {
}
func TestRope_GrepLines_MaxMatches(t *testing.T) {
t.Parallel()
r := rlm.NewRope("aa\naa\naa\naa\naa")
matches := r.GrepLines("aa", 3, false)
assert.Len(t, matches, 3)
}
func TestRope_GrepLines_NoMatches(t *testing.T) {
t.Parallel()
r := rlm.NewRope("hello world")
matches := r.GrepLines("xyz", 0, false)
assert.Empty(t, matches)
}
func TestRope_Partition_Even(t *testing.T) {
t.Parallel()
r := rlm.NewRope("12345678")
parts := r.Partition(4)
assert.Len(t, parts, 4)
@ -98,6 +111,7 @@ func TestRope_Partition_Even(t *testing.T) {
}
func TestRope_Partition_MoreThanContent(t *testing.T) {
t.Parallel()
r := rlm.NewRope("hi")
parts := r.Partition(10)
assert.Len(t, parts, 10)
@ -107,6 +121,7 @@ func TestRope_Partition_MoreThanContent(t *testing.T) {
}
func TestRope_Partition_Empty(t *testing.T) {
t.Parallel()
r := rlm.NewRope("")
parts := r.Partition(4)
assert.Len(t, parts, 4)
@ -116,7 +131,10 @@ func TestRope_Partition_Empty(t *testing.T) {
}
func TestRope_RuneLen(t *testing.T) {
t.Parallel(
// Multi-byte Unicode characters.
)
r := rlm.NewRope("héllo") // 'é' is 2 bytes
assert.Equal(t, 5, r.RuneLen())
assert.Equal(t, 6, r.Len()) // bytes

View file

@ -7,6 +7,7 @@ import (
)
func TestStrategyPlanNextFinalAtMaxDepth(t *testing.T) {
t.Parallel()
sp := NewStrategyPlanner(StrategyConfig{
DirectThreshold: 8192,
DefaultPartitionK: 4,
@ -18,6 +19,7 @@ func TestStrategyPlanNextFinalAtMaxDepth(t *testing.T) {
}
func TestStrategyPlanNextFinalSmallContext(t *testing.T) {
t.Parallel()
sp := NewStrategyPlanner(DefaultStrategyConfig())
op := sp.PlanNext(1000, "any query", 0)
@ -25,6 +27,7 @@ func TestStrategyPlanNextFinalSmallContext(t *testing.T) {
}
func TestStrategyPlanNextGrepForKeywordQuery(t *testing.T) {
t.Parallel()
sp := NewStrategyPlanner(DefaultStrategyConfig())
tests := []struct {
@ -47,6 +50,7 @@ func TestStrategyPlanNextGrepForKeywordQuery(t *testing.T) {
}
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)
@ -54,6 +58,7 @@ func TestStrategyPlanNextPartitionDefault(t *testing.T) {
}
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)
@ -61,18 +66,22 @@ func TestStrategyPlanNextPartitionLargeContext(t *testing.T) {
}
func TestExtractKeywordQuoted(t *testing.T) {
t.Parallel()
assert.Equal(t, "handleRequest", extractKeyword(`find "handleRequest" in the codebase`))
}
func TestExtractKeywordNoQuotes(t *testing.T) {
t.Parallel()
assert.Equal(t, "find", extractKeyword("find the main function"))
}
func TestExtractKeywordEmpty(t *testing.T) {
t.Parallel()
assert.Equal(t, "", extractKeyword(""))
}
func TestLooksLikeKeywordQuery(t *testing.T) {
t.Parallel()
assert.True(t, looksLikeKeywordQuery(`find "something"`))
assert.True(t, looksLikeKeywordQuery("error: something broke"))
assert.True(t, looksLikeKeywordQuery("func processData"))

View file

@ -44,6 +44,7 @@ func TestResolveBaseConfigPath_PrefersXDGOverLegacy(t *testing.T) {
}
func TestResolveBaseConfigPath_FallsBackToLegacyWhenXDGMissing(t *testing.T) {
t.Parallel()
home := t.TempDir()
xdg := t.TempDir()
t.Setenv("HOME", home)
@ -58,6 +59,7 @@ func TestResolveBaseConfigPath_FallsBackToLegacyWhenXDGMissing(t *testing.T) {
}
func TestLoadResolvedConfig_AppliesOverlayAndKeepsBaseValues(t *testing.T) {
t.Parallel()
dir := t.TempDir()
basePath := filepath.Join(dir, "base.json")
overlayPath := filepath.Join(dir, "overlay.json")
@ -82,6 +84,7 @@ func TestLoadResolvedConfig_AppliesOverlayAndKeepsBaseValues(t *testing.T) {
}
func TestEnsureMinProviderTimeout_SetsFloor(t *testing.T) {
t.Parallel()
dir := t.TempDir()
basePath := filepath.Join(dir, "base.json")
require.NoError(t, os.WriteFile(basePath, []byte(`{"providers":{"openai":{"timeout":0}}}`), 0o644))
@ -95,10 +98,11 @@ func TestEnsureMinProviderTimeout_SetsFloor(t *testing.T) {
}
func TestStartOutbound_DropAndConsumeDoNotBlockPublishers(t *testing.T) {
t.Parallel()
modes := []OutboundMode{OutboundModeDrop, OutboundModeConsume}
for _, mode := range modes {
t.Run(string(mode), func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
msgBus := bus.NewMessageBus()
@ -127,7 +131,8 @@ func TestStartOutbound_DropAndConsumeDoNotBlockPublishers(t *testing.T) {
}
func TestStartOutbound_CallbackReceivesMessages(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
t.Parallel()
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
msgBus := bus.NewMessageBus()
@ -158,6 +163,7 @@ func TestStartOutbound_CallbackReceivesMessages(t *testing.T) {
}
func TestValidateKernelInvariants(t *testing.T) {
t.Parallel()
t.Run("nil loop", func(t *testing.T) {
err := validateKernelInvariants(nil)
require.Error(t, err)

View file

@ -9,6 +9,7 @@ import (
)
func TestExtractJSON_RawJSON(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
@ -30,6 +31,7 @@ func TestExtractJSON_RawJSON(t *testing.T) {
}
func TestExtractJSON_CodeFence(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
@ -58,6 +60,7 @@ func TestExtractJSON_CodeFence(t *testing.T) {
}
func TestExtractJSON_EmbeddedInProse(t *testing.T) {
t.Parallel()
input := `Based on my analysis, the result is {"importance": 0.9, "sector": "semantic"} which indicates high relevance.`
var result struct {
Importance float64 `json:"importance"`
@ -70,6 +73,7 @@ func TestExtractJSON_EmbeddedInProse(t *testing.T) {
}
func TestExtractJSON_NestedBracesInStrings(t *testing.T) {
t.Parallel()
input := `{"content": "function() { return {}; }", "count": 1}`
var result map[string]interface{}
err := ExtractJSON(input, &result, nil)
@ -79,6 +83,7 @@ func TestExtractJSON_NestedBracesInStrings(t *testing.T) {
}
func TestExtractJSON_InjectionAttempts(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
@ -131,12 +136,14 @@ func TestExtractJSON_InjectionAttempts(t *testing.T) {
}
func TestExtractJSON_EmptyInput(t *testing.T) {
t.Parallel()
var result map[string]interface{}
err := ExtractJSON("", &result, nil)
assert.ErrorIs(t, err, ErrNoJSON)
}
func TestExtractJSON_MultipleFences_TakesFirst(t *testing.T) {
t.Parallel()
input := "```json\n{\"first\": true}\n```\nmore text\n```json\n{\"second\": true}\n```"
var result map[string]interface{}
err := ExtractJSON(input, &result, nil)
@ -147,6 +154,7 @@ func TestExtractJSON_MultipleFences_TakesFirst(t *testing.T) {
}
func TestSanitizeToolArgs_ValidInput(t *testing.T) {
t.Parallel()
schema := map[string]ArgSpec{
"path": {Type: ArgString, Required: true, MaxLength: 256},
"content": {Type: ArgString, Required: true},
@ -166,6 +174,7 @@ func TestSanitizeToolArgs_ValidInput(t *testing.T) {
}
func TestSanitizeToolArgs_MissingRequired(t *testing.T) {
t.Parallel()
schema := map[string]ArgSpec{
"path": {Type: ArgString, Required: true},
}
@ -176,6 +185,7 @@ func TestSanitizeToolArgs_MissingRequired(t *testing.T) {
}
func TestSanitizeToolArgs_ExceedsMaxLength(t *testing.T) {
t.Parallel()
schema := map[string]ArgSpec{
"cmd": {Type: ArgString, Required: true, MaxLength: 10},
}
@ -186,6 +196,7 @@ func TestSanitizeToolArgs_ExceedsMaxLength(t *testing.T) {
}
func TestSanitizeToolArgs_TypeCoercion(t *testing.T) {
t.Parallel()
tests := []struct {
name string
argType ArgType
@ -220,12 +231,14 @@ func TestSanitizeToolArgs_TypeCoercion(t *testing.T) {
}
func TestExtractFirstBraced_EscapedQuotes(t *testing.T) {
t.Parallel()
input := `{"msg": "say \"hello\" world"}`
result := extractFirstBraced(input)
assert.Equal(t, input, result)
}
func TestExtractFirstBraced_UnbalancedBraces(t *testing.T) {
t.Parallel()
input := `text { not closed`
result := extractFirstBraced(input)
assert.Empty(t, result)

View file

@ -7,6 +7,7 @@ import (
)
func TestRedactor_APIKeys(t *testing.T) {
t.Parallel()
r := NewRedactor()
tests := []struct {
name string
@ -30,6 +31,7 @@ func TestRedactor_APIKeys(t *testing.T) {
}
func TestRedactor_Bearer(t *testing.T) {
t.Parallel()
r := NewRedactor()
input := "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxxxx"
out := r.Redact(input)
@ -37,6 +39,7 @@ func TestRedactor_Bearer(t *testing.T) {
}
func TestRedactor_JWT(t *testing.T) {
t.Parallel()
r := NewRedactor()
jwt := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"
out := r.Redact(jwt)
@ -44,6 +47,7 @@ func TestRedactor_JWT(t *testing.T) {
}
func TestRedactor_PII(t *testing.T) {
t.Parallel()
r := NewRedactor()
tests := []struct {
name string
@ -64,6 +68,7 @@ func TestRedactor_PII(t *testing.T) {
}
func TestRedactor_SecretValues(t *testing.T) {
t.Parallel()
r := NewRedactor()
tests := []struct {
name string
@ -84,6 +89,7 @@ func TestRedactor_SecretValues(t *testing.T) {
}
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))
@ -91,6 +97,7 @@ func TestRedactor_SafeText(t *testing.T) {
}
func TestRedactor_RedactMap(t *testing.T) {
t.Parallel()
r := NewRedactor()
m := map[string]interface{}{
"command": "curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxxxxxxxxxxxxxxxxxxx.yyyyyyyy'",
@ -109,6 +116,7 @@ func TestRedactor_RedactMap(t *testing.T) {
}
func TestMaskKey(t *testing.T) {
t.Parallel()
tests := []struct {
input string
want string

View file

@ -11,6 +11,7 @@ import (
)
func TestAuditLogAppendAndRetrieve(t *testing.T) {
t.Parallel()
al := NewAuditLog()
event := AuditEvent{
@ -32,6 +33,7 @@ func TestAuditLogAppendAndRetrieve(t *testing.T) {
}
func TestAuditLogConcurrentAppend(t *testing.T) {
t.Parallel()
al := NewAuditLog()
n := 100
@ -52,6 +54,7 @@ func TestAuditLogConcurrentAppend(t *testing.T) {
}
func TestAuditLogFilterBySession(t *testing.T) {
t.Parallel()
al := NewAuditLog()
_ = al.Append(AuditEvent{RequestID: "r1", SessionKey: "sess-A"})
@ -69,6 +72,7 @@ func TestAuditLogFilterBySession(t *testing.T) {
}
func TestAuditLogLeakEvents(t *testing.T) {
t.Parallel()
al := NewAuditLog()
_ = al.Append(AuditEvent{RequestID: "r1", LeakDetected: false})
@ -98,6 +102,7 @@ func (ms *mockSink) Write(event AuditEvent) error {
}
func TestAuditLogSinkIntegration(t *testing.T) {
t.Parallel()
sink := &mockSink{}
al := NewAuditLog(sink)
@ -109,6 +114,7 @@ func TestAuditLogSinkIntegration(t *testing.T) {
}
func TestAuditLogSinkError(t *testing.T) {
t.Parallel()
sink := &mockSink{failAt: 1}
al := NewAuditLog(sink)
@ -121,6 +127,7 @@ func TestAuditLogSinkError(t *testing.T) {
}
func TestAuditLogEventsImmutable(t *testing.T) {
t.Parallel()
al := NewAuditLog()
_ = al.Append(AuditEvent{RequestID: "r1"})

View file

@ -2,9 +2,10 @@ package securebus_test
import (
"context"
jsonv2 "github.com/go-json-experiment/json"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
"github.com/ZanzyTHEbar/dragonscale/pkg/security"
"github.com/ZanzyTHEbar/dragonscale/pkg/security/securebus"
@ -76,12 +77,13 @@ func makeBus(t *testing.T, toolMap map[string]tools.Tool, secrets *security.Secr
// ── tests ─────────────────────────────────────────────────────────────────────
func TestBus_SuccessfulToolExec(t *testing.T) {
t.Parallel()
tool := &staticTool{name: "greet", result: "hello world"}
bus := makeBus(t, map[string]tools.Tool{"greet": tool}, nil)
defer bus.Close()
req := itr.NewToolExecRequest("req-1", "sess", "tc-1", "greet", makeArgsJSON(nil))
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.False(t, resp.IsError)
assert.Equal(t, "hello world", resp.Result)
@ -89,22 +91,24 @@ func TestBus_SuccessfulToolExec(t *testing.T) {
}
func TestBus_UnknownTool(t *testing.T) {
t.Parallel()
bus := makeBus(t, map[string]tools.Tool{}, nil)
defer bus.Close()
req := itr.NewToolExecRequest("req-2", "sess", "tc-2", "nonexistent", makeArgsJSON(nil))
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.True(t, resp.IsError)
}
func TestBus_ToolReturnsError(t *testing.T) {
t.Parallel()
tool := &staticTool{name: "fail", result: "something broke", isErr: true}
bus := makeBus(t, map[string]tools.Tool{"fail": tool}, nil)
defer bus.Close()
req := itr.NewToolExecRequest("req-3", "sess", "tc-3", "fail", makeArgsJSON(nil))
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.True(t, resp.IsError)
assert.Equal(t, 1, bus.AuditLog().Len())
@ -113,14 +117,17 @@ func TestBus_ToolReturnsError(t *testing.T) {
}
func TestBus_LeakDetection(t *testing.T) {
t.Parallel(
// Tool output contains an API key — should be redacted.
)
apiKey := "AKIAIOSFODNN7EXAMPLE" // fake AWS key matching redactor pattern
tool := &staticTool{name: "leaky", result: "result: " + apiKey}
bus := makeBus(t, map[string]tools.Tool{"leaky": tool}, nil)
defer bus.Close()
req := itr.NewToolExecRequest("req-4", "sess", "tc-4", "leaky", makeArgsJSON(nil))
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.True(t, resp.LeakDetected, "should detect API key in output")
assert.NotContains(t, resp.Result, apiKey, "raw API key must not appear in response")
@ -130,7 +137,10 @@ func TestBus_LeakDetection(t *testing.T) {
}
func TestBus_SecretInjection_ArgVariant(t *testing.T) {
t.Parallel(
// Tool reads injected "token" arg from args map.
)
echoT := &echoTool{}
// Give echo tool a capability that declares a secret injected as arg:input.
@ -174,7 +184,7 @@ func TestBus_SecretInjection_ArgVariant(t *testing.T) {
defer bus.Close()
req := itr.NewToolExecRequest("req-5", "sess", "tc-5", "echo", makeArgsJSON(nil))
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.False(t, resp.IsError)
assert.Equal(t, "supersecret", resp.Result, "injected secret should appear in tool output")
@ -185,6 +195,7 @@ func TestBus_SecretInjection_ArgVariant(t *testing.T) {
}
func TestBus_PolicyViolation_RecursionDepth(t *testing.T) {
t.Parallel()
tool := &staticTool{name: "ok", result: "fine"}
bus := makeBus(t, map[string]tools.Tool{"ok": tool}, nil)
defer bus.Close()
@ -192,7 +203,7 @@ func TestBus_PolicyViolation_RecursionDepth(t *testing.T) {
req := itr.NewToolExecRequest("req-6", "sess", "tc-6", "ok", makeArgsJSON(nil))
req.Depth = 255 // far exceeds MaxRecursionDepth=10
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.True(t, resp.IsError, "depth violation should produce an error response")
events := bus.AuditLog().Events()
@ -201,13 +212,14 @@ func TestBus_PolicyViolation_RecursionDepth(t *testing.T) {
}
func TestBus_AuditLog_FilterBySession(t *testing.T) {
t.Parallel()
tool := &staticTool{name: "t", result: "ok"}
bus := makeBus(t, map[string]tools.Tool{"t": tool}, nil)
defer bus.Close()
for _, sk := range []string{"session-A", "session-A", "session-B"} {
req := itr.NewToolExecRequest("req-audit-"+sk, sk, "tc", "t", makeArgsJSON(nil))
bus.Execute(context.Background(), req)
bus.Execute(t.Context(), req)
}
assert.Equal(t, 3, bus.AuditLog().Len())
@ -216,40 +228,44 @@ func TestBus_AuditLog_FilterBySession(t *testing.T) {
}
func TestBus_Transport_Send(t *testing.T) {
t.Parallel()
tool := &staticTool{name: "ping", result: "pong"}
bus := makeBus(t, map[string]tools.Tool{"ping": tool}, nil)
defer bus.Close()
req := itr.NewToolExecRequest("req-tr", "sess", "tc", "ping", makeArgsJSON(nil))
resp, err := bus.Transport().Send(context.Background(), req)
resp, err := bus.Transport().Send(t.Context(), req)
require.NoError(t, err)
assert.Equal(t, "pong", resp.Result)
}
func TestBus_InvalidArgsJSON(t *testing.T) {
t.Parallel()
tool := &staticTool{name: "ok", result: "ok"}
bus := makeBus(t, map[string]tools.Tool{"ok": tool}, nil)
defer bus.Close()
req := itr.NewToolExecRequest("req-bad", "sess", "tc", "ok", "{invalid json")
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.True(t, resp.IsError)
}
func TestBus_RLMFinalCommand(t *testing.T) {
t.Parallel()
bus := makeBus(t, nil, nil)
defer bus.Close()
req := itr.NewFinalRequest("req-final", "sess", 0, "the answer", "")
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.False(t, resp.IsError)
assert.Equal(t, "the answer", resp.Result)
}
func TestBus_CloseIdempotent(t *testing.T) {
t.Parallel()
bus := makeBus(t, nil, nil)
assert.NotPanics(t, func() {
@ -260,6 +276,7 @@ func TestBus_CloseIdempotent(t *testing.T) {
}
func TestBus_ToolSearch(t *testing.T) {
t.Parallel()
bus := makeBus(t, nil, nil)
defer bus.Close()
@ -268,24 +285,26 @@ func TestBus_ToolSearch(t *testing.T) {
})
req := itr.NewToolSearchRequest("req-search", "sess", "file operations", 5)
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.False(t, resp.IsError)
assert.Contains(t, resp.Result, "read_file")
}
func TestBus_ToolSearchNotConfigured(t *testing.T) {
t.Parallel()
bus := makeBus(t, nil, nil)
defer bus.Close()
req := itr.NewToolSearchRequest("req-search2", "sess", "anything", 5)
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.True(t, resp.IsError)
assert.Contains(t, resp.Result, "not configured")
}
func TestBus_NilToolResult(t *testing.T) {
t.Parallel()
executor := func(ctx context.Context, name string, args map[string]interface{}) *tools.ToolResult {
return nil
}
@ -296,7 +315,7 @@ func TestBus_NilToolResult(t *testing.T) {
defer bus.Close()
req := itr.NewToolExecRequest("req-nil", "sess", "tc", "something", "{}")
resp := bus.Execute(context.Background(), req)
resp := bus.Execute(t.Context(), req)
assert.False(t, resp.IsError)
assert.Empty(t, resp.Result)

View file

@ -10,6 +10,7 @@ import (
)
func TestPolicyValidateRecursionDepth(t *testing.T) {
t.Parallel()
pe := NewPolicyEngine(PolicyConfig{MaxRecursionDepth: 5})
req := itr.ToolRequest{Depth: 3}
@ -22,6 +23,7 @@ func TestPolicyValidateRecursionDepth(t *testing.T) {
}
func TestPolicyValidateNoDepthLimit(t *testing.T) {
t.Parallel()
pe := NewPolicyEngine(PolicyConfig{MaxRecursionDepth: 0})
req := itr.ToolRequest{Depth: 255}
@ -29,6 +31,7 @@ func TestPolicyValidateNoDepthLimit(t *testing.T) {
}
func TestPolicyValidateNetworkSSRFBlocked(t *testing.T) {
t.Parallel()
pe := NewPolicyEngine(DefaultPolicyConfig())
ssrfURLs := []string{
@ -53,6 +56,7 @@ func TestPolicyValidateNetworkSSRFBlocked(t *testing.T) {
}
func TestPolicyValidateNetworkAllowed(t *testing.T) {
t.Parallel()
pe := NewPolicyEngine(DefaultPolicyConfig())
rules := []tools.EndpointRule{{Pattern: "https://api.github.com/*"}}
@ -60,6 +64,7 @@ func TestPolicyValidateNetworkAllowed(t *testing.T) {
}
func TestPolicyValidateNetworkNoRules(t *testing.T) {
t.Parallel()
pe := NewPolicyEngine(DefaultPolicyConfig())
err := pe.ValidateNetwork("https://example.com", nil)
require.Error(t, err)
@ -67,6 +72,7 @@ func TestPolicyValidateNetworkNoRules(t *testing.T) {
}
func TestPolicyValidateNetworkNoMatchingRule(t *testing.T) {
t.Parallel()
pe := NewPolicyEngine(DefaultPolicyConfig())
rules := []tools.EndpointRule{{Pattern: "https://api.github.com/*"}}
@ -76,6 +82,7 @@ func TestPolicyValidateNetworkNoMatchingRule(t *testing.T) {
}
func TestPolicyValidateFilesystemAllowed(t *testing.T) {
t.Parallel()
pe := NewPolicyEngine(PolicyConfig{AllowedWorkspace: "/workspace"})
rules := []tools.PathRule{{Pattern: "src/*", Mode: "rw"}}
@ -85,6 +92,7 @@ func TestPolicyValidateFilesystemAllowed(t *testing.T) {
}
func TestPolicyValidateFilesystemNoRules(t *testing.T) {
t.Parallel()
pe := NewPolicyEngine(PolicyConfig{})
err := pe.ValidateFilesystem("/etc/passwd", "r", nil)
require.Error(t, err)
@ -92,6 +100,7 @@ func TestPolicyValidateFilesystemNoRules(t *testing.T) {
}
func TestPolicyValidateFilesystemModeMismatch(t *testing.T) {
t.Parallel()
pe := NewPolicyEngine(PolicyConfig{AllowedWorkspace: "/workspace"})
rules := []tools.PathRule{{Pattern: "data/*", Mode: "r"}}

View file

@ -14,6 +14,7 @@ import (
)
func TestSocketTransportRoundTrip(t *testing.T) {
t.Parallel()
sockPath := filepath.Join(t.TempDir(), "test.sock")
server, err := NewSocketTransportServer(sockPath)
@ -47,7 +48,7 @@ func TestSocketTransportRoundTrip(t *testing.T) {
Timestamp: time.Now().UnixNano(),
}
resp, err := client.Send(context.Background(), req)
resp, err := client.Send(t.Context(), req)
require.NoError(t, err)
assert.Equal(t, "req-001", resp.ID)
assert.Contains(t, resp.Result, "req-001")
@ -57,6 +58,7 @@ func TestSocketTransportRoundTrip(t *testing.T) {
}
func TestSocketTransportMultipleRequests(t *testing.T) {
t.Parallel()
sockPath := filepath.Join(t.TempDir(), "multi.sock")
server, err := NewSocketTransportServer(sockPath)
@ -82,13 +84,14 @@ func TestSocketTransportMultipleRequests(t *testing.T) {
"echo",
`{}`,
)
resp, err := client.Send(context.Background(), req)
resp, err := client.Send(t.Context(), req)
require.NoError(t, err)
assert.Equal(t, req.ID, resp.ID)
}
}
func TestSocketTransportCleanup(t *testing.T) {
t.Parallel()
sockPath := filepath.Join(t.TempDir(), "cleanup.sock")
server, err := NewSocketTransportServer(sockPath)
@ -104,6 +107,7 @@ func TestSocketTransportCleanup(t *testing.T) {
}
func TestSocketTransportClientSendOnClosed(t *testing.T) {
t.Parallel()
sockPath := filepath.Join(t.TempDir(), "closed.sock")
server, err := NewSocketTransportServer(sockPath)
@ -120,7 +124,7 @@ func TestSocketTransportClientSendOnClosed(t *testing.T) {
client.Close()
_, err = client.Send(context.Background(), itr.ToolRequest{ID: "fail"})
_, err = client.Send(t.Context(), itr.ToolRequest{ID: "fail"})
assert.Error(t, err)
server.Close()

View file

@ -8,6 +8,7 @@ import (
)
func TestValidateURL_AllowedURLs(t *testing.T) {
t.Parallel()
tests := []string{
"https://example.com",
"https://api.openai.com/v1/chat",
@ -23,6 +24,7 @@ func TestValidateURL_AllowedURLs(t *testing.T) {
}
func TestValidateURL_BlockedSchemes(t *testing.T) {
t.Parallel()
tests := []string{
"file:///etc/passwd",
"ftp://internal.server/data",
@ -38,6 +40,7 @@ func TestValidateURL_BlockedSchemes(t *testing.T) {
}
func TestValidateURL_BlockedHosts(t *testing.T) {
t.Parallel()
tests := []struct {
name string
url string
@ -57,6 +60,7 @@ func TestValidateURL_BlockedHosts(t *testing.T) {
}
func TestValidateURL_BlockedIPs(t *testing.T) {
t.Parallel()
tests := []struct {
name string
url string
@ -78,6 +82,7 @@ func TestValidateURL_BlockedIPs(t *testing.T) {
}
func TestValidateURL_EmptyAndInvalid(t *testing.T) {
t.Parallel()
tests := []struct {
name string
url string
@ -95,6 +100,7 @@ func TestValidateURL_EmptyAndInvalid(t *testing.T) {
}
func TestIsBlockedIP(t *testing.T) {
t.Parallel()
tests := []struct {
ip string
blocked bool

View file

@ -8,6 +8,7 @@ import (
)
func TestVault_RoundTrip(t *testing.T) {
t.Parallel()
key, err := GenerateKey()
require.NoError(t, err)
require.Len(t, key, 32)
@ -35,6 +36,7 @@ func TestVault_RoundTrip(t *testing.T) {
}
func TestVault_DifferentCiphertexts(t *testing.T) {
t.Parallel()
key, _ := GenerateKey()
v, _ := NewVault(key)
@ -44,6 +46,7 @@ func TestVault_DifferentCiphertexts(t *testing.T) {
}
func TestVault_WrongKey(t *testing.T) {
t.Parallel()
key1, _ := GenerateKey()
key2, _ := GenerateKey()
@ -58,6 +61,7 @@ func TestVault_WrongKey(t *testing.T) {
}
func TestVault_TamperedCiphertext(t *testing.T) {
t.Parallel()
key, _ := GenerateKey()
v, _ := NewVault(key)
@ -70,6 +74,7 @@ func TestVault_TamperedCiphertext(t *testing.T) {
}
func TestVault_InvalidKeyLength(t *testing.T) {
t.Parallel()
_, err := NewVault([]byte("too-short"))
assert.ErrorIs(t, err, ErrKeyLength)
@ -78,6 +83,7 @@ func TestVault_InvalidKeyLength(t *testing.T) {
}
func TestVault_EmptyInput(t *testing.T) {
t.Parallel()
key, _ := GenerateKey()
v, _ := NewVault(key)
@ -90,6 +96,7 @@ func TestVault_EmptyInput(t *testing.T) {
}
func TestVault_BinaryData(t *testing.T) {
t.Parallel()
key, _ := GenerateKey()
v, _ := NewVault(key)

View file

@ -9,6 +9,7 @@ import (
)
func TestSchnorrKeypair(t *testing.T) {
t.Parallel()
key := make([]byte, 32)
for i := range key {
key[i] = byte(i + 1)
@ -22,11 +23,13 @@ func TestSchnorrKeypair(t *testing.T) {
}
func TestSchnorrKeypairRejectsBadLength(t *testing.T) {
t.Parallel()
_, _, err := SchnorrKeypair([]byte("short"))
assert.Error(t, err)
}
func TestSchnorrFullHandshake(t *testing.T) {
t.Parallel()
masterKey := make([]byte, 32)
for i := range masterKey {
masterKey[i] = byte(i + 42)
@ -49,6 +52,7 @@ func TestSchnorrFullHandshake(t *testing.T) {
}
func TestSchnorrRejectsWrongKey(t *testing.T) {
t.Parallel()
masterKey1 := make([]byte, 32)
masterKey2 := make([]byte, 32)
for i := range masterKey1 {
@ -73,6 +77,7 @@ func TestSchnorrRejectsWrongKey(t *testing.T) {
}
func TestZKPSessionManagerIssueAndValidate(t *testing.T) {
t.Parallel()
masterKey := make([]byte, 32)
for i := range masterKey {
masterKey[i] = byte(i + 7)
@ -96,6 +101,7 @@ func TestZKPSessionManagerIssueAndValidate(t *testing.T) {
}
func TestZKPSessionManagerRejectsInvalidProof(t *testing.T) {
t.Parallel()
masterKey := make([]byte, 32)
for i := range masterKey {
masterKey[i] = byte(i)
@ -109,6 +115,7 @@ func TestZKPSessionManagerRejectsInvalidProof(t *testing.T) {
}
func TestZKPSessionManagerExpiry(t *testing.T) {
t.Parallel()
masterKey := make([]byte, 32)
for i := range masterKey {
masterKey[i] = byte(i + 3)
@ -128,6 +135,7 @@ func TestZKPSessionManagerExpiry(t *testing.T) {
}
func TestZKPSessionManagerRevoke(t *testing.T) {
t.Parallel()
masterKey := make([]byte, 32)
for i := range masterKey {
masterKey[i] = byte(i + 5)
@ -148,6 +156,7 @@ func TestZKPSessionManagerRevoke(t *testing.T) {
}
func TestHandshakePayloadBinaryRoundTrip(t *testing.T) {
t.Parallel()
hp := HandshakePayload{Phase: 3}
for i := 0; i < 32; i++ {
hp.RX[i] = byte(i)
@ -165,6 +174,7 @@ func TestHandshakePayloadBinaryRoundTrip(t *testing.T) {
}
func TestHandshakeResultBinaryRoundTrip(t *testing.T) {
t.Parallel()
hr := HandshakeResult{ExpiresUnix: time.Now().Unix()}
for i := 0; i < 32; i++ {
hr.SessionToken[i] = byte(i + 200)
@ -179,6 +189,7 @@ func TestHandshakeResultBinaryRoundTrip(t *testing.T) {
}
func TestZKPSessionManagerCleanup(t *testing.T) {
t.Parallel()
masterKey := make([]byte, 32)
for i := range masterKey {
masterKey[i] = byte(i + 11)

View file

@ -1,7 +1,6 @@
package session
import (
"context"
"fmt"
"os"
"path/filepath"
@ -9,6 +8,8 @@ import (
"time"
jsonv2 "github.com/go-json-experiment/json"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -17,6 +18,7 @@ import (
)
func TestSanitizeFilename(t *testing.T) {
t.Parallel()
tests := []struct {
input string
expected string
@ -40,6 +42,7 @@ func TestSanitizeFilename(t *testing.T) {
}
func TestSave_WithColonInKey(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
sm := NewSessionManager(tmpDir)
@ -71,6 +74,7 @@ func TestSave_WithColonInKey(t *testing.T) {
}
func TestSave_RejectsPathTraversal(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
sm := NewSessionManager(tmpDir)
@ -84,6 +88,7 @@ func TestSave_RejectsPathTraversal(t *testing.T) {
}
func TestTruncateHistory_ToolCallAware(t *testing.T) {
t.Parallel()
sm := NewSessionManager("") // in-memory only
key := "test-tool-truncation"
@ -129,6 +134,7 @@ func TestTruncateHistory_ToolCallAware(t *testing.T) {
}
func TestTruncateHistory_NoToolCalls(t *testing.T) {
t.Parallel()
sm := NewSessionManager("")
key := "test-no-tools"
@ -151,6 +157,7 @@ func TestTruncateHistory_NoToolCalls(t *testing.T) {
}
func TestAddFullMessage_NoHardCapTruncation(t *testing.T) {
t.Parallel()
sm := NewSessionManager("")
key := "test-hard-cap"
@ -167,6 +174,7 @@ func TestAddFullMessage_NoHardCapTruncation(t *testing.T) {
}
func TestCleanupStale(t *testing.T) {
t.Parallel()
sm := NewSessionManager("")
sm.AddFullMessage("active", messages.Message{Role: "user", Content: "hi"})
@ -195,11 +203,12 @@ func TestCleanupStale(t *testing.T) {
}
func TestSessionManager_DelegatePersistence(t *testing.T) {
t.Parallel()
del, err := delegate.NewLibSQLInMemory()
if err != nil {
t.Fatalf("NewLibSQLInMemory: %v", err)
}
if err := del.Init(context.Background()); err != nil {
if err := del.Init(t.Context()); err != nil {
t.Fatalf("Init: %v", err)
}
defer del.Close()
@ -215,7 +224,7 @@ func TestSessionManager_DelegatePersistence(t *testing.T) {
t.Fatalf("expected 2 messages in-memory, got %d", len(history))
}
items, err := del.ListRecallItems(context.Background(), "test-agent", key, 100, 0)
items, err := del.ListRecallItems(t.Context(), "test-agent", key, 100, 0)
if err != nil {
t.Fatalf("ListRecallItems: %v", err)
}
@ -228,11 +237,12 @@ func TestSessionManager_DelegatePersistence(t *testing.T) {
}
func TestSessionManager_DelegateSaveIsNoop(t *testing.T) {
t.Parallel()
del, err := delegate.NewLibSQLInMemory()
if err != nil {
t.Fatalf("NewLibSQLInMemory: %v", err)
}
if err := del.Init(context.Background()); err != nil {
if err := del.Init(t.Context()); err != nil {
t.Fatalf("Init: %v", err)
}
defer del.Close()
@ -256,11 +266,12 @@ func TestSessionManager_DelegateSaveIsNoop(t *testing.T) {
}
func TestSessionManager_DelegateBootstrapPaginationAndOrder(t *testing.T) {
t.Parallel()
del, err := delegate.NewLibSQLInMemory()
if err != nil {
t.Fatalf("NewLibSQLInMemory: %v", err)
}
if err := del.Init(context.Background()); err != nil {
if err := del.Init(t.Context()); err != nil {
t.Fatalf("Init: %v", err)
}
defer del.Close()
@ -291,9 +302,10 @@ func TestSessionManager_DelegateBootstrapPaginationAndOrder(t *testing.T) {
}
func TestSessionManager_ProjectionPointerPersistedAndRestored(t *testing.T) {
t.Parallel()
del, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
require.NoError(t, del.Init(context.Background()))
require.NoError(t, del.Init(t.Context()))
defer del.Close()
sessionKey := "ptr-persist"
@ -303,35 +315,34 @@ func TestSessionManager_ProjectionPointerPersistedAndRestored(t *testing.T) {
sm.AddMessage(sessionKey, "user", "third")
// Read persisted pointer from KV
raw, err := del.GetKV(context.Background(), "test-agent", projectionPointerKey(sessionKey))
raw, err := del.GetKV(t.Context(), "test-agent", projectionPointerKey(sessionKey))
require.NoError(t, err)
require.NotEmpty(t, raw)
var ptr ProjectionPointer
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &ptr))
assert.Equal(t, 3, ptr.Count)
assert.False(t, ptr.FirstMessageID.IsZero())
assert.False(t, ptr.LastMessageID.IsZero())
assert.NotZero(t, ptr.FirstMessageID)
assert.NotZero(t, ptr.LastMessageID)
assert.False(t, ptr.FirstCreatedAt.IsZero())
assert.False(t, ptr.LastCreatedAt.IsZero())
assert.Equal(t, 3, ptr.Count)
// New manager restores; pointer is re-persisted (same values)
sm2 := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
history := sm2.GetHistory(sessionKey)
require.Len(t, history, 3)
raw2, err := del.GetKV(context.Background(), "test-agent", projectionPointerKey(sessionKey))
raw2, err := del.GetKV(t.Context(), "test-agent", projectionPointerKey(sessionKey))
require.NoError(t, err)
require.NotEmpty(t, raw2)
var ptr2 ProjectionPointer
require.NoError(t, jsonv2.Unmarshal([]byte(raw2), &ptr2))
assert.Equal(t, ptr.Count, ptr2.Count)
assert.Equal(t, ptr.FirstMessageID, ptr2.FirstMessageID)
assert.Equal(t, ptr.LastMessageID, ptr2.LastMessageID)
assert.Empty(t, cmp.Diff(ptr, ptr2, cmpopts.IgnoreFields(ProjectionPointer{}, "FirstCreatedAt", "LastCreatedAt")))
}
func TestSessionManager_ProjectionPointerUpdatedOnAppend(t *testing.T) {
t.Parallel()
del, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
require.NoError(t, del.Init(context.Background()))
require.NoError(t, del.Init(t.Context()))
defer del.Close()
sessionKey := "ptr-append"
@ -339,7 +350,7 @@ func TestSessionManager_ProjectionPointerUpdatedOnAppend(t *testing.T) {
for i := 0; i < 4; i++ {
sm.AddMessage(sessionKey, "user", fmt.Sprintf("msg-%d", i))
raw, err := del.GetKV(context.Background(), "test-agent", projectionPointerKey(sessionKey))
raw, err := del.GetKV(t.Context(), "test-agent", projectionPointerKey(sessionKey))
require.NoError(t, err)
require.NotEmpty(t, raw)
var ptr ProjectionPointer
@ -349,9 +360,10 @@ func TestSessionManager_ProjectionPointerUpdatedOnAppend(t *testing.T) {
}
func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
t.Parallel()
del, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
require.NoError(t, del.Init(context.Background()))
require.NoError(t, del.Init(t.Context()))
defer del.Close()
sessionKey := "integrity-mismatch"
@ -362,7 +374,7 @@ func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
// Corrupt the stored pointer to simulate prior state mismatch
corrupt := ProjectionPointer{Count: 0}
data, _ := jsonv2.Marshal(corrupt)
require.NoError(t, del.UpsertKV(context.Background(), "test-agent", projectionPointerKey(sessionKey), string(data)))
require.NoError(t, del.UpsertKV(t.Context(), "test-agent", projectionPointerKey(sessionKey), string(data)))
// New manager restores; should succeed (lossless) despite mismatch
sm2 := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
@ -372,7 +384,7 @@ func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
assert.Equal(t, "b", history[1].Content)
// Pointer should now reflect restored state
raw, err := del.GetKV(context.Background(), "test-agent", projectionPointerKey(sessionKey))
raw, err := del.GetKV(t.Context(), "test-agent", projectionPointerKey(sessionKey))
require.NoError(t, err)
require.NotEmpty(t, raw)
var ptr ProjectionPointer
@ -381,9 +393,10 @@ func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
}
func TestSessionManager_ProjectionBackfillStatusPersistedOnBootstrap(t *testing.T) {
t.Parallel()
del, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
require.NoError(t, del.Init(context.Background()))
require.NoError(t, del.Init(t.Context()))
defer del.Close()
writer := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
@ -393,7 +406,7 @@ func TestSessionManager_ProjectionBackfillStatusPersistedOnBootstrap(t *testing.
_ = NewSessionManager("", WithSessionDelegate(del, "test-agent"))
raw, err := del.GetKV(context.Background(), "test-agent", projectionBackfillStatusKey())
raw, err := del.GetKV(t.Context(), "test-agent", projectionBackfillStatusKey())
require.NoError(t, err)
require.NotEmpty(t, raw)

View file

@ -10,6 +10,7 @@ import (
)
func TestParseWikilinks(t *testing.T) {
t.Parallel()
tests := []struct {
name string
content string
@ -66,6 +67,7 @@ func TestParseWikilinks(t *testing.T) {
}
func TestMergeUnique(t *testing.T) {
t.Parallel()
tests := []struct {
name string
a, b []string
@ -94,6 +96,7 @@ func writeSkill(t *testing.T, dir, name, content string) {
}
func TestBuildGraph_Basic(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
writeSkill(t, tmp, "risk-management", `---
@ -152,6 +155,7 @@ No wikilinks here.
}
func TestBuildGraph_FrontmatterLinksAndWikilinks(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
writeSkill(t, tmp, "alpha", `---
@ -186,6 +190,7 @@ No links.
}
func TestTraverseFrom(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
writeSkill(t, tmp, "a", `---
@ -233,6 +238,7 @@ No outgoing links.
}
func TestSearchSkills(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
writeSkill(t, tmp, "risk-management", `---
@ -278,6 +284,7 @@ Content.
}
func TestListMOCs(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
writeSkill(t, tmp, "trading-moc", `---
@ -305,6 +312,7 @@ Content.
}
func TestGetIndex(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
writeSkill(t, tmp, "index", `---
@ -332,6 +340,7 @@ Content.
}
func TestGetIndex_None(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
writeSkill(t, tmp, "some-skill", `---
name: some-skill
@ -346,6 +355,7 @@ Content.
}
func TestExtendedFrontmatter_JSON(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
writeSkill(t, tmp, "json-skill", `---

View file

@ -7,6 +7,7 @@ import (
)
func TestSkillsInfoValidate(t *testing.T) {
t.Parallel()
testcases := []struct {
name string
skillName string

View file

@ -10,6 +10,7 @@ import (
)
func TestAvailableTemplates(t *testing.T) {
t.Parallel()
templates := AvailableTemplates()
assert.Contains(t, templates, "trading")
assert.Contains(t, templates, "legal")
@ -17,6 +18,7 @@ func TestAvailableTemplates(t *testing.T) {
}
func TestInstallTemplate(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
err := InstallTemplate("trading", tmp)
@ -35,6 +37,7 @@ func TestInstallTemplate(t *testing.T) {
}
func TestInstallTemplate_BuildsValidGraph(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
require.NoError(t, InstallTemplate("trading", tmp))
@ -56,6 +59,7 @@ func TestInstallTemplate_BuildsValidGraph(t *testing.T) {
}
func TestInstallTemplate_NotFound(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
err := InstallTemplate("nonexistent", tmp)
assert.Error(t, err)

View file

@ -1,7 +1,6 @@
package state
import (
"context"
"fmt"
"os"
"path/filepath"
@ -11,7 +10,10 @@ import (
)
func TestAtomicSave(t *testing.T) {
t.Parallel(
// Create temp workspace
)
tmpDir, err := os.MkdirTemp("", "state-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -21,7 +23,7 @@ func TestAtomicSave(t *testing.T) {
sm := NewManager(tmpDir)
// Test SetLastChannel
err = sm.SetLastChannel(context.Background(), "test-channel")
err = sm.SetLastChannel(t.Context(), "test-channel")
if err != nil {
t.Fatalf("SetLastChannel failed: %v", err)
}
@ -51,6 +53,7 @@ func TestAtomicSave(t *testing.T) {
}
func TestSetLastChatID(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "state-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -60,7 +63,7 @@ func TestSetLastChatID(t *testing.T) {
sm := NewManager(tmpDir)
// Test SetLastChatID
err = sm.SetLastChatID(context.Background(), "test-chat-id")
err = sm.SetLastChatID(t.Context(), "test-chat-id")
if err != nil {
t.Fatalf("SetLastChatID failed: %v", err)
}
@ -84,6 +87,7 @@ func TestSetLastChatID(t *testing.T) {
}
func TestAtomicity_NoCorruptionOnInterrupt(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "state-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -93,7 +97,7 @@ func TestAtomicity_NoCorruptionOnInterrupt(t *testing.T) {
sm := NewManager(tmpDir)
// Write initial state
err = sm.SetLastChannel(context.Background(), "initial-channel")
err = sm.SetLastChannel(t.Context(), "initial-channel")
if err != nil {
t.Fatalf("SetLastChannel failed: %v", err)
}
@ -115,7 +119,7 @@ func TestAtomicity_NoCorruptionOnInterrupt(t *testing.T) {
os.Remove(tempFile)
// Now do a proper save
err = sm.SetLastChannel(context.Background(), "new-channel")
err = sm.SetLastChannel(t.Context(), "new-channel")
if err != nil {
t.Fatalf("SetLastChannel failed: %v", err)
}
@ -127,6 +131,7 @@ func TestAtomicity_NoCorruptionOnInterrupt(t *testing.T) {
}
func TestConcurrentAccess(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "state-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -140,7 +145,7 @@ func TestConcurrentAccess(t *testing.T) {
for i := 0; i < 10; i++ {
go func(idx int) {
channel := fmt.Sprintf("channel-%d", idx)
sm.SetLastChannel(context.Background(), channel)
sm.SetLastChannel(t.Context(), channel)
done <- true
}(i)
}
@ -170,6 +175,7 @@ func TestConcurrentAccess(t *testing.T) {
}
func TestNewManager_ExistingState(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "state-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
@ -178,8 +184,8 @@ func TestNewManager_ExistingState(t *testing.T) {
// Create initial state
sm1 := NewManager(tmpDir)
sm1.SetLastChannel(context.Background(), "existing-channel")
sm1.SetLastChatID(context.Background(), "existing-chat-id")
sm1.SetLastChannel(t.Context(), "existing-channel")
sm1.SetLastChatID(t.Context(), "existing-chat-id")
// Create new manager with same workspace
sm2 := NewManager(tmpDir)
@ -195,6 +201,7 @@ func TestNewManager_ExistingState(t *testing.T) {
}
func TestNewManager_EmptyWorkspace(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "state-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)

View file

@ -85,6 +85,7 @@ func setupIdentityDir(t *testing.T, files map[string]string) string {
}
func TestSyncAll_InsertsNewFiles(t *testing.T) {
t.Parallel()
dir := setupIdentityDir(t, map[string]string{
"AGENT.md": "# Agent\nYou are helpful.",
"SOUL.md": "# Soul\nCurious and kind.",
@ -95,7 +96,7 @@ func TestSyncAll_InsertsNewFiles(t *testing.T) {
store := newMockStore()
s := New(dir, "agent-1", store)
require.NoError(t, s.SyncAll(context.Background()))
require.NoError(t, s.SyncAll(t.Context()))
for _, name := range IdentityFiles {
doc := store.getDoc("agent-1", name)
@ -109,6 +110,7 @@ func TestSyncAll_InsertsNewFiles(t *testing.T) {
}
func TestSyncAll_SkipsUnchangedFiles(t *testing.T) {
t.Parallel()
dir := setupIdentityDir(t, map[string]string{
"AGENT.md": "# Agent\nSame content.",
})
@ -116,29 +118,30 @@ func TestSyncAll_SkipsUnchangedFiles(t *testing.T) {
store := newMockStore()
s := New(dir, "agent-1", store)
require.NoError(t, s.SyncAll(context.Background()))
require.NoError(t, s.SyncAll(t.Context()))
firstDoc := store.getDoc("agent-1", "AGENT.md")
require.NotNil(t, firstDoc)
firstID := firstDoc.ID
require.NoError(t, s.SyncAll(context.Background()))
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")
}
func TestSyncAll_UpsertsModifiedFiles(t *testing.T) {
t.Parallel()
dir := setupIdentityDir(t, map[string]string{
"AGENT.md": "# Agent\nVersion 1",
})
store := newMockStore()
s := New(dir, "agent-1", store)
require.NoError(t, s.SyncAll(context.Background()))
require.NoError(t, s.SyncAll(t.Context()))
v1Hash := store.getHash("agent-1", "AGENT.md")
require.NoError(t, os.WriteFile(filepath.Join(dir, "AGENT.md"), []byte("# Agent\nVersion 2"), 0644))
require.NoError(t, s.SyncAll(context.Background()))
require.NoError(t, s.SyncAll(t.Context()))
v2Hash := store.getHash("agent-1", "AGENT.md")
assert.NotEqual(t, v1Hash, v2Hash, "hash should change after file modification")
@ -148,6 +151,7 @@ func TestSyncAll_UpsertsModifiedFiles(t *testing.T) {
}
func TestSyncAll_SkipsMissingFiles(t *testing.T) {
t.Parallel()
dir := setupIdentityDir(t, map[string]string{
"AGENT.md": "# Agent only",
})
@ -155,7 +159,7 @@ func TestSyncAll_SkipsMissingFiles(t *testing.T) {
store := newMockStore()
s := New(dir, "agent-1", store)
require.NoError(t, s.SyncAll(context.Background()))
require.NoError(t, s.SyncAll(t.Context()))
assert.NotNil(t, store.getDoc("agent-1", "AGENT.md"))
assert.Nil(t, store.getDoc("agent-1", "SOUL.md"), "missing file should not create a doc")
@ -164,6 +168,7 @@ func TestSyncAll_SkipsMissingFiles(t *testing.T) {
}
func TestSyncAll_SkipsEmptyFiles(t *testing.T) {
t.Parallel()
dir := setupIdentityDir(t, map[string]string{
"AGENT.md": " \n\t\n ",
})
@ -171,11 +176,12 @@ func TestSyncAll_SkipsEmptyFiles(t *testing.T) {
store := newMockStore()
s := New(dir, "agent-1", store)
require.NoError(t, s.SyncAll(context.Background()))
require.NoError(t, s.SyncAll(t.Context()))
assert.Nil(t, store.getDoc("agent-1", "AGENT.md"), "empty/whitespace-only file should be skipped")
}
func TestSyncAll_IsolatesAgents(t *testing.T) {
t.Parallel()
dir := setupIdentityDir(t, map[string]string{
"AGENT.md": "# Shared agent file",
})
@ -184,8 +190,8 @@ func TestSyncAll_IsolatesAgents(t *testing.T) {
s1 := New(dir, "agent-a", store)
s2 := New(dir, "agent-b", store)
require.NoError(t, s1.SyncAll(context.Background()))
require.NoError(t, s2.SyncAll(context.Background()))
require.NoError(t, s1.SyncAll(t.Context()))
require.NoError(t, s2.SyncAll(t.Context()))
docA := store.getDoc("agent-a", "AGENT.md")
docB := store.getDoc("agent-b", "AGENT.md")
@ -195,51 +201,54 @@ func TestSyncAll_IsolatesAgents(t *testing.T) {
}
func TestCheckAndSync_DetectsModifiedFile(t *testing.T) {
t.Parallel()
dir := setupIdentityDir(t, map[string]string{
"AGENT.md": "# Original",
})
store := newMockStore()
s := New(dir, "agent-1", store)
require.NoError(t, s.SyncAll(context.Background()))
require.NoError(t, s.SyncAll(t.Context()))
time.Sleep(50 * time.Millisecond)
require.NoError(t, os.WriteFile(filepath.Join(dir, "AGENT.md"), []byte("# Modified"), 0644))
require.NoError(t, s.CheckAndSync(context.Background()))
require.NoError(t, s.CheckAndSync(t.Context()))
doc := store.getDoc("agent-1", "AGENT.md")
assert.Contains(t, doc.Content, "Modified")
}
func TestCheckAndSync_SkipsUntouchedFiles(t *testing.T) {
t.Parallel()
dir := setupIdentityDir(t, map[string]string{
"AGENT.md": "# Stable",
})
store := newMockStore()
s := New(dir, "agent-1", store)
require.NoError(t, s.SyncAll(context.Background()))
require.NoError(t, s.SyncAll(t.Context()))
hash1 := store.getHash("agent-1", "AGENT.md")
s.lastSync.Store(time.Now().Add(time.Second).UnixNano())
require.NoError(t, s.CheckAndSync(context.Background()))
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")
}
func TestWatch_DetectsFileChange(t *testing.T) {
t.Parallel()
dir := setupIdentityDir(t, map[string]string{
"AGENT.md": "# Initial",
})
store := newMockStore()
s := New(dir, "agent-1", store)
require.NoError(t, s.SyncAll(context.Background()))
require.NoError(t, s.SyncAll(t.Context()))
ctx := context.Background()
ctx := t.Context()
require.NoError(t, s.Watch(ctx))
defer s.Close()
@ -252,15 +261,16 @@ func TestWatch_DetectsFileChange(t *testing.T) {
}
func TestWatch_IgnoresNonIdentityFiles(t *testing.T) {
t.Parallel()
dir := setupIdentityDir(t, map[string]string{
"AGENT.md": "# Agent",
})
store := newMockStore()
s := New(dir, "agent-1", store)
require.NoError(t, s.SyncAll(context.Background()))
require.NoError(t, s.SyncAll(t.Context()))
ctx := context.Background()
ctx := t.Context()
require.NoError(t, s.Watch(ctx))
defer s.Close()
@ -271,6 +281,7 @@ func TestWatch_IgnoresNonIdentityFiles(t *testing.T) {
}
func TestContentHash_Deterministic(t *testing.T) {
t.Parallel()
data := []byte("hello world")
h1 := contentHash(data)
h2 := contentHash(data)
@ -279,12 +290,14 @@ func TestContentHash_Deterministic(t *testing.T) {
}
func TestContentHash_DifferentForDifferentContent(t *testing.T) {
t.Parallel()
h1 := contentHash([]byte("version 1"))
h2 := contentHash([]byte("version 2"))
assert.NotEqual(t, h1, h2)
}
func TestIsIdentityFile(t *testing.T) {
t.Parallel()
tests := []struct {
name string
want bool
@ -306,6 +319,7 @@ func TestIsIdentityFile(t *testing.T) {
}
func TestNew_SetsFields(t *testing.T) {
t.Parallel()
store := newMockStore()
s := New("/tmp/identity", "test-agent", store)
assert.Equal(t, "/tmp/identity", s.identityDir)

View file

@ -13,6 +13,7 @@ import (
)
func TestAgenticMapTool_Execute_Success(t *testing.T) {
t.Parallel()
provider := &MockLanguageModel{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
manager.SetRunLoop(func(_ context.Context, _ ToolLoopConfig, _, userPrompt, _, _ string) (*ToolLoopResult, error) {
@ -20,7 +21,7 @@ func TestAgenticMapTool_Execute_Success(t *testing.T) {
})
tool := NewAgenticMapTool(manager)
result := tool.Execute(context.Background(), map[string]interface{}{
result := tool.Execute(t.Context(), map[string]interface{}{
"items": []interface{}{
map[string]interface{}{"name": "a"},
map[string]interface{}{"name": "b"},
@ -46,6 +47,7 @@ func TestAgenticMapTool_Execute_Success(t *testing.T) {
}
func TestAgenticMapTool_Execute_RetriesFailedItems(t *testing.T) {
t.Parallel()
provider := &MockLanguageModel{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
callCount := 0
@ -58,7 +60,7 @@ func TestAgenticMapTool_Execute_RetriesFailedItems(t *testing.T) {
})
tool := NewAgenticMapTool(manager)
result := tool.Execute(context.Background(), map[string]interface{}{
result := tool.Execute(t.Context(), map[string]interface{}{
"items": []interface{}{map[string]interface{}{"name": "retry-me"}},
"task_template": "Retry item {{index}} => {{item_json}}",
"max_retries": float64(2),
@ -80,11 +82,12 @@ func TestAgenticMapTool_Execute_RetriesFailedItems(t *testing.T) {
}
func TestAgenticMapTool_Execute_RequiresPlaceholders(t *testing.T) {
t.Parallel()
provider := &MockLanguageModel{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
tool := NewAgenticMapTool(manager)
result := tool.Execute(context.Background(), map[string]interface{}{
result := tool.Execute(t.Context(), map[string]interface{}{
"items": []interface{}{map[string]interface{}{"name": "x"}},
"task_template": "Handle item without placeholders",
})
@ -95,11 +98,12 @@ func TestAgenticMapTool_Execute_RequiresPlaceholders(t *testing.T) {
}
func TestAgenticMapTool_Execute_ContextCancelled(t *testing.T) {
t.Parallel()
provider := &MockLanguageModel{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
tool := NewAgenticMapTool(manager)
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(t.Context())
cancel()
result := tool.Execute(ctx, map[string]interface{}{

View file

@ -7,6 +7,7 @@ import (
)
func TestToolCallTool_Name(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
tc := NewToolCallTool(r)
if tc.Name() != "tool_call" {
@ -15,6 +16,7 @@ func TestToolCallTool_Name(t *testing.T) {
}
func TestToolCallTool_Description(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
tc := NewToolCallTool(r)
if tc.Description() == "" {
@ -23,6 +25,7 @@ func TestToolCallTool_Description(t *testing.T) {
}
func TestToolCallTool_Parameters(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
tc := NewToolCallTool(r)
params := tc.Parameters()
@ -40,9 +43,10 @@ func TestToolCallTool_Parameters(t *testing.T) {
}
func TestToolCallTool_MissingToolName(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{})
result := tc.Execute(t.Context(), map[string]interface{}{})
if !result.IsError {
t.Error("expected error for missing tool_name")
@ -50,11 +54,12 @@ func TestToolCallTool_MissingToolName(t *testing.T) {
}
func TestToolCallTool_DispatchesToTool(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
r.Register(&stubTool{name: "read_file", desc: "Read a file"})
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "read_file",
"arguments": map[string]interface{}{"path": "/tmp/test.txt"},
})
@ -68,11 +73,12 @@ func TestToolCallTool_DispatchesToTool(t *testing.T) {
}
func TestToolCallTool_PreventRecursion_ToolCall(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
r.RegisterMetaTools()
tc, _ := r.Get("tool_call")
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "tool_call",
})
@ -82,11 +88,12 @@ func TestToolCallTool_PreventRecursion_ToolCall(t *testing.T) {
}
func TestToolCallTool_PreventRecursion_ToolSearch(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
r.RegisterMetaTools()
tc, _ := r.Get("tool_call")
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "tool_search",
})
@ -96,11 +103,12 @@ func TestToolCallTool_PreventRecursion_ToolSearch(t *testing.T) {
}
func TestToolCallTool_JSONStringArguments(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
r.Register(&echoTool{})
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "echo",
"arguments": `{"msg":"hello"}`,
})
@ -114,11 +122,12 @@ func TestToolCallTool_JSONStringArguments(t *testing.T) {
}
func TestToolCallTool_NilArguments(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
r.Register(&stubTool{name: "no_args", desc: "Tool that needs no args"})
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "no_args",
})
@ -128,11 +137,12 @@ func TestToolCallTool_NilArguments(t *testing.T) {
}
func TestToolCallTool_InvalidJSONArguments(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
r.Register(&stubToolWithSchema{name: "read_file", desc: "Read a file"})
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "read_file",
"arguments": "not-json",
})
@ -147,11 +157,12 @@ func TestToolCallTool_InvalidJSONArguments(t *testing.T) {
}
func TestToolCallTool_MissingRequiredArgs_IncludesSchemaHint(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
r.Register(&stubToolWithSchema{name: "read_file", desc: "Read a file"})
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "read_file",
"arguments": map[string]interface{}{},
})
@ -168,10 +179,11 @@ func TestToolCallTool_MissingRequiredArgs_IncludesSchemaHint(t *testing.T) {
}
func TestToolCallTool_ToolNotFoundSuggestsSearch(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "nonexistent",
})
@ -184,6 +196,7 @@ func TestToolCallTool_ToolNotFoundSuggestsSearch(t *testing.T) {
}
func TestToolCallTool_ContextPropagation(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
ct := &contextCaptureTool{}
r.Register(ct)
@ -191,7 +204,7 @@ func TestToolCallTool_ContextPropagation(t *testing.T) {
tc := NewToolCallTool(r)
tc.SetContext("test-channel", "test-chat")
tc.Execute(context.Background(), map[string]interface{}{
tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "capture",
"arguments": map[string]interface{}{},
})
@ -203,6 +216,7 @@ func TestToolCallTool_ContextPropagation(t *testing.T) {
}
func TestToolCallTool_ResourceProvider_LoadsResources(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
rt := &resourceAwareTool{
resources: map[string]string{
@ -212,7 +226,7 @@ func TestToolCallTool_ResourceProvider_LoadsResources(t *testing.T) {
r.Register(rt)
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "resource_tool",
"arguments": map[string]interface{}{},
})
@ -231,12 +245,13 @@ func TestToolCallTool_ResourceProvider_LoadsResources(t *testing.T) {
}
func TestToolCallTool_NormalizesCommaSeparatedToolName(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
r.Register(&stubTool{name: "write_file", desc: "write"})
r.Register(&stubTool{name: "read_file", desc: "read"})
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "write_file, read_file",
"arguments": map[string]interface{}{"path": "x.txt"},
})
@ -250,11 +265,12 @@ func TestToolCallTool_NormalizesCommaSeparatedToolName(t *testing.T) {
}
func TestToolCallTool_NormalizesEmbeddedToolName(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
r.Register(&stubTool{name: "exec", desc: "exec"})
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "exec_tool_search_query_exec_run_shell_command_return_output_caution",
"arguments": map[string]interface{}{"command": "echo hi"},
})
@ -268,11 +284,12 @@ func TestToolCallTool_NormalizesEmbeddedToolName(t *testing.T) {
}
func TestToolCallTool_NoResourceProvider_StillWorks(t *testing.T) {
t.Parallel()
r := NewToolRegistry()
r.Register(&stubTool{name: "plain", desc: "No resources"})
tc := NewToolCallTool(r)
result := tc.Execute(context.Background(), map[string]interface{}{
result := tc.Execute(t.Context(), map[string]interface{}{
"tool_name": "plain",
"arguments": map[string]interface{}{},
})
@ -283,7 +300,8 @@ func TestToolCallTool_NoResourceProvider_StillWorks(t *testing.T) {
}
func TestResourcesFromContext_Empty(t *testing.T) {
res := ResourcesFromContext(context.Background())
t.Parallel()
res := ResourcesFromContext(t.Context())
if res != nil {
t.Error("expected nil for empty context")
}

View file

@ -1,7 +1,6 @@
package tools
import (
"context"
"encoding/json"
"strconv"
"testing"
@ -16,7 +15,7 @@ import (
func setupDAGTools(t *testing.T) (DAGToolDeps, *delegate.LibSQLDelegate) {
d, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
require.NoError(t, d.Init(context.Background()))
require.NoError(t, d.Init(t.Context()))
// Insert session messages so dag_expand/dag_grep have data
agentID, sessionKey := "test-agent", "test-session"
@ -26,7 +25,7 @@ func setupDAGTools(t *testing.T) (DAGToolDeps, *delegate.LibSQLDelegate) {
role = "assistant"
}
content := "Message number " + strconv.Itoa(i)
require.NoError(t, d.InsertSessionMessage(context.Background(), agentID, sessionKey, role, content))
require.NoError(t, d.InsertSessionMessage(t.Context(), agentID, sessionKey, role, content))
}
// Persist a DAG snapshot
@ -40,7 +39,7 @@ func setupDAGTools(t *testing.T) (DAGToolDeps, *delegate.LibSQLDelegate) {
msgs[i] = dag.Message{Role: role, Content: "Message " + string(rune('A'+i%26))}
}
dagOut := compressor.Compress(msgs)
require.NoError(t, d.PersistDAG(context.Background(), agentID, sessionKey, &dag.PersistSnapshot{
require.NoError(t, d.PersistDAG(t.Context(), agentID, sessionKey, &dag.PersistSnapshot{
FromMsgIdx: 0,
ToMsgIdx: 16,
MsgCount: 16,
@ -66,11 +65,12 @@ func setupDAGTools(t *testing.T) (DAGToolDeps, *delegate.LibSQLDelegate) {
}
func TestDagDescribeTool(t *testing.T) {
t.Parallel()
deps, del := setupDAGTools(t)
defer del.Close()
tool := NewDagDescribeTool(deps)
ctx := context.Background()
ctx := t.Context()
// Describe an existing node (chunk-1 from default config with 16 msgs)
res := tool.Execute(ctx, map[string]interface{}{"node_id": "chunk-1", "session_key": "test-session"})
@ -80,10 +80,11 @@ func TestDagDescribeTool(t *testing.T) {
}
func TestDagDescribeTool_NoSnapshot(t *testing.T) {
t.Parallel()
d, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
defer d.Close()
require.NoError(t, d.Init(context.Background()))
require.NoError(t, d.Init(t.Context()))
tool := NewDagDescribeTool(DAGToolDeps{
Queries: d.Queries(),
@ -91,17 +92,18 @@ func TestDagDescribeTool_NoSnapshot(t *testing.T) {
AgentID: "x",
SessionFn: func() string { return "nonexistent" },
})
res := tool.Execute(context.Background(), map[string]interface{}{"node_id": "chunk-1"})
res := tool.Execute(t.Context(), map[string]interface{}{"node_id": "chunk-1"})
assert.True(t, res.IsError)
assert.Contains(t, res.ForLLM, "no DAG snapshot")
}
func TestDagGrepTool(t *testing.T) {
t.Parallel()
deps, del := setupDAGTools(t)
defer del.Close()
tool := NewDagGrepTool(deps)
ctx := context.Background()
ctx := t.Context()
res := tool.Execute(ctx, map[string]interface{}{"query": "Message", "session_key": "test-session"})
assert.False(t, res.IsError)
@ -110,11 +112,12 @@ func TestDagGrepTool(t *testing.T) {
}
func TestDagGrepTool_ScopedByNode(t *testing.T) {
t.Parallel()
deps, del := setupDAGTools(t)
defer del.Close()
tool := NewDagGrepTool(deps)
ctx := context.Background()
ctx := t.Context()
res := tool.Execute(ctx, map[string]interface{}{
"query": "number",
@ -126,11 +129,12 @@ func TestDagGrepTool_ScopedByNode(t *testing.T) {
}
func TestDagExpandTool(t *testing.T) {
t.Parallel()
deps, del := setupDAGTools(t)
defer del.Close()
tool := NewDagExpandTool(deps)
ctx := context.Background()
ctx := t.Context()
// Expand chunk-1; needs Lister to return messages
res := tool.Execute(ctx, map[string]interface{}{"node_id": "chunk-1", "session_key": "test-session"})
@ -143,16 +147,17 @@ func TestDagExpandTool(t *testing.T) {
}
func TestDagExpandTool_NoReader(t *testing.T) {
t.Parallel()
d, err := delegate.NewLibSQLInMemory()
require.NoError(t, err)
defer d.Close()
require.NoError(t, d.Init(context.Background()))
require.NoError(t, d.Init(t.Context()))
// Persist minimal DAG
compressor := dag.NewCompressor(dag.DefaultCompressorConfig())
msgs := []dag.Message{{Role: "user", Content: "x"}}
dagOut := compressor.Compress(msgs)
require.NoError(t, d.PersistDAG(context.Background(), "a", "s", &dag.PersistSnapshot{
require.NoError(t, d.PersistDAG(t.Context(), "a", "s", &dag.PersistSnapshot{
FromMsgIdx: 0, ToMsgIdx: 1, MsgCount: 1, DAG: dagOut,
}))
@ -162,16 +167,17 @@ func TestDagExpandTool_NoReader(t *testing.T) {
AgentID: "a",
SessionFn: func() string { return "s" },
})
res := tool.Execute(context.Background(), map[string]interface{}{"node_id": "chunk-1", "session_key": "s"})
res := tool.Execute(t.Context(), map[string]interface{}{"node_id": "chunk-1", "session_key": "s"})
assert.True(t, res.IsError)
assert.Contains(t, res.ForLLM, "dag query store is not configured")
}
func TestDagExpandTool_RecoveryReference(t *testing.T) {
t.Parallel()
deps, del := setupDAGTools(t)
defer del.Close()
ctx := context.Background()
ctx := t.Context()
record := DAGRecoveryRecord{
NodeID: DAGRecoveryNodePrefix + "test-recovery",
SessionKey: "test-session",

Some files were not shown because too many files have changed in this diff Show more