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:
parent
38d1faf425
commit
906d11612d
120 changed files with 1938 additions and 1139 deletions
|
|
@ -39,6 +39,7 @@ func allFileTools(workspace string) []tools.Tool {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func TestToolRegistry_AllToolsHaveSchema(t *testing.T) {
|
func TestToolRegistry_AllToolsHaveSchema(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
registry := tools.NewToolRegistry()
|
registry := tools.NewToolRegistry()
|
||||||
|
|
@ -68,6 +69,7 @@ func TestToolRegistry_AllToolsHaveSchema(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolRegistry_SchemaPropertiesAreTyped(t *testing.T) {
|
func TestToolRegistry_SchemaPropertiesAreTyped(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
for _, tool := range allFileTools(workspace) {
|
for _, tool := range allFileTools(workspace) {
|
||||||
|
|
@ -91,10 +93,11 @@ func TestToolRegistry_SchemaPropertiesAreTyped(t *testing.T) {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func TestToolExecution_ReadFile_NonExistent(t *testing.T) {
|
func TestToolExecution_ReadFile_NonExistent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
readTool := tools.NewReadFileTool(workspace, true)
|
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",
|
"path": "nonexistent_file_12345.txt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -103,10 +106,11 @@ func TestToolExecution_ReadFile_NonExistent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolExecution_WriteAndReadFile(t *testing.T) {
|
func TestToolExecution_WriteAndReadFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
writeTool := tools.NewWriteFileTool(workspace, true)
|
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",
|
"path": "eval_test.txt",
|
||||||
"content": "hello from eval test",
|
"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)
|
assert.False(t, writeResult.IsError, "write should succeed: %s", writeResult.ForLLM)
|
||||||
|
|
||||||
readTool := tools.NewReadFileTool(workspace, true)
|
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",
|
"path": "eval_test.txt",
|
||||||
})
|
})
|
||||||
require.NotNil(t, readResult)
|
require.NotNil(t, readResult)
|
||||||
|
|
@ -123,10 +127,11 @@ func TestToolExecution_WriteAndReadFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolExecution_ExecBlocking(t *testing.T) {
|
func TestToolExecution_ExecBlocking(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
execTool := tools.NewExecTool(workspace, false)
|
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",
|
"command": "echo dragonscale-eval-test",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -136,13 +141,14 @@ func TestToolExecution_ExecBlocking(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolExecution_ListDir(t *testing.T) {
|
func TestToolExecution_ListDir(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
os.WriteFile(filepath.Join(workspace, "file_a.txt"), []byte("a"), 0644)
|
os.WriteFile(filepath.Join(workspace, "file_a.txt"), []byte("a"), 0644)
|
||||||
os.WriteFile(filepath.Join(workspace, "file_b.txt"), []byte("b"), 0644)
|
os.WriteFile(filepath.Join(workspace, "file_b.txt"), []byte("b"), 0644)
|
||||||
|
|
||||||
listTool := tools.NewListDirTool(workspace, true)
|
listTool := tools.NewListDirTool(workspace, true)
|
||||||
result := listTool.Execute(context.Background(), map[string]interface{}{
|
result := listTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"path": ".",
|
"path": ".",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -153,16 +159,17 @@ func TestToolExecution_ListDir(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolExecution_EditFile(t *testing.T) {
|
func TestToolExecution_EditFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
writeTool := tools.NewWriteFileTool(workspace, true)
|
writeTool := tools.NewWriteFileTool(workspace, true)
|
||||||
writeTool.Execute(context.Background(), map[string]interface{}{
|
writeTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"path": "edit_target.txt",
|
"path": "edit_target.txt",
|
||||||
"content": "hello world foo bar",
|
"content": "hello world foo bar",
|
||||||
})
|
})
|
||||||
|
|
||||||
editTool := tools.NewEditFileTool(workspace, true)
|
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",
|
"path": "edit_target.txt",
|
||||||
"old_text": "world",
|
"old_text": "world",
|
||||||
"new_text": "dragonscale",
|
"new_text": "dragonscale",
|
||||||
|
|
@ -171,7 +178,7 @@ func TestToolExecution_EditFile(t *testing.T) {
|
||||||
assert.False(t, result.IsError, "edit should succeed: %s", result.ForLLM)
|
assert.False(t, result.IsError, "edit should succeed: %s", result.ForLLM)
|
||||||
|
|
||||||
readTool := tools.NewReadFileTool(workspace, true)
|
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",
|
"path": "edit_target.txt",
|
||||||
})
|
})
|
||||||
assert.Contains(t, readResult.ForLLM, "dragonscale")
|
assert.Contains(t, readResult.ForLLM, "dragonscale")
|
||||||
|
|
@ -179,16 +186,17 @@ func TestToolExecution_EditFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolExecution_AppendFile(t *testing.T) {
|
func TestToolExecution_AppendFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
writeTool := tools.NewWriteFileTool(workspace, true)
|
writeTool := tools.NewWriteFileTool(workspace, true)
|
||||||
writeTool.Execute(context.Background(), map[string]interface{}{
|
writeTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"path": "append_target.txt",
|
"path": "append_target.txt",
|
||||||
"content": "line one\n",
|
"content": "line one\n",
|
||||||
})
|
})
|
||||||
|
|
||||||
appendTool := tools.NewAppendFileTool(workspace, true)
|
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",
|
"path": "append_target.txt",
|
||||||
"content": "line two\n",
|
"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)
|
assert.False(t, result.IsError, "append should succeed: %s", result.ForLLM)
|
||||||
|
|
||||||
readTool := tools.NewReadFileTool(workspace, true)
|
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",
|
"path": "append_target.txt",
|
||||||
})
|
})
|
||||||
assert.Contains(t, readResult.ForLLM, "line one")
|
assert.Contains(t, readResult.ForLLM, "line one")
|
||||||
|
|
@ -208,10 +216,11 @@ func TestToolExecution_AppendFile(t *testing.T) {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func TestToolExecution_ReadFile_WorkspaceRestriction(t *testing.T) {
|
func TestToolExecution_ReadFile_WorkspaceRestriction(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
readTool := tools.NewReadFileTool(workspace, true)
|
readTool := tools.NewReadFileTool(workspace, true)
|
||||||
result := readTool.Execute(context.Background(), map[string]interface{}{
|
result := readTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"path": "/etc/passwd",
|
"path": "/etc/passwd",
|
||||||
})
|
})
|
||||||
require.NotNil(t, result)
|
require.NotNil(t, result)
|
||||||
|
|
@ -219,10 +228,11 @@ func TestToolExecution_ReadFile_WorkspaceRestriction(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolExecution_WriteFile_WorkspaceRestriction(t *testing.T) {
|
func TestToolExecution_WriteFile_WorkspaceRestriction(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
writeTool := tools.NewWriteFileTool(workspace, true)
|
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",
|
"path": "/tmp/escape_test.txt",
|
||||||
"content": "should not write",
|
"content": "should not write",
|
||||||
})
|
})
|
||||||
|
|
@ -231,10 +241,11 @@ func TestToolExecution_WriteFile_WorkspaceRestriction(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolExecution_ReadFile_PathTraversal(t *testing.T) {
|
func TestToolExecution_ReadFile_PathTraversal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
readTool := tools.NewReadFileTool(workspace, true)
|
readTool := tools.NewReadFileTool(workspace, true)
|
||||||
result := readTool.Execute(context.Background(), map[string]interface{}{
|
result := readTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"path": "../../../../etc/hostname",
|
"path": "../../../../etc/hostname",
|
||||||
})
|
})
|
||||||
require.NotNil(t, result)
|
require.NotNil(t, result)
|
||||||
|
|
@ -242,6 +253,7 @@ func TestToolExecution_ReadFile_PathTraversal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolExecution_Unrestricted(t *testing.T) {
|
func TestToolExecution_Unrestricted(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
tmpFile := filepath.Join(os.TempDir(), "dragonscale_unrestricted_test.txt")
|
tmpFile := filepath.Join(os.TempDir(), "dragonscale_unrestricted_test.txt")
|
||||||
|
|
@ -249,7 +261,7 @@ func TestToolExecution_Unrestricted(t *testing.T) {
|
||||||
defer os.Remove(tmpFile)
|
defer os.Remove(tmpFile)
|
||||||
|
|
||||||
readTool := tools.NewReadFileTool(workspace, false)
|
readTool := tools.NewReadFileTool(workspace, false)
|
||||||
result := readTool.Execute(context.Background(), map[string]interface{}{
|
result := readTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"path": tmpFile,
|
"path": tmpFile,
|
||||||
})
|
})
|
||||||
require.NotNil(t, result)
|
require.NotNil(t, result)
|
||||||
|
|
@ -262,6 +274,7 @@ func TestToolExecution_Unrestricted(t *testing.T) {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func TestToolRegistry_ProgressiveDisclosure(t *testing.T) {
|
func TestToolRegistry_ProgressiveDisclosure(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
registry := tools.NewToolRegistry()
|
registry := tools.NewToolRegistry()
|
||||||
|
|
@ -290,6 +303,7 @@ func TestToolRegistry_ProgressiveDisclosure(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolSearch_FindsReadFile(t *testing.T) {
|
func TestToolSearch_FindsReadFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
registry := tools.NewToolRegistry()
|
registry := tools.NewToolRegistry()
|
||||||
|
|
@ -299,7 +313,7 @@ func TestToolSearch_FindsReadFile(t *testing.T) {
|
||||||
registry.RegisterMetaTools()
|
registry.RegisterMetaTools()
|
||||||
|
|
||||||
searchTool := tools.NewToolSearchTool(registry)
|
searchTool := tools.NewToolSearchTool(registry)
|
||||||
result := searchTool.Execute(context.Background(), map[string]interface{}{
|
result := searchTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"query": "read file",
|
"query": "read file",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -309,6 +323,7 @@ func TestToolSearch_FindsReadFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolSearch_ListsAll(t *testing.T) {
|
func TestToolSearch_ListsAll(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
registry := tools.NewToolRegistry()
|
registry := tools.NewToolRegistry()
|
||||||
|
|
@ -317,7 +332,7 @@ func TestToolSearch_ListsAll(t *testing.T) {
|
||||||
registry.RegisterMetaTools()
|
registry.RegisterMetaTools()
|
||||||
|
|
||||||
searchTool := tools.NewToolSearchTool(registry)
|
searchTool := tools.NewToolSearchTool(registry)
|
||||||
result := searchTool.Execute(context.Background(), map[string]interface{}{
|
result := searchTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"query": "",
|
"query": "",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -327,6 +342,7 @@ func TestToolSearch_ListsAll(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolSearch_NoResults(t *testing.T) {
|
func TestToolSearch_NoResults(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
registry := tools.NewToolRegistry()
|
registry := tools.NewToolRegistry()
|
||||||
|
|
@ -334,7 +350,7 @@ func TestToolSearch_NoResults(t *testing.T) {
|
||||||
registry.RegisterMetaTools()
|
registry.RegisterMetaTools()
|
||||||
|
|
||||||
searchTool := tools.NewToolSearchTool(registry)
|
searchTool := tools.NewToolSearchTool(registry)
|
||||||
result := searchTool.Execute(context.Background(), map[string]interface{}{
|
result := searchTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"query": "xyzzy_nonexistent_tool",
|
"query": "xyzzy_nonexistent_tool",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -343,6 +359,7 @@ func TestToolSearch_NoResults(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCall_DispatchesCorrectly(t *testing.T) {
|
func TestToolCall_DispatchesCorrectly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
os.WriteFile(filepath.Join(workspace, "dispatch_test.txt"), []byte("dispatch ok"), 0644)
|
os.WriteFile(filepath.Join(workspace, "dispatch_test.txt"), []byte("dispatch ok"), 0644)
|
||||||
|
|
@ -352,7 +369,7 @@ func TestToolCall_DispatchesCorrectly(t *testing.T) {
|
||||||
registry.RegisterMetaTools()
|
registry.RegisterMetaTools()
|
||||||
|
|
||||||
callTool := tools.NewToolCallTool(registry)
|
callTool := tools.NewToolCallTool(registry)
|
||||||
result := callTool.Execute(context.Background(), map[string]interface{}{
|
result := callTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "read_file",
|
"tool_name": "read_file",
|
||||||
"arguments": map[string]interface{}{
|
"arguments": map[string]interface{}{
|
||||||
"path": "dispatch_test.txt",
|
"path": "dispatch_test.txt",
|
||||||
|
|
@ -365,6 +382,7 @@ func TestToolCall_DispatchesCorrectly(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCall_RejectsRecursion(t *testing.T) {
|
func TestToolCall_RejectsRecursion(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
registry := tools.NewToolRegistry()
|
registry := tools.NewToolRegistry()
|
||||||
|
|
@ -372,7 +390,7 @@ func TestToolCall_RejectsRecursion(t *testing.T) {
|
||||||
registry.RegisterMetaTools()
|
registry.RegisterMetaTools()
|
||||||
|
|
||||||
callTool := tools.NewToolCallTool(registry)
|
callTool := tools.NewToolCallTool(registry)
|
||||||
result := callTool.Execute(context.Background(), map[string]interface{}{
|
result := callTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "tool_call",
|
"tool_name": "tool_call",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -381,11 +399,12 @@ func TestToolCall_RejectsRecursion(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCall_RejectsUnknownTool(t *testing.T) {
|
func TestToolCall_RejectsUnknownTool(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
registry := tools.NewToolRegistry()
|
registry := tools.NewToolRegistry()
|
||||||
registry.RegisterMetaTools()
|
registry.RegisterMetaTools()
|
||||||
|
|
||||||
callTool := tools.NewToolCallTool(registry)
|
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",
|
"tool_name": "nonexistent_tool_xyz",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -394,17 +413,19 @@ func TestToolCall_RejectsUnknownTool(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCall_MissingToolName(t *testing.T) {
|
func TestToolCall_MissingToolName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
registry := tools.NewToolRegistry()
|
registry := tools.NewToolRegistry()
|
||||||
registry.RegisterMetaTools()
|
registry.RegisterMetaTools()
|
||||||
|
|
||||||
callTool := tools.NewToolCallTool(registry)
|
callTool := tools.NewToolCallTool(registry)
|
||||||
result := callTool.Execute(context.Background(), map[string]interface{}{})
|
result := callTool.Execute(t.Context(), map[string]interface{}{})
|
||||||
|
|
||||||
require.NotNil(t, result)
|
require.NotNil(t, result)
|
||||||
assert.True(t, result.IsError, "missing tool_name should be rejected")
|
assert.True(t, result.IsError, "missing tool_name should be rejected")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCall_StringArguments(t *testing.T) {
|
func TestToolCall_StringArguments(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
os.WriteFile(filepath.Join(workspace, "str_args.txt"), []byte("string args ok"), 0644)
|
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()
|
registry.RegisterMetaTools()
|
||||||
|
|
||||||
callTool := tools.NewToolCallTool(registry)
|
callTool := tools.NewToolCallTool(registry)
|
||||||
result := callTool.Execute(context.Background(), map[string]interface{}{
|
result := callTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "read_file",
|
"tool_name": "read_file",
|
||||||
"arguments": `{"path": "str_args.txt"}`,
|
"arguments": `{"path": "str_args.txt"}`,
|
||||||
})
|
})
|
||||||
|
|
@ -429,6 +450,7 @@ func TestToolCall_StringArguments(t *testing.T) {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func TestToolRegistry_GatewayMarking(t *testing.T) {
|
func TestToolRegistry_GatewayMarking(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
registry := tools.NewToolRegistry()
|
registry := tools.NewToolRegistry()
|
||||||
|
|
@ -458,6 +480,7 @@ func TestToolRegistry_GatewayMarking(t *testing.T) {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func TestConfig_DefaultValues(t *testing.T) {
|
func TestConfig_DefaultValues(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := config.DefaultConfig()
|
cfg := config.DefaultConfig()
|
||||||
|
|
||||||
assert.True(t, cfg.Agents.Defaults.RestrictToSandbox, "restrict to sandbox should be on by default")
|
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) {
|
func TestConfig_LoadEvalConfigs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
evalDir := filepath.Join("..", "..", "eval", "configs")
|
evalDir := filepath.Join("..", "..", "eval", "configs")
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
@ -492,6 +516,7 @@ func TestConfig_LoadEvalConfigs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfig_MissingFileReturnsDefaults(t *testing.T) {
|
func TestConfig_MissingFileReturnsDefaults(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg, err := config.LoadConfig("/nonexistent/path/config.json")
|
cfg, err := config.LoadConfig("/nonexistent/path/config.json")
|
||||||
require.NoError(t, err, "missing config should return defaults, not error")
|
require.NoError(t, err, "missing config should return defaults, not error")
|
||||||
assert.Equal(t, 768, cfg.Memory.EmbeddingDims, "should have default embedding dims")
|
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) {
|
func TestToolExecution_ExecTimeout(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
execTool := tools.NewExecTool(workspace, false)
|
execTool := tools.NewExecTool(workspace, false)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
ctx, cancel := context.WithTimeout(t.Context(), 1)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
result := execTool.Execute(ctx, map[string]interface{}{
|
result := execTool.Execute(ctx, map[string]interface{}{
|
||||||
|
|
@ -516,10 +542,11 @@ func TestToolExecution_ExecTimeout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolExecution_ExecEmptyCommand(t *testing.T) {
|
func TestToolExecution_ExecEmptyCommand(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
execTool := tools.NewExecTool(workspace, false)
|
execTool := tools.NewExecTool(workspace, false)
|
||||||
result := execTool.Execute(context.Background(), map[string]interface{}{
|
result := execTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"command": "",
|
"command": "",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -532,6 +559,7 @@ func TestToolExecution_ExecEmptyCommand(t *testing.T) {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func TestToolSchema_JSONRoundtrip(t *testing.T) {
|
func TestToolSchema_JSONRoundtrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
workspace := testWorkspace(t)
|
workspace := testWorkspace(t)
|
||||||
|
|
||||||
for _, tool := range allFileTools(workspace) {
|
for _, tool := range allFileTools(workspace) {
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@ package fantasy
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -111,7 +112,7 @@ func TestStreamingAgentCallbacks(t *testing.T) {
|
||||||
// Create agent
|
// Create agent
|
||||||
agent := NewAgent(mockModel)
|
agent := NewAgent(mockModel)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Create streaming call with all callbacks
|
// Create streaming call with all callbacks
|
||||||
streamCall := AgentStreamCall{
|
streamCall := AgentStreamCall{
|
||||||
|
|
@ -301,7 +302,7 @@ func TestStreamingAgentWithTools(t *testing.T) {
|
||||||
WithTools(&EchoTool{}),
|
WithTools(&EchoTool{}),
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Track callback invocations
|
// Track callback invocations
|
||||||
var toolInputStartCalled bool
|
var toolInputStartCalled bool
|
||||||
|
|
@ -399,7 +400,7 @@ func TestStreamingAgentTextDeltas(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(mockModel)
|
agent := NewAgent(mockModel)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Track text deltas
|
// Track text deltas
|
||||||
var textDeltas []string
|
var textDeltas []string
|
||||||
|
|
@ -461,7 +462,7 @@ func TestStreamingAgentReasoning(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(mockModel)
|
agent := NewAgent(mockModel)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
var reasoningDeltas []string
|
var reasoningDeltas []string
|
||||||
var textDeltas []string
|
var textDeltas []string
|
||||||
|
|
@ -502,7 +503,7 @@ func TestStreamingAgentError(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(mockModel)
|
agent := NewAgent(mockModel)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Track error callbacks
|
// Track error callbacks
|
||||||
var errorOccurred bool
|
var errorOccurred bool
|
||||||
|
|
@ -568,7 +569,7 @@ func TestStreamingAgentSources(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(mockModel)
|
agent := NewAgent(mockModel)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
var sources []SourceContent
|
var sources []SourceContent
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ func TestAgent_Generate_ResultContent_AllTypes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool1))
|
agent := NewAgent(model, WithTools(tool1))
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "prompt",
|
Prompt: "prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -209,7 +209,7 @@ func TestAgent_Generate_ResultText(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(model)
|
agent := NewAgent(model)
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "prompt",
|
Prompt: "prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -279,7 +279,7 @@ func TestAgent_Generate_ResultToolCalls(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool1, tool2))
|
agent := NewAgent(model, WithTools(tool1, tool2))
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test-input",
|
Prompt: "test-input",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -353,7 +353,7 @@ func TestAgent_Generate_ResultToolResults(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool1))
|
agent := NewAgent(model, WithTools(tool1))
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test-input",
|
Prompt: "test-input",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -440,7 +440,7 @@ func TestAgent_Generate_MultipleSteps(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool1))
|
agent := NewAgent(model, WithTools(tool1))
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test-input",
|
Prompt: "test-input",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -499,7 +499,7 @@ func TestAgent_Generate_BasicText(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(model)
|
agent := NewAgent(model)
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test prompt",
|
Prompt: "test prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -531,7 +531,7 @@ func TestAgent_Generate_EmptyPrompt(t *testing.T) {
|
||||||
model := &mockLanguageModel{}
|
model := &mockLanguageModel{}
|
||||||
agent := NewAgent(model)
|
agent := NewAgent(model)
|
||||||
|
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "", // Empty prompt should cause error
|
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"))
|
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",
|
Prompt: "test prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -623,7 +623,7 @@ func TestAgent_Generate_OptionsActiveTools(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool1, tool2))
|
agent := NewAgent(model, WithTools(tool1, tool2))
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test-input",
|
Prompt: "test-input",
|
||||||
ActiveTools: []string{"tool1"}, // Only tool1 should be active
|
ActiveTools: []string{"tool1"}, // Only tool1 should be active
|
||||||
})
|
})
|
||||||
|
|
@ -872,7 +872,7 @@ func TestStopConditions_Integration(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithStopConditions(StepCountIs(1)))
|
agent := NewAgent(model, WithStopConditions(StepCountIs(1)))
|
||||||
|
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test prompt",
|
Prompt: "test prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -904,7 +904,7 @@ func TestStopConditions_Integration(t *testing.T) {
|
||||||
FinishReasonIs(FinishReasonStop), // Or stop on finish reason
|
FinishReasonIs(FinishReasonStop), // Or stop on finish reason
|
||||||
))
|
))
|
||||||
|
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test prompt",
|
Prompt: "test prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -952,7 +952,7 @@ func TestPrepareStep(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithSystemPrompt("Original system prompt"))
|
agent := NewAgent(model, WithSystemPrompt("Original system prompt"))
|
||||||
|
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test prompt",
|
Prompt: "test prompt",
|
||||||
PrepareStep: prepareStepFunc,
|
PrepareStep: prepareStepFunc,
|
||||||
})
|
})
|
||||||
|
|
@ -989,7 +989,7 @@ func TestPrepareStep(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model)
|
agent := NewAgent(model)
|
||||||
|
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test prompt",
|
Prompt: "test prompt",
|
||||||
PrepareStep: prepareStepFunc,
|
PrepareStep: prepareStepFunc,
|
||||||
})
|
})
|
||||||
|
|
@ -1034,7 +1034,7 @@ func TestPrepareStep(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool1, tool2, tool3))
|
agent := NewAgent(model, WithTools(tool1, tool2, tool3))
|
||||||
|
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test prompt",
|
Prompt: "test prompt",
|
||||||
PrepareStep: prepareStepFunc,
|
PrepareStep: prepareStepFunc,
|
||||||
})
|
})
|
||||||
|
|
@ -1073,7 +1073,7 @@ func TestPrepareStep(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool1))
|
agent := NewAgent(model, WithTools(tool1))
|
||||||
|
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test prompt",
|
Prompt: "test prompt",
|
||||||
PrepareStep: prepareStepFunc,
|
PrepareStep: prepareStepFunc,
|
||||||
})
|
})
|
||||||
|
|
@ -1133,7 +1133,7 @@ func TestPrepareStep(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithSystemPrompt("Original system"), WithTools(tool1, tool2))
|
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",
|
Prompt: "test prompt",
|
||||||
PrepareStep: prepareStepFunc,
|
PrepareStep: prepareStepFunc,
|
||||||
})
|
})
|
||||||
|
|
@ -1194,7 +1194,7 @@ func TestPrepareStep(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithSystemPrompt("Parent system"), WithTools(tool1))
|
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",
|
Prompt: "test prompt",
|
||||||
PrepareStep: prepareStepFunc,
|
PrepareStep: prepareStepFunc,
|
||||||
})
|
})
|
||||||
|
|
@ -1240,7 +1240,7 @@ func TestPrepareStep(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool1, tool2))
|
agent := NewAgent(model, WithTools(tool1, tool2))
|
||||||
|
|
||||||
result, err := agent.Generate(context.Background(), AgentCall{
|
result, err := agent.Generate(t.Context(), AgentCall{
|
||||||
Prompt: "test prompt",
|
Prompt: "test prompt",
|
||||||
PrepareStep: prepareStepFunc,
|
PrepareStep: prepareStepFunc,
|
||||||
})
|
})
|
||||||
|
|
@ -1289,7 +1289,7 @@ func TestToolCallRepair(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool), WithStopConditions(StepCountIs(2))) // Limit steps
|
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",
|
Prompt: "test prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1333,7 +1333,7 @@ func TestToolCallRepair(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool), WithStopConditions(StepCountIs(2))) // Limit steps
|
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",
|
Prompt: "test prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1388,7 +1388,7 @@ func TestToolCallRepair(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool), WithRepairToolCall(repairFunc), WithStopConditions(StepCountIs(2)))
|
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",
|
Prompt: "test prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1438,7 +1438,7 @@ func TestToolCallRepair(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool), WithRepairToolCall(repairFunc), WithStopConditions(StepCountIs(2)))
|
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",
|
Prompt: "test prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1476,7 +1476,7 @@ func TestToolCallRepair(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool), WithStopConditions(StepCountIs(2)))
|
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",
|
Prompt: "test prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1521,7 +1521,7 @@ func TestToolCallRepair(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(tool), WithStopConditions(StepCountIs(2)))
|
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",
|
Prompt: "test prompt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1582,7 +1582,7 @@ func TestAgent_MediaToolResponses(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(imageTool), WithStopConditions(StepCountIs(3)))
|
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",
|
Prompt: "Generate an image",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1636,7 +1636,7 @@ func TestAgent_MediaToolResponses(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(audioTool), WithStopConditions(StepCountIs(3)))
|
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",
|
Prompt: "Generate audio",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1690,7 +1690,7 @@ func TestAgent_MediaToolResponses(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(imageTool), WithStopConditions(StepCountIs(3)))
|
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",
|
Prompt: "Take a screenshot",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1749,7 +1749,7 @@ func TestAgent_MediaToolResponses(t *testing.T) {
|
||||||
|
|
||||||
agent := NewAgent(model, WithTools(imageTool), WithStopConditions(StepCountIs(3)))
|
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",
|
Prompt: "Generate image",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMessageJSONSerialization(t *testing.T) {
|
func TestMessageJSONSerialization(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
message Message
|
message Message
|
||||||
|
|
@ -199,44 +201,29 @@ func compareMessagePart(t *testing.T, index int, original, decoded MessagePart)
|
||||||
case ContentTypeText:
|
case ContentTypeText:
|
||||||
orig := original.(TextPart)
|
orig := original.(TextPart)
|
||||||
dec := decoded.(TextPart)
|
dec := decoded.(TextPart)
|
||||||
if orig.Text != dec.Text {
|
if diff := cmp.Diff(orig, dec); diff != "" {
|
||||||
t.Errorf("content[%d] text mismatch: got %q, want %q", index, dec.Text, orig.Text)
|
t.Errorf("content[%d] TextPart mismatch (-want +got):\n%s", index, diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ContentTypeReasoning:
|
case ContentTypeReasoning:
|
||||||
orig := original.(ReasoningPart)
|
orig := original.(ReasoningPart)
|
||||||
dec := decoded.(ReasoningPart)
|
dec := decoded.(ReasoningPart)
|
||||||
if orig.Text != dec.Text {
|
if diff := cmp.Diff(orig, dec); diff != "" {
|
||||||
t.Errorf("content[%d] reasoning text mismatch: got %q, want %q", index, dec.Text, orig.Text)
|
t.Errorf("content[%d] ReasoningPart mismatch (-want +got):\n%s", index, diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ContentTypeFile:
|
case ContentTypeFile:
|
||||||
orig := original.(FilePart)
|
orig := original.(FilePart)
|
||||||
dec := decoded.(FilePart)
|
dec := decoded.(FilePart)
|
||||||
if orig.Filename != dec.Filename {
|
if diff := cmp.Diff(orig, dec); diff != "" {
|
||||||
t.Errorf("content[%d] filename mismatch: got %q, want %q", index, dec.Filename, orig.Filename)
|
t.Errorf("content[%d] FilePart mismatch (-want +got):\n%s", index, diff)
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case ContentTypeToolCall:
|
case ContentTypeToolCall:
|
||||||
orig := original.(ToolCallPart)
|
orig := original.(ToolCallPart)
|
||||||
dec := decoded.(ToolCallPart)
|
dec := decoded.(ToolCallPart)
|
||||||
if orig.ToolCallID != dec.ToolCallID {
|
if diff := cmp.Diff(orig, dec); diff != "" {
|
||||||
t.Errorf("content[%d] tool call id mismatch: got %q, want %q", index, dec.ToolCallID, orig.ToolCallID)
|
t.Errorf("content[%d] ToolCallPart mismatch (-want +got):\n%s", index, diff)
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case ContentTypeToolResult:
|
case ContentTypeToolResult:
|
||||||
|
|
@ -259,30 +246,35 @@ func compareToolResultOutput(t *testing.T, index int, original, decoded ToolResu
|
||||||
case ToolResultContentTypeText:
|
case ToolResultContentTypeText:
|
||||||
orig := original.(ToolResultOutputContentText)
|
orig := original.(ToolResultOutputContentText)
|
||||||
dec := decoded.(ToolResultOutputContentText)
|
dec := decoded.(ToolResultOutputContentText)
|
||||||
if orig.Text != dec.Text {
|
if diff := cmp.Diff(orig, dec); diff != "" {
|
||||||
t.Errorf("content[%d] tool result text mismatch: got %q, want %q", index, dec.Text, orig.Text)
|
t.Errorf("content[%d] ToolResultOutputContentText mismatch (-want +got):\n%s", index, diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ToolResultContentTypeError:
|
case ToolResultContentTypeError:
|
||||||
orig := original.(ToolResultOutputContentError)
|
orig := original.(ToolResultOutputContentError)
|
||||||
dec := decoded.(ToolResultOutputContentError)
|
dec := decoded.(ToolResultOutputContentError)
|
||||||
if orig.Error.Error() != dec.Error.Error() {
|
if orig.Error == nil && dec.Error == nil {
|
||||||
t.Errorf("content[%d] tool result error mismatch: got %q, want %q", index, dec.Error.Error(), orig.Error.Error())
|
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:
|
case ToolResultContentTypeMedia:
|
||||||
orig := original.(ToolResultOutputContentMedia)
|
orig := original.(ToolResultOutputContentMedia)
|
||||||
dec := decoded.(ToolResultOutputContentMedia)
|
dec := decoded.(ToolResultOutputContentMedia)
|
||||||
if orig.Data != dec.Data {
|
if diff := cmp.Diff(orig, dec); diff != "" {
|
||||||
t.Errorf("content[%d] tool result media data mismatch", index)
|
t.Errorf("content[%d] ToolResultOutputContentMedia mismatch (-want +got):\n%s", index, diff)
|
||||||
}
|
|
||||||
if orig.MediaType != dec.MediaType {
|
|
||||||
t.Errorf("content[%d] tool result media type mismatch: got %q, want %q", index, dec.MediaType, orig.MediaType)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelperFunctions(t *testing.T) {
|
func TestHelperFunctions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("NewUserMessage - text only", func(t *testing.T) {
|
t.Run("NewUserMessage - text only", func(t *testing.T) {
|
||||||
msg := NewUserMessage("Hello")
|
msg := NewUserMessage("Hello")
|
||||||
|
|
||||||
|
|
@ -412,6 +404,7 @@ func TestHelperFunctions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEdgeCases(t *testing.T) {
|
func TestEdgeCases(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("empty text part", func(t *testing.T) {
|
t.Run("empty text part", func(t *testing.T) {
|
||||||
msg := Message{
|
msg := Message{
|
||||||
Role: MessageRoleUser,
|
Role: MessageRoleUser,
|
||||||
|
|
@ -520,6 +513,7 @@ func TestEdgeCases(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidJSONHandling(t *testing.T) {
|
func TestInvalidJSONHandling(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("unknown message part type", func(t *testing.T) {
|
t.Run("unknown message part type", func(t *testing.T) {
|
||||||
invalidJSON := `{
|
invalidJSON := `{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
|
|
@ -606,6 +600,7 @@ func (m *mockProviderData) UnmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPromptSerialization(t *testing.T) {
|
func TestPromptSerialization(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("serialize prompt (message slice)", func(t *testing.T) {
|
t.Run("serialize prompt (message slice)", func(t *testing.T) {
|
||||||
prompt := Prompt{
|
prompt := Prompt{
|
||||||
NewSystemMessage("You are helpful"),
|
NewSystemMessage("You are helpful"),
|
||||||
|
|
@ -647,6 +642,7 @@ func TestPromptSerialization(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStreamPartErrorSerialization(t *testing.T) {
|
func TestStreamPartErrorSerialization(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("stream part with ProviderError containing OpenAI API error", func(t *testing.T) {
|
t.Run("stream part with ProviderError containing OpenAI API error", func(t *testing.T) {
|
||||||
// Create a mock OpenAI API error
|
// Create a mock OpenAI API error
|
||||||
openaiErr := errors.New("invalid_api_key: Incorrect API key provided")
|
openaiErr := errors.New("invalid_api_key: Incorrect API key provided")
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRepairJSON(t *testing.T) {
|
func TestRepairJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -79,6 +80,7 @@ func TestRepairJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepairJSONMultipleTopLevel(t *testing.T) {
|
func TestRepairJSONMultipleTopLevel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -125,6 +127,7 @@ func TestRepairJSONMultipleTopLevel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepairJSONEnsureASCII(t *testing.T) {
|
func TestRepairJSONEnsureASCII(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
got, err := RepairJSON("{'test_中国人_ascii':'统一码'}", WithEnsureASCII(false))
|
got, err := RepairJSON("{'test_中国人_ascii':'统一码'}", WithEnsureASCII(false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
|
@ -136,6 +139,7 @@ func TestRepairJSONEnsureASCII(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepairJSONStreamStable(t *testing.T) {
|
func TestRepairJSONStreamStable(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -197,6 +201,7 @@ func TestRepairJSONStreamStable(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoads(t *testing.T) {
|
func TestLoads(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -273,6 +278,7 @@ func TestLoads(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepairJSONSkipJSONLoads(t *testing.T) {
|
func TestRepairJSONSkipJSONLoads(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -320,6 +326,7 @@ func TestRepairJSONSkipJSONLoads(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepairJSONWithLog(t *testing.T) {
|
func TestRepairJSONWithLog(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -368,6 +375,7 @@ func TestRepairJSONWithLog(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepairJSONStrict(t *testing.T) {
|
func TestRepairJSONStrict(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -438,6 +446,7 @@ func TestRepairJSONStrict(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseArrayObjects(t *testing.T) {
|
func TestParseArrayObjects(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -479,6 +488,7 @@ func TestParseArrayObjects(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseArrayEdgeCases(t *testing.T) {
|
func TestParseArrayEdgeCases(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -620,6 +630,7 @@ func TestParseArrayEdgeCases(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseArrayMissingQuotes(t *testing.T) {
|
func TestParseArrayMissingQuotes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -656,6 +667,7 @@ func TestParseArrayMissingQuotes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseComment(t *testing.T) {
|
func TestParseComment(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -712,6 +724,7 @@ func TestParseComment(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseNumber(t *testing.T) {
|
func TestParseNumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -757,6 +770,7 @@ func TestParseNumber(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseNumberEdgeCases(t *testing.T) {
|
func TestParseNumberEdgeCases(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -863,6 +877,7 @@ func TestParseNumberEdgeCases(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseObjectObjects(t *testing.T) {
|
func TestParseObjectObjects(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -912,6 +927,7 @@ func TestParseObjectObjects(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseObjectEdgeCases(t *testing.T) {
|
func TestParseObjectEdgeCases(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -1118,6 +1134,7 @@ func TestParseObjectEdgeCases(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseObjectMergeAtEnd(t *testing.T) {
|
func TestParseObjectMergeAtEnd(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -1179,6 +1196,7 @@ func TestParseObjectMergeAtEnd(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseStringBasics(t *testing.T) {
|
func TestParseStringBasics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -1225,6 +1243,7 @@ func TestParseStringBasics(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMissingAndMixedQuotes(t *testing.T) {
|
func TestMissingAndMixedQuotes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -1346,6 +1365,7 @@ func TestMissingAndMixedQuotes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEscaping(t *testing.T) {
|
func TestEscaping(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -1416,6 +1436,7 @@ func TestEscaping(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkdown(t *testing.T) {
|
func TestMarkdown(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -1452,6 +1473,7 @@ func TestMarkdown(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLeadingTrailingCharacters(t *testing.T) {
|
func TestLeadingTrailingCharacters(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -1493,6 +1515,7 @@ func TestLeadingTrailingCharacters(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringJSONLLMBlock(t *testing.T) {
|
func TestStringJSONLLMBlock(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -1534,6 +1557,7 @@ func TestStringJSONLLMBlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseBooleanOrNull(t *testing.T) {
|
func TestParseBooleanOrNull(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
loadCases := []struct {
|
loadCases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseAzureURL(t *testing.T) {
|
func TestParseAzureURL(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package openai
|
package openai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -817,7 +816,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -850,7 +849,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -875,7 +874,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -916,7 +915,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -943,7 +942,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
LogProbs: fantasy.Opt(true),
|
LogProbs: fantasy.Opt(true),
|
||||||
|
|
@ -978,7 +977,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1003,7 +1002,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1028,7 +1027,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1061,7 +1060,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
LogitBias: map[string]int64{
|
LogitBias: map[string]int64{
|
||||||
|
|
@ -1104,7 +1103,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "o1-mini")
|
model, _ := provider.LanguageModel(t.Context(), "o1-mini")
|
||||||
|
|
||||||
_, err = model.Generate(context.Background(), fantasy.Call{
|
_, err = model.Generate(t.Context(), fantasy.Call{
|
||||||
Prompt: testPrompt,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(
|
ProviderOptions: NewProviderOptions(
|
||||||
&ProviderOptions{
|
&ProviderOptions{
|
||||||
|
|
@ -1145,7 +1144,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-4o")
|
model, _ := provider.LanguageModel(t.Context(), "gpt-4o")
|
||||||
|
|
||||||
_, err = model.Generate(context.Background(), fantasy.Call{
|
_, err = model.Generate(t.Context(), fantasy.Call{
|
||||||
Prompt: testPrompt,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
TextVerbosity: fantasy.Opt("low"),
|
TextVerbosity: fantasy.Opt("low"),
|
||||||
|
|
@ -1184,7 +1183,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
Tools: []fantasy.Tool{
|
Tools: []fantasy.Tool{
|
||||||
fantasy.FunctionTool{
|
fantasy.FunctionTool{
|
||||||
|
|
@ -1257,7 +1256,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
Tools: []fantasy.Tool{
|
Tools: []fantasy.Tool{
|
||||||
fantasy.FunctionTool{
|
fantasy.FunctionTool{
|
||||||
|
|
@ -1305,7 +1304,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
Tools: []fantasy.Tool{
|
Tools: []fantasy.Tool{
|
||||||
fantasy.FunctionTool{
|
fantasy.FunctionTool{
|
||||||
|
|
@ -1375,7 +1374,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1418,7 +1417,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1454,7 +1453,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1483,7 +1482,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
|
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,
|
Prompt: testPrompt,
|
||||||
Temperature: &[]float64{0.5}[0],
|
Temperature: &[]float64{0.5}[0],
|
||||||
TopP: &[]float64{0.7}[0],
|
TopP: &[]float64{0.7}[0],
|
||||||
|
|
@ -1532,7 +1531,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
|
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
|
||||||
|
|
||||||
_, err = model.Generate(context.Background(), fantasy.Call{
|
_, err = model.Generate(t.Context(), fantasy.Call{
|
||||||
Prompt: testPrompt,
|
Prompt: testPrompt,
|
||||||
MaxOutputTokens: &[]int64{1000}[0],
|
MaxOutputTokens: &[]int64{1000}[0],
|
||||||
})
|
})
|
||||||
|
|
@ -1577,7 +1576,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1605,7 +1604,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
|
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
|
||||||
|
|
||||||
_, err = model.Generate(context.Background(), fantasy.Call{
|
_, err = model.Generate(t.Context(), fantasy.Call{
|
||||||
Prompt: testPrompt,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
MaxCompletionTokens: fantasy.Opt(int64(255)),
|
MaxCompletionTokens: fantasy.Opt(int64(255)),
|
||||||
|
|
@ -1644,7 +1643,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
Prediction: map[string]any{
|
Prediction: map[string]any{
|
||||||
|
|
@ -1689,7 +1688,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
Store: fantasy.Opt(true),
|
Store: fantasy.Opt(true),
|
||||||
|
|
@ -1728,7 +1727,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
Metadata: map[string]any{
|
Metadata: map[string]any{
|
||||||
|
|
@ -1771,7 +1770,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
PromptCacheKey: fantasy.Opt("test-cache-key-123"),
|
PromptCacheKey: fantasy.Opt("test-cache-key-123"),
|
||||||
|
|
@ -1810,7 +1809,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
SafetyIdentifier: fantasy.Opt("test-safety-identifier-123"),
|
SafetyIdentifier: fantasy.Opt("test-safety-identifier-123"),
|
||||||
|
|
@ -1847,7 +1846,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-search-preview")
|
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,
|
Prompt: testPrompt,
|
||||||
Temperature: &[]float64{0.7}[0],
|
Temperature: &[]float64{0.7}[0],
|
||||||
})
|
})
|
||||||
|
|
@ -1882,7 +1881,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "o3-mini")
|
model, _ := provider.LanguageModel(t.Context(), "o3-mini")
|
||||||
|
|
||||||
_, err = model.Generate(context.Background(), fantasy.Call{
|
_, err = model.Generate(t.Context(), fantasy.Call{
|
||||||
Prompt: testPrompt,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
ServiceTier: fantasy.Opt("flex"),
|
ServiceTier: fantasy.Opt("flex"),
|
||||||
|
|
@ -1919,7 +1918,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
ServiceTier: fantasy.Opt("flex"),
|
ServiceTier: fantasy.Opt("flex"),
|
||||||
|
|
@ -1953,7 +1952,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
|
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
|
||||||
|
|
||||||
_, err = model.Generate(context.Background(), fantasy.Call{
|
_, err = model.Generate(t.Context(), fantasy.Call{
|
||||||
Prompt: testPrompt,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
ServiceTier: fantasy.Opt("priority"),
|
ServiceTier: fantasy.Opt("priority"),
|
||||||
|
|
@ -1990,7 +1989,7 @@ func TestDoGenerate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
ServiceTier: fantasy.Opt("priority"),
|
ServiceTier: fantasy.Opt("priority"),
|
||||||
|
|
@ -2298,7 +2297,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -2355,7 +2354,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
Tools: []fantasy.Tool{
|
Tools: []fantasy.Tool{
|
||||||
fantasy.FunctionTool{
|
fantasy.FunctionTool{
|
||||||
|
|
@ -2442,7 +2441,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -2482,7 +2481,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -2524,7 +2523,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -2573,7 +2572,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -2624,7 +2623,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -2668,7 +2667,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
Store: fantasy.Opt(true),
|
Store: fantasy.Opt(true),
|
||||||
|
|
@ -2711,7 +2710,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-3.5-turbo")
|
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,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
Metadata: map[string]any{
|
Metadata: map[string]any{
|
||||||
|
|
@ -2758,7 +2757,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "o3-mini")
|
model, _ := provider.LanguageModel(t.Context(), "o3-mini")
|
||||||
|
|
||||||
_, err = model.Stream(context.Background(), fantasy.Call{
|
_, err = model.Stream(t.Context(), fantasy.Call{
|
||||||
Prompt: testPrompt,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
ServiceTier: fantasy.Opt("flex"),
|
ServiceTier: fantasy.Opt("flex"),
|
||||||
|
|
@ -2801,7 +2800,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
|
model, _ := provider.LanguageModel(t.Context(), "gpt-4o-mini")
|
||||||
|
|
||||||
_, err = model.Stream(context.Background(), fantasy.Call{
|
_, err = model.Stream(t.Context(), fantasy.Call{
|
||||||
Prompt: testPrompt,
|
Prompt: testPrompt,
|
||||||
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
ProviderOptions: NewProviderOptions(&ProviderOptions{
|
||||||
ServiceTier: fantasy.Opt("priority"),
|
ServiceTier: fantasy.Opt("priority"),
|
||||||
|
|
@ -2845,7 +2844,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -2892,7 +2891,7 @@ func TestDoStream(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model, _ := provider.LanguageModel(t.Context(), "o1-preview")
|
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,
|
Prompt: testPrompt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ var anthropicTestModels = []testModel{
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnthropicCommon(t *testing.T) {
|
func TestAnthropicCommon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range anthropicTestModels {
|
for _, m := range anthropicTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, anthropicBuilder(m.model), nil, nil})
|
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) {
|
func TestAnthropicCommonWithCacheControl(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range anthropicTestModels {
|
for _, m := range anthropicTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, anthropicBuilder(m.model), nil, addAnthropicCaching})
|
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) {
|
func TestAnthropicThinking(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
opts := fantasy.ProviderOptions{
|
opts := fantasy.ProviderOptions{
|
||||||
anthropic.Name: &anthropic.ProviderOptions{
|
anthropic.Name: &anthropic.ProviderOptions{
|
||||||
Thinking: &anthropic.ThinkingProviderOption{
|
Thinking: &anthropic.ThinkingProviderOption{
|
||||||
|
|
@ -82,6 +85,7 @@ func TestAnthropicThinking(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnthropicThinkingWithCacheControl(t *testing.T) {
|
func TestAnthropicThinkingWithCacheControl(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
opts := fantasy.ProviderOptions{
|
opts := fantasy.ProviderOptions{
|
||||||
anthropic.Name: &anthropic.ProviderOptions{
|
anthropic.Name: &anthropic.ProviderOptions{
|
||||||
Thinking: &anthropic.ThinkingProviderOption{
|
Thinking: &anthropic.ThinkingProviderOption{
|
||||||
|
|
@ -100,6 +104,7 @@ func TestAnthropicThinkingWithCacheControl(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnthropicObjectGeneration(t *testing.T) {
|
func TestAnthropicObjectGeneration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range anthropicTestModels {
|
for _, m := range anthropicTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, anthropicBuilder(m.model), nil, nil})
|
pairs = append(pairs, builderPair{m.name, anthropicBuilder(m.model), nil, nil})
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAzureResponsesCommon(t *testing.T) {
|
func TestAzureResponsesCommon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
models := []testModel{
|
models := []testModel{
|
||||||
{"azure-gpt-5-mini", "gpt-5-mini", true},
|
{"azure-gpt-5-mini", "gpt-5-mini", true},
|
||||||
|
|
@ -41,6 +42,7 @@ func azureReasoningBuilder(model string) builderFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAzureResponsesWithSummaryThinking(t *testing.T) {
|
func TestAzureResponsesWithSummaryThinking(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
opts := fantasy.ProviderOptions{
|
opts := fantasy.ProviderOptions{
|
||||||
openai.Name: &openai.ResponsesProviderOptions{
|
openai.Name: &openai.ResponsesProviderOptions{
|
||||||
Include: []openai.IncludeType{
|
Include: []openai.IncludeType{
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
const defaultBaseURL = "https://fantasy-playground-resource.openai.azure.com"
|
const defaultBaseURL = "https://fantasy-playground-resource.openai.azure.com"
|
||||||
|
|
||||||
func TestAzureCommon(t *testing.T) {
|
func TestAzureCommon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testCommon(t, []builderPair{
|
testCommon(t, []builderPair{
|
||||||
{"azure-o4-mini", builderAzureO4Mini, nil, nil},
|
{"azure-o4-mini", builderAzureO4Mini, nil, nil},
|
||||||
{"azure-gpt-5-mini", builderAzureGpt5Mini, nil, nil},
|
{"azure-gpt-5-mini", builderAzureGpt5Mini, nil, nil},
|
||||||
|
|
@ -24,6 +25,7 @@ func TestAzureCommon(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAzureThinking(t *testing.T) {
|
func TestAzureThinking(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
opts := fantasy.ProviderOptions{
|
opts := fantasy.ProviderOptions{
|
||||||
openai.Name: &openai.ProviderOptions{
|
openai.Name: &openai.ProviderOptions{
|
||||||
ReasoningEffort: openai.ReasoningEffortOption(openai.ReasoningEffortHigh),
|
ReasoningEffort: openai.ReasoningEffortOption(openai.ReasoningEffortHigh),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBedrockCommon(t *testing.T) {
|
func TestBedrockCommon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testCommon(t, []builderPair{
|
testCommon(t, []builderPair{
|
||||||
{"bedrock-anthropic-claude-3-sonnet", builderBedrockClaude3Sonnet, nil, nil},
|
{"bedrock-anthropic-claude-3-sonnet", builderBedrockClaude3Sonnet, nil, nil},
|
||||||
{"bedrock-anthropic-claude-3-opus", builderBedrockClaude3Opus, nil, nil},
|
{"bedrock-anthropic-claude-3-opus", builderBedrockClaude3Opus, nil, nil},
|
||||||
|
|
@ -19,6 +20,7 @@ func TestBedrockCommon(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBedrockBasicAuth(t *testing.T) {
|
func TestBedrockBasicAuth(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testSimple(t, builderPair{"bedrock-anthropic-claude-3-sonnet", buildersBedrockBasicAuth, nil, nil})
|
testSimple(t, builderPair{"bedrock-anthropic-claude-3-sonnet", buildersBedrockBasicAuth, nil, nil})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ var vertexTestModels = []testModel{
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGoogleCommon(t *testing.T) {
|
func TestGoogleCommon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range geminiTestModels {
|
for _, m := range geminiTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, geminiBuilder(m.model), nil, nil})
|
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) {
|
func TestGoogleThinking(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
opts := fantasy.ProviderOptions{
|
opts := fantasy.ProviderOptions{
|
||||||
google.Name: &google.ProviderOptions{
|
google.Name: &google.ProviderOptions{
|
||||||
ThinkingConfig: &google.ThinkingConfig{
|
ThinkingConfig: &google.ThinkingConfig{
|
||||||
|
|
@ -57,6 +59,7 @@ func TestGoogleThinking(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGoogleObjectGeneration(t *testing.T) {
|
func TestGoogleObjectGeneration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range geminiTestModels {
|
for _, m := range geminiTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, geminiBuilder(m.model), nil, nil})
|
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) {
|
func TestGoogleVertexObjectGeneration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range vertexTestModels {
|
for _, m := range vertexTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, vertexBuilder(m.model), nil, nil})
|
pairs = append(pairs, builderPair{m.name, vertexBuilder(m.model), nil, nil})
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ func geminiImageBuilder(model string) builderFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImageUploadAgent(t *testing.T) {
|
func TestImageUploadAgent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pairs := []builderPair{
|
pairs := []builderPair{
|
||||||
{
|
{
|
||||||
name: "anthropic-claude-sonnet-4",
|
name: "anthropic-claude-sonnet-4",
|
||||||
|
|
@ -100,6 +101,7 @@ func TestImageUploadAgent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImageUploadAgentStreaming(t *testing.T) {
|
func TestImageUploadAgentStreaming(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pairs := []builderPair{
|
pairs := []builderPair{
|
||||||
{
|
{
|
||||||
name: "anthropic-claude-sonnet-4",
|
name: "anthropic-claude-sonnet-4",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOpenAIResponsesCommon(t *testing.T) {
|
func TestOpenAIResponsesCommon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range openaiTestModels {
|
for _, m := range openaiTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, openAIReasoningBuilder(m.model), nil, nil})
|
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) {
|
func TestOpenAIResponsesWithSummaryThinking(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
opts := fantasy.ProviderOptions{
|
opts := fantasy.ProviderOptions{
|
||||||
openai.Name: &openai.ResponsesProviderOptions{
|
openai.Name: &openai.ResponsesProviderOptions{
|
||||||
Include: []openai.IncludeType{
|
Include: []openai.IncludeType{
|
||||||
|
|
@ -54,6 +56,7 @@ func TestOpenAIResponsesWithSummaryThinking(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenAIResponsesObjectGeneration(t *testing.T) {
|
func TestOpenAIResponsesObjectGeneration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range openaiTestModels {
|
for _, m := range openaiTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, openAIReasoningBuilder(m.model), nil, nil})
|
pairs = append(pairs, builderPair{m.name, openAIReasoningBuilder(m.model), nil, nil})
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ var openaiTestModels = []testModel{
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenAICommon(t *testing.T) {
|
func TestOpenAICommon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range openaiTestModels {
|
for _, m := range openaiTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, openAIBuilder(m.model), nil, nil})
|
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) {
|
func TestOpenAIObjectGeneration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range openaiTestModels {
|
for _, m := range openaiTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, openAIBuilder(m.model), nil, nil})
|
pairs = append(pairs, builderPair{m.name, openAIBuilder(m.model), nil, nil})
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOpenAICompatibleCommon(t *testing.T) {
|
func TestOpenAICompatibleCommon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testCommon(t, []builderPair{
|
testCommon(t, []builderPair{
|
||||||
{"xai-grok-4-fast", builderXAIGrok4Fast, nil, nil},
|
{"xai-grok-4-fast", builderXAIGrok4Fast, nil, nil},
|
||||||
{"xai-grok-code-fast", builderXAIGrokCodeFast, nil, nil},
|
{"xai-grok-code-fast", builderXAIGrokCodeFast, nil, nil},
|
||||||
|
|
@ -24,6 +25,7 @@ func TestOpenAICompatibleCommon(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenAICompatObjectGeneration(t *testing.T) {
|
func TestOpenAICompatObjectGeneration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testObjectGeneration(t, []builderPair{
|
testObjectGeneration(t, []builderPair{
|
||||||
{"xai-grok-4-fast", builderXAIGrok4Fast, nil, nil},
|
{"xai-grok-4-fast", builderXAIGrok4Fast, nil, nil},
|
||||||
{"xai-grok-code-fast", builderXAIGrokCodeFast, nil, nil},
|
{"xai-grok-code-fast", builderXAIGrokCodeFast, nil, nil},
|
||||||
|
|
@ -32,6 +34,7 @@ func TestOpenAICompatObjectGeneration(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenAICompatibleThinking(t *testing.T) {
|
func TestOpenAICompatibleThinking(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
opts := fantasy.ProviderOptions{
|
opts := fantasy.ProviderOptions{
|
||||||
openaicompat.Name: &openaicompat.ProviderOptions{
|
openaicompat.Name: &openaicompat.ProviderOptions{
|
||||||
ReasoningEffort: openai.ReasoningEffortOption(openai.ReasoningEffortHigh),
|
ReasoningEffort: openai.ReasoningEffortOption(openai.ReasoningEffortHigh),
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ var openrouterTestModels = []testModel{
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenRouterCommon(t *testing.T) {
|
func TestOpenRouterCommon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range openrouterTestModels {
|
for _, m := range openrouterTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, openrouterBuilder(m.model), nil, nil})
|
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) {
|
func TestOpenRouterCommonWithAnthropicCache(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testCommon(t, []builderPair{
|
testCommon(t, []builderPair{
|
||||||
{"claude-sonnet-4", openrouterBuilder("anthropic/claude-sonnet-4"), nil, addAnthropicCaching},
|
{"claude-sonnet-4", openrouterBuilder("anthropic/claude-sonnet-4"), nil, addAnthropicCaching},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenRouterThinking(t *testing.T) {
|
func TestOpenRouterThinking(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
opts := fantasy.ProviderOptions{
|
opts := fantasy.ProviderOptions{
|
||||||
openrouter.Name: &openrouter.ProviderOptions{
|
openrouter.Name: &openrouter.ProviderOptions{
|
||||||
Reasoning: &openrouter.ReasoningOptions{
|
Reasoning: &openrouter.ReasoningOptions{
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package providertests
|
package providertests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
"charm.land/fantasy/providers/anthropic"
|
"charm.land/fantasy/providers/anthropic"
|
||||||
"charm.land/fantasy/providers/google"
|
"charm.land/fantasy/providers/google"
|
||||||
|
|
@ -14,6 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProviderRegistry_Serialization_OpenAIOptions(t *testing.T) {
|
func TestProviderRegistry_Serialization_OpenAIOptions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msg := fantasy.Message{
|
msg := fantasy.Message{
|
||||||
Role: fantasy.MessageRoleUser,
|
Role: fantasy.MessageRoleUser,
|
||||||
Content: []fantasy.MessagePart{
|
Content: []fantasy.MessagePart{
|
||||||
|
|
@ -52,7 +54,10 @@ func TestProviderRegistry_Serialization_OpenAIOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_Serialization_OpenAIResponses(t *testing.T) {
|
func TestProviderRegistry_Serialization_OpenAIResponses(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Use ResponsesProviderOptions in provider options
|
// Use ResponsesProviderOptions in provider options
|
||||||
|
)
|
||||||
|
|
||||||
msg := fantasy.Message{
|
msg := fantasy.Message{
|
||||||
Role: fantasy.MessageRoleUser,
|
Role: fantasy.MessageRoleUser,
|
||||||
Content: []fantasy.MessagePart{
|
Content: []fantasy.MessagePart{
|
||||||
|
|
@ -95,6 +100,7 @@ func TestProviderRegistry_Serialization_OpenAIResponses(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *testing.T) {
|
func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
resp := fantasy.Response{
|
resp := fantasy.Response{
|
||||||
Content: []fantasy.Content{
|
Content: []fantasy.Content{
|
||||||
fantasy.TextContent{
|
fantasy.TextContent{
|
||||||
|
|
@ -144,6 +150,7 @@ func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *test
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_Serialization_AnthropicOptions(t *testing.T) {
|
func TestProviderRegistry_Serialization_AnthropicOptions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sendReasoning := true
|
sendReasoning := true
|
||||||
msg := fantasy.Message{
|
msg := fantasy.Message{
|
||||||
Role: fantasy.MessageRoleUser,
|
Role: fantasy.MessageRoleUser,
|
||||||
|
|
@ -172,6 +179,7 @@ func TestProviderRegistry_Serialization_AnthropicOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_Serialization_GoogleOptions(t *testing.T) {
|
func TestProviderRegistry_Serialization_GoogleOptions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msg := fantasy.Message{
|
msg := fantasy.Message{
|
||||||
Role: fantasy.MessageRoleUser,
|
Role: fantasy.MessageRoleUser,
|
||||||
Content: []fantasy.MessagePart{
|
Content: []fantasy.MessagePart{
|
||||||
|
|
@ -200,6 +208,7 @@ func TestProviderRegistry_Serialization_GoogleOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_Serialization_OpenRouterOptions(t *testing.T) {
|
func TestProviderRegistry_Serialization_OpenRouterOptions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
includeUsage := true
|
includeUsage := true
|
||||||
msg := fantasy.Message{
|
msg := fantasy.Message{
|
||||||
Role: fantasy.MessageRoleUser,
|
Role: fantasy.MessageRoleUser,
|
||||||
|
|
@ -231,6 +240,7 @@ func TestProviderRegistry_Serialization_OpenRouterOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_Serialization_OpenAICompatOptions(t *testing.T) {
|
func TestProviderRegistry_Serialization_OpenAICompatOptions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
effort := openai.ReasoningEffortHigh
|
effort := openai.ReasoningEffortHigh
|
||||||
msg := fantasy.Message{
|
msg := fantasy.Message{
|
||||||
Role: fantasy.MessageRoleUser,
|
Role: fantasy.MessageRoleUser,
|
||||||
|
|
@ -262,7 +272,10 @@ func TestProviderRegistry_Serialization_OpenAICompatOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_MultiProvider(t *testing.T) {
|
func TestProviderRegistry_MultiProvider(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Test with multiple providers in one message
|
// Test with multiple providers in one message
|
||||||
|
)
|
||||||
|
|
||||||
sendReasoning := true
|
sendReasoning := true
|
||||||
msg := fantasy.Message{
|
msg := fantasy.Message{
|
||||||
Role: fantasy.MessageRoleUser,
|
Role: fantasy.MessageRoleUser,
|
||||||
|
|
@ -299,6 +312,7 @@ func TestProviderRegistry_MultiProvider(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_ErrorHandling(t *testing.T) {
|
func TestProviderRegistry_ErrorHandling(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("unknown provider type", func(t *testing.T) {
|
t.Run("unknown provider type", func(t *testing.T) {
|
||||||
invalidJSON := `{
|
invalidJSON := `{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
|
|
@ -333,8 +347,11 @@ func TestProviderRegistry_ErrorHandling(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderRegistry_AllTypesRegistered(t *testing.T) {
|
func TestProviderRegistry_AllTypesRegistered(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Verify all expected provider types are registered
|
// Verify all expected provider types are registered
|
||||||
// We test that unmarshaling with proper type IDs doesn't fail with "unknown provider data type"
|
// We test that unmarshaling with proper type IDs doesn't fail with "unknown provider data type"
|
||||||
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
providerName string
|
providerName string
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ var vercelTestModels = []testModel{
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVercelCommon(t *testing.T) {
|
func TestVercelCommon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var pairs []builderPair
|
var pairs []builderPair
|
||||||
for _, m := range vercelTestModels {
|
for _, m := range vercelTestModels {
|
||||||
pairs = append(pairs, builderPair{m.name, vercelBuilder(m.model), nil, nil})
|
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) {
|
func TestVercelCommonWithAnthropicCache(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testCommon(t, []builderPair{
|
testCommon(t, []builderPair{
|
||||||
{"claude-sonnet-4", vercelBuilder("anthropic/claude-sonnet-4"), nil, addAnthropicCaching},
|
{"claude-sonnet-4", vercelBuilder("anthropic/claude-sonnet-4"), nil, addAnthropicCaching},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVercelThinking(t *testing.T) {
|
func TestVercelThinking(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
enabled := true
|
enabled := true
|
||||||
opts := fantasy.ProviderOptions{
|
opts := fantasy.ProviderOptions{
|
||||||
vercel.Name: &vercel.ProviderOptions{
|
vercel.Name: &vercel.ProviderOptions{
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,10 @@ func driveGeneratePath(ctx context.Context, f *reactFSM) {
|
||||||
|
|
||||||
// TestFSM_Start verifies the FSM transitions from Init to PrepareStep on Start.
|
// TestFSM_Start verifies the FSM transitions from Init to PrepareStep on Start.
|
||||||
func TestFSM_Start(t *testing.T) {
|
func TestFSM_Start(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
obs := &captureObserver{}
|
obs := &captureObserver{}
|
||||||
f, _ := newTestFSM(t, obs)
|
f, _ := newTestFSM(t, obs)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
f.Fire(ctx, ReActTriggerStart)
|
f.Fire(ctx, ReActTriggerStart)
|
||||||
|
|
||||||
|
|
@ -63,9 +64,10 @@ func TestFSM_Start(t *testing.T) {
|
||||||
// TestFSM_FullHappyPath drives one complete step through all states and
|
// TestFSM_FullHappyPath drives one complete step through all states and
|
||||||
// ends in Done via Finished.
|
// ends in Done via Finished.
|
||||||
func TestFSM_FullHappyPath(t *testing.T) {
|
func TestFSM_FullHappyPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
obs := &captureObserver{}
|
obs := &captureObserver{}
|
||||||
f, _ := newTestFSM(t, obs)
|
f, _ := newTestFSM(t, obs)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
f.Fire(ctx, ReActTriggerStart)
|
f.Fire(ctx, ReActTriggerStart)
|
||||||
driveGeneratePath(ctx, f)
|
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
|
// TestFSM_Continue verifies that the loop can re-enter PrepareStep after a
|
||||||
// tool-call step.
|
// tool-call step.
|
||||||
func TestFSM_Continue(t *testing.T) {
|
func TestFSM_Continue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
obs := &captureObserver{}
|
obs := &captureObserver{}
|
||||||
f, _ := newTestFSM(t, obs)
|
f, _ := newTestFSM(t, obs)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
f.Fire(ctx, ReActTriggerStart)
|
f.Fire(ctx, ReActTriggerStart)
|
||||||
driveGeneratePath(ctx, f)
|
driveGeneratePath(ctx, f)
|
||||||
|
|
@ -101,9 +104,10 @@ func TestFSM_Continue(t *testing.T) {
|
||||||
|
|
||||||
// TestFSM_StopConditionMet verifies the alternative Done path.
|
// TestFSM_StopConditionMet verifies the alternative Done path.
|
||||||
func TestFSM_StopConditionMet(t *testing.T) {
|
func TestFSM_StopConditionMet(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
obs := &captureObserver{}
|
obs := &captureObserver{}
|
||||||
f, _ := newTestFSM(t, obs)
|
f, _ := newTestFSM(t, obs)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
f.Fire(ctx, ReActTriggerStart)
|
f.Fire(ctx, ReActTriggerStart)
|
||||||
driveGeneratePath(ctx, f)
|
driveGeneratePath(ctx, f)
|
||||||
|
|
@ -115,9 +119,10 @@ func TestFSM_StopConditionMet(t *testing.T) {
|
||||||
|
|
||||||
// TestFSM_ErrorTransition verifies the error state is reachable from any state.
|
// TestFSM_ErrorTransition verifies the error state is reachable from any state.
|
||||||
func TestFSM_ErrorTransition(t *testing.T) {
|
func TestFSM_ErrorTransition(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
obs := &captureObserver{}
|
obs := &captureObserver{}
|
||||||
f, _ := newTestFSM(t, obs)
|
f, _ := newTestFSM(t, obs)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
f.Fire(ctx, ReActTriggerStart)
|
f.Fire(ctx, ReActTriggerStart)
|
||||||
f.Fire(ctx, ReActTriggerPrepared)
|
f.Fire(ctx, ReActTriggerPrepared)
|
||||||
|
|
@ -130,9 +135,10 @@ func TestFSM_ErrorTransition(t *testing.T) {
|
||||||
|
|
||||||
// TestFSM_RecoveredContinue verifies the error → PrepareStep recovery path.
|
// TestFSM_RecoveredContinue verifies the error → PrepareStep recovery path.
|
||||||
func TestFSM_RecoveredContinue(t *testing.T) {
|
func TestFSM_RecoveredContinue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
obs := &captureObserver{}
|
obs := &captureObserver{}
|
||||||
f, _ := newTestFSM(t, obs)
|
f, _ := newTestFSM(t, obs)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
f.Fire(ctx, ReActTriggerStart)
|
f.Fire(ctx, ReActTriggerStart)
|
||||||
f.Fire(ctx, ReActTriggerErrored) // error before prepared
|
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
|
// TestFSM_UnhandledTriggerIsPermissive verifies that firing an invalid trigger
|
||||||
// from a given state does NOT return an error (permissive design).
|
// from a given state does NOT return an error (permissive design).
|
||||||
func TestFSM_UnhandledTriggerIsPermissive(t *testing.T) {
|
func TestFSM_UnhandledTriggerIsPermissive(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
f, _ := newTestFSM(t, nil)
|
f, _ := newTestFSM(t, nil)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// From Init, firing Finished is not a permitted transition.
|
// From Init, firing Finished is not a permitted transition.
|
||||||
// The FSM must silently ignore it (no panic, no error).
|
// 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
|
// TestFSM_TransitionLog verifies the log accumulates correctly and Snapshot
|
||||||
// returns a copy.
|
// returns a copy.
|
||||||
func TestFSM_TransitionLog(t *testing.T) {
|
func TestFSM_TransitionLog(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
f, _ := newTestFSM(t, nil)
|
f, _ := newTestFSM(t, nil)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
f.Fire(ctx, ReActTriggerStart)
|
f.Fire(ctx, ReActTriggerStart)
|
||||||
f.Fire(ctx, ReActTriggerPrepared)
|
f.Fire(ctx, ReActTriggerPrepared)
|
||||||
|
|
@ -177,10 +185,11 @@ func TestFSM_TransitionLog(t *testing.T) {
|
||||||
// TestFSM_StepIndex verifies that the step index embedded in transitions
|
// TestFSM_StepIndex verifies that the step index embedded in transitions
|
||||||
// reflects the pointer value at emission time.
|
// reflects the pointer value at emission time.
|
||||||
func TestFSM_StepIndex(t *testing.T) {
|
func TestFSM_StepIndex(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
idx := 0
|
idx := 0
|
||||||
obs := &captureObserver{}
|
obs := &captureObserver{}
|
||||||
f := newReActFSM(obs, &idx)
|
f := newReActFSM(obs, &idx)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
f.Fire(ctx, ReActTriggerStart) // stepIndex = 0
|
f.Fire(ctx, ReActTriggerStart) // stepIndex = 0
|
||||||
idx = 1
|
idx = 1
|
||||||
|
|
@ -194,8 +203,9 @@ func TestFSM_StepIndex(t *testing.T) {
|
||||||
|
|
||||||
// TestFSM_NilObserverSafe verifies no panic when no observer is attached.
|
// TestFSM_NilObserverSafe verifies no panic when no observer is attached.
|
||||||
func TestFSM_NilObserverSafe(t *testing.T) {
|
func TestFSM_NilObserverSafe(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
f, _ := newTestFSM(t, nil)
|
f, _ := newTestFSM(t, nil)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
f.Fire(ctx, ReActTriggerStart)
|
f.Fire(ctx, ReActTriggerStart)
|
||||||
|
|
@ -206,6 +216,7 @@ func TestFSM_NilObserverSafe(t *testing.T) {
|
||||||
// TestReActTransitionLog_ConcurrentAppend verifies the log is safe under
|
// TestReActTransitionLog_ConcurrentAppend verifies the log is safe under
|
||||||
// concurrent writes.
|
// concurrent writes.
|
||||||
func TestReActTransitionLog_ConcurrentAppend(t *testing.T) {
|
func TestReActTransitionLog_ConcurrentAppend(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
log := NewReActTransitionLog()
|
log := NewReActTransitionLog()
|
||||||
const n = 100
|
const n = 100
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEnumSupport(t *testing.T) {
|
func TestEnumSupport(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Test enum via struct tags
|
// Test enum via struct tags
|
||||||
|
)
|
||||||
|
|
||||||
type WeatherInput struct {
|
type WeatherInput struct {
|
||||||
Location string `json:"location" description:"City name"`
|
Location string `json:"location" description:"City name"`
|
||||||
Units string `json:"units" enum:"celsius,fahrenheit,kelvin" description:"Temperature units"`
|
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) {
|
func TestSchemaToParameters(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testSchema := Schema{
|
testSchema := Schema{
|
||||||
Type: "object",
|
Type: "object",
|
||||||
Properties: map[string]*Schema{
|
Properties: map[string]*Schema{
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func TestDAGToolRuntime_IndependentToolsRunConcurrently(t *testing.T) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
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.
|
// Both tools should start before we release.
|
||||||
|
|
@ -100,7 +100,7 @@ func TestDAGToolRuntime_DependenciesWaitAndInputIsResolved(t *testing.T) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
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
|
<-startA
|
||||||
|
|
@ -138,7 +138,7 @@ func TestDAGToolRuntime_CycleDetected(t *testing.T) {
|
||||||
{ToolCallID: "b", ToolName: "p", Input: `{"x":"$tool.a"}`},
|
{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.Error(t, err)
|
||||||
require.Nil(t, res)
|
require.Nil(t, res)
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +172,7 @@ func TestDAGToolRuntime_OnToolResultSerialized(t *testing.T) {
|
||||||
return nil
|
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.NoError(t, err)
|
||||||
require.Len(t, res, 2)
|
require.Len(t, res, 2)
|
||||||
|
|
||||||
|
|
@ -206,7 +206,7 @@ func TestDAGToolRuntime_MetricsAndLogHooks(t *testing.T) {
|
||||||
toolCalls := []ToolCallContent{
|
toolCalls := []ToolCallContent{
|
||||||
{ToolCallID: "a", ToolName: "p", Input: `{}`},
|
{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.NoError(t, err)
|
||||||
require.Len(t, res, 1)
|
require.Len(t, res, 1)
|
||||||
require.True(t, metricsCalled)
|
require.True(t, metricsCalled)
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ func TestParallelToolRuntime_OrderAndCallbackDeterminism(t *testing.T) {
|
||||||
{ToolCallID: "c3", ToolName: "p", Input: `{"delay_ms":30,"value":"c"}`},
|
{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.NoError(t, err)
|
||||||
require.Len(t, results, 3)
|
require.Len(t, results, 3)
|
||||||
|
|
||||||
|
|
@ -92,7 +92,7 @@ func TestParallelToolRuntime_BarrierForNonParallelTools(t *testing.T) {
|
||||||
{ToolCallID: "p3", ToolName: "p", Input: `{}`},
|
{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.NoError(t, err)
|
||||||
require.Len(t, results, 4)
|
require.Len(t, results, 4)
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ func TestParallelToolRuntime_CriticalErrorPropagation(t *testing.T) {
|
||||||
{ToolCallID: "bad", ToolName: "p", Input: `{}`},
|
{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.Error(t, err)
|
||||||
require.Nil(t, results)
|
require.Nil(t, results)
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +148,7 @@ func TestParallelToolRuntime_MetricsAndLogHooks(t *testing.T) {
|
||||||
toolCalls := []ToolCallContent{
|
toolCalls := []ToolCallContent{
|
||||||
{ToolCallID: "a", ToolName: "p", Input: `{}`},
|
{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.NoError(t, err)
|
||||||
require.Len(t, res, 1)
|
require.Len(t, res, 1)
|
||||||
require.True(t, metricsCalled)
|
require.True(t, metricsCalled)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -14,7 +15,8 @@ type CalculatorInput struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTypedToolFuncExample(t *testing.T) {
|
func TestTypedToolFuncExample(t *testing.T) {
|
||||||
// Create a typed tool using the function API
|
t.Parallel()
|
||||||
|
|
||||||
tool := NewAgentTool(
|
tool := NewAgentTool(
|
||||||
"calculator",
|
"calculator",
|
||||||
"Evaluates simple mathematical expressions",
|
"Evaluates simple mathematical expressions",
|
||||||
|
|
@ -39,13 +41,14 @@ func TestTypedToolFuncExample(t *testing.T) {
|
||||||
Input: `{"expression": "2+2"}`,
|
Input: `{"expression": "2+2"}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := tool.Run(context.Background(), call)
|
result, err := tool.Run(t.Context(), call)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "4", result.Content)
|
require.Equal(t, "4", result.Content)
|
||||||
require.False(t, result.IsError)
|
require.False(t, result.IsError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnumToolExample(t *testing.T) {
|
func TestEnumToolExample(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type WeatherInput struct {
|
type WeatherInput struct {
|
||||||
Location string `json:"location" description:"City name"`
|
Location string `json:"location" description:"City name"`
|
||||||
Units string `json:"units" enum:"celsius,fahrenheit" description:"Temperature units"`
|
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"}`,
|
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.NoError(t, err)
|
||||||
require.Contains(t, result.Content, "San Francisco")
|
require.Contains(t, result.Content, "San Francisco")
|
||||||
require.Contains(t, result.Content, "72°F")
|
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) {
|
func TestNewMediaResponse(t *testing.T) {
|
||||||
audioData := []byte{0x52, 0x49, 0x46, 0x46} // RIFF header bytes
|
t.Parallel()
|
||||||
mediaType := "audio/wav"
|
tests := []struct {
|
||||||
|
name string
|
||||||
resp := NewMediaResponse(audioData, mediaType)
|
mediaType string
|
||||||
|
data []byte
|
||||||
require.Equal(t, "media", resp.Type)
|
wantType string
|
||||||
require.Equal(t, audioData, resp.Data)
|
}{
|
||||||
require.Equal(t, mediaType, resp.MediaType)
|
{
|
||||||
require.False(t, resp.IsError)
|
name: "image response",
|
||||||
require.Empty(t, resp.Content)
|
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",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Empty(t, cmp.Diff(ToolResponse{
|
||||||
|
Type: tt.wantType,
|
||||||
|
Data: tt.data,
|
||||||
|
MediaType: tt.mediaType,
|
||||||
|
IsError: false,
|
||||||
|
Content: "",
|
||||||
|
}, got))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,8 @@ func (m *toolCallingModel) Generate(_ context.Context, call fantasy.Call) (*fant
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *toolCallingModel) Stream(_ context.Context, call fantasy.Call) (fantasy.StreamResponse, error) {
|
func (m *toolCallingModel) Stream(ctx context.Context, call fantasy.Call) (fantasy.StreamResponse, error) {
|
||||||
resp, err := m.Generate(context.Background(), call)
|
resp, err := m.Generate(ctx, call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// TestIntegration_FullAgentLoop_SimpleResponse tests the full agent loop
|
||||||
// with a simple mock model that returns text directly (no tool calls).
|
// with a simple mock model that returns text directly (no tool calls).
|
||||||
func TestIntegration_FullAgentLoop_SimpleResponse(t *testing.T) {
|
func TestIntegration_FullAgentLoop_SimpleResponse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-integration-*")
|
tmpDir, err := os.MkdirTemp("", "agent-integration-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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")
|
model := newMockLanguageModel("Hello from Fantasy agent")
|
||||||
al := mustNewAgentLoop(t, cfg, msgBus, model)
|
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()
|
defer cancel()
|
||||||
|
|
||||||
msg := bus.InboundMessage{
|
msg := bus.InboundMessage{
|
||||||
|
|
@ -225,6 +226,7 @@ func TestIntegration_FullAgentLoop_SimpleResponse(t *testing.T) {
|
||||||
// TestIntegration_FullAgentLoop_WithToolCalls tests the full agent loop
|
// TestIntegration_FullAgentLoop_WithToolCalls tests the full agent loop
|
||||||
// including tool call execution and response incorporation.
|
// including tool call execution and response incorporation.
|
||||||
func TestIntegration_FullAgentLoop_WithToolCalls(t *testing.T) {
|
func TestIntegration_FullAgentLoop_WithToolCalls(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-integration-tools-*")
|
tmpDir, err := os.MkdirTemp("", "agent-integration-tools-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -249,7 +251,7 @@ func TestIntegration_FullAgentLoop_WithToolCalls(t *testing.T) {
|
||||||
// Register the echo tool
|
// Register the echo tool
|
||||||
al.RegisterTool(&echoTool{})
|
al.RegisterTool(&echoTool{})
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
msg := bus.InboundMessage{
|
msg := bus.InboundMessage{
|
||||||
|
|
@ -279,6 +281,7 @@ func TestIntegration_FullAgentLoop_WithToolCalls(t *testing.T) {
|
||||||
// TestIntegration_ProcessDirect tests the ProcessDirect method
|
// TestIntegration_ProcessDirect tests the ProcessDirect method
|
||||||
// which is used by CLI mode for one-shot message processing.
|
// which is used by CLI mode for one-shot message processing.
|
||||||
func TestIntegration_ProcessDirect(t *testing.T) {
|
func TestIntegration_ProcessDirect(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-integration-direct-*")
|
tmpDir, err := os.MkdirTemp("", "agent-integration-direct-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -300,7 +303,7 @@ func TestIntegration_ProcessDirect(t *testing.T) {
|
||||||
model := newMockLanguageModel("Direct CLI response")
|
model := newMockLanguageModel("Direct CLI response")
|
||||||
al := mustNewAgentLoop(t, cfg, msgBus, model)
|
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()
|
defer cancel()
|
||||||
|
|
||||||
response, err := al.ProcessDirect(ctx, "Direct message", "direct-session")
|
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
|
// TestIntegration_Streaming_TextDeltas tests that the streaming agent loop
|
||||||
// publishes text deltas to the bus and returns the complete text.
|
// publishes text deltas to the bus and returns the complete text.
|
||||||
func TestIntegration_Streaming_TextDeltas(t *testing.T) {
|
func TestIntegration_Streaming_TextDeltas(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-integration-stream-*")
|
tmpDir, err := os.MkdirTemp("", "agent-integration-stream-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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")
|
model := newStreamingModel("Hello from streaming agent response")
|
||||||
al := mustNewAgentLoop(t, cfg, msgBus, model)
|
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()
|
defer cancel()
|
||||||
|
|
||||||
// Collect stream deltas in background
|
// Collect stream deltas in background
|
||||||
|
|
@ -450,6 +454,7 @@ func TestIntegration_Streaming_TextDeltas(t *testing.T) {
|
||||||
// TestIntegration_Streaming_WithToolCalls tests streaming with a model
|
// TestIntegration_Streaming_WithToolCalls tests streaming with a model
|
||||||
// that requests tool calls before producing a final streamed response.
|
// that requests tool calls before producing a final streamed response.
|
||||||
func TestIntegration_Streaming_WithToolCalls(t *testing.T) {
|
func TestIntegration_Streaming_WithToolCalls(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-integration-stream-tools-*")
|
tmpDir, err := os.MkdirTemp("", "agent-integration-stream-tools-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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 := mustNewAgentLoop(t, cfg, msgBus, model)
|
||||||
al.RegisterTool(&echoTool{})
|
al.RegisterTool(&echoTool{})
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Collect deltas
|
// Collect deltas
|
||||||
|
|
@ -512,6 +517,7 @@ func TestIntegration_Streaming_WithToolCalls(t *testing.T) {
|
||||||
// TestIntegration_MultipleMessages tests sequential message processing
|
// TestIntegration_MultipleMessages tests sequential message processing
|
||||||
// to verify session history accumulation.
|
// to verify session history accumulation.
|
||||||
func TestIntegration_MultipleMessages(t *testing.T) {
|
func TestIntegration_MultipleMessages(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-integration-multi-*")
|
tmpDir, err := os.MkdirTemp("", "agent-integration-multi-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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)
|
al := mustNewAgentLoop(t, cfg, msgBus, model)
|
||||||
|
|
||||||
sessionKey := "multi-msg-session"
|
sessionKey := "multi-msg-session"
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Send 3 messages
|
// Send 3 messages
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package agent_test
|
package agent_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -17,8 +16,9 @@ func newDelegateKV(t *testing.T, agentID string) *agent.DelegateKV {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelegateKV_PutAndGet(t *testing.T) {
|
func TestDelegateKV_PutAndGet(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
kv := newDelegateKV(t, "agent-1")
|
kv := newDelegateKV(t, "agent-1")
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
require.NoError(t, kv.Put(ctx, "key1", []byte("hello world")))
|
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) {
|
func TestDelegateKV_GetMissingKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
kv := newDelegateKV(t, "agent-1")
|
kv := newDelegateKV(t, "agent-1")
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
got, err := kv.Get(ctx, "nonexistent")
|
got, err := kv.Get(ctx, "nonexistent")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -37,8 +38,9 @@ func TestDelegateKV_GetMissingKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelegateKV_PutOverwrite(t *testing.T) {
|
func TestDelegateKV_PutOverwrite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
kv := newDelegateKV(t, "agent-1")
|
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("v1")))
|
||||||
require.NoError(t, kv.Put(ctx, "k", []byte("v2")))
|
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) {
|
func TestDelegateKV_BinaryValues(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
kv := newDelegateKV(t, "agent-bin")
|
kv := newDelegateKV(t, "agent-bin")
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Include bytes that need base64 encoding (null bytes, high bytes)
|
// Include bytes that need base64 encoding (null bytes, high bytes)
|
||||||
data := []byte{0x00, 0xFF, 0x1F, 0x7E, 0x80, 0xAB}
|
data := []byte{0x00, 0xFF, 0x1F, 0x7E, 0x80, 0xAB}
|
||||||
|
|
@ -62,8 +65,9 @@ func TestDelegateKV_BinaryValues(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelegateKV_Scan(t *testing.T) {
|
func TestDelegateKV_Scan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
kv := newDelegateKV(t, "agent-scan")
|
kv := newDelegateKV(t, "agent-scan")
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
keys := []string{"prefix/a", "prefix/b", "prefix/c", "other/x"}
|
keys := []string{"prefix/a", "prefix/b", "prefix/c", "other/x"}
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
|
|
@ -76,8 +80,9 @@ func TestDelegateKV_Scan(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelegateKV_ScanEmpty(t *testing.T) {
|
func TestDelegateKV_ScanEmpty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
kv := newDelegateKV(t, "agent-scan-empty")
|
kv := newDelegateKV(t, "agent-scan-empty")
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
got, err := kv.Scan(ctx, "nothing/")
|
got, err := kv.Scan(ctx, "nothing/")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -85,8 +90,9 @@ func TestDelegateKV_ScanEmpty(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelegateKV_ScanSorted(t *testing.T) {
|
func TestDelegateKV_ScanSorted(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
kv := newDelegateKV(t, "agent-sorted")
|
kv := newDelegateKV(t, "agent-sorted")
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Insert out of order
|
// Insert out of order
|
||||||
for _, k := range []string{"z/c", "z/a", "z/b"} {
|
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) {
|
func TestDelegateKV_AgentIsolation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
kv1 := agent.NewDelegateKV(db.delegate, "agent-A")
|
kv1 := agent.NewDelegateKV(db.delegate, "agent-A")
|
||||||
kv2 := agent.NewDelegateKV(db.delegate, "agent-B")
|
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, kv1.Put(ctx, "shared-key", []byte("from-A")))
|
||||||
require.NoError(t, kv2.Put(ctx, "shared-key", []byte("from-B")))
|
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) {
|
func TestDelegateKV_EmptyKey_PutErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
kv := newDelegateKV(t, "agent-1")
|
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")
|
assert.Error(t, err, "empty key should be rejected")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelegateKV_EmptyKey_GetErrors(t *testing.T) {
|
func TestDelegateKV_EmptyKey_GetErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
kv := newDelegateKV(t, "agent-1")
|
kv := newDelegateKV(t, "agent-1")
|
||||||
_, err := kv.Get(context.Background(), "")
|
_, err := kv.Get(t.Context(), "")
|
||||||
assert.Error(t, err, "empty key should be rejected")
|
assert.Error(t, err, "empty key should be rejected")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) != "" {
|
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")
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("NewAgentLoop: %v", err)
|
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 (m *mockLanguageModel) Model() string { return "mock-model" }
|
||||||
|
|
||||||
func TestContinuityKeepCount_UsesConfiguredPolicy(t *testing.T) {
|
func TestContinuityKeepCount_UsesConfiguredPolicy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
buildHistory := func(n int, content string) []messages.Message {
|
buildHistory := func(n int, content string) []messages.Message {
|
||||||
history := make([]messages.Message, 0, n)
|
history := make([]messages.Message, 0, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
|
|
@ -113,6 +114,7 @@ func TestContinuityKeepCount_UsesConfiguredPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *testing.T) {
|
func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -133,7 +135,7 @@ func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *test
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
model := newMockLanguageModel("")
|
model := newMockLanguageModel("")
|
||||||
al := mustNewAgentLoop(t, cfg, msgBus, model)
|
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,
|
Limit: 10000,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -152,7 +154,7 @@ func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *test
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
<-start
|
<-start
|
||||||
conversationID, _, prepareErr := al.prepareRuntimeState(context.Background(), "race-session")
|
conversationID, _, prepareErr := al.prepareRuntimeState(t.Context(), "race-session")
|
||||||
if prepareErr != nil {
|
if prepareErr != nil {
|
||||||
errorsCh <- prepareErr
|
errorsCh <- prepareErr
|
||||||
return
|
return
|
||||||
|
|
@ -180,7 +182,7 @@ func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *test
|
||||||
t.Fatalf("expected one conversation id, got %d (%v)", len(uniqueConversationIDs), uniqueConversationIDs)
|
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,
|
Limit: 10000,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -192,7 +194,10 @@ func TestPrepareRuntimeState_ConcurrentSameSessionUsesSingleConversation(t *test
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRecordLastChannel(t *testing.T) {
|
func TestRecordLastChannel(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Create temp workspace
|
// Create temp workspace
|
||||||
|
)
|
||||||
|
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -218,7 +223,7 @@ func TestRecordLastChannel(t *testing.T) {
|
||||||
|
|
||||||
// Test RecordLastChannel
|
// Test RecordLastChannel
|
||||||
testChannel := "test-channel"
|
testChannel := "test-channel"
|
||||||
err = al.RecordLastChannel(context.Background(), testChannel)
|
err = al.RecordLastChannel(t.Context(), testChannel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("RecordLastChannel failed: %v", err)
|
t.Fatalf("RecordLastChannel failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +242,10 @@ func TestRecordLastChannel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRecordLastChatID(t *testing.T) {
|
func TestRecordLastChatID(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Create temp workspace
|
// Create temp workspace
|
||||||
|
)
|
||||||
|
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -263,7 +271,7 @@ func TestRecordLastChatID(t *testing.T) {
|
||||||
|
|
||||||
// Test RecordLastChatID
|
// Test RecordLastChatID
|
||||||
testChatID := "test-chat-id-123"
|
testChatID := "test-chat-id-123"
|
||||||
err = al.RecordLastChatID(context.Background(), testChatID)
|
err = al.RecordLastChatID(t.Context(), testChatID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("RecordLastChatID failed: %v", err)
|
t.Fatalf("RecordLastChatID failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +290,10 @@ func TestRecordLastChatID(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewAgentLoop_StateInitialized(t *testing.T) {
|
func TestNewAgentLoop_StateInitialized(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Create temp workspace
|
// Create temp workspace
|
||||||
|
)
|
||||||
|
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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) {
|
func TestNewAgentLoop_UnifiedKernelDependenciesInitialized(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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
|
// TestToolRegistry_ToolRegistration verifies tools can be registered and retrieved
|
||||||
func TestToolRegistry_ToolRegistration(t *testing.T) {
|
func TestToolRegistry_ToolRegistration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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
|
// TestToolContext_Updates verifies tool context is updated with channel/chatID
|
||||||
func TestToolContext_Updates(t *testing.T) {
|
func TestToolContext_Updates(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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
|
// TestToolRegistry_GetDefinitions verifies tool definitions can be retrieved
|
||||||
func TestToolRegistry_GetDefinitions(t *testing.T) {
|
func TestToolRegistry_GetDefinitions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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
|
// TestAgentLoop_GetStartupInfo verifies startup info contains tools
|
||||||
func TestAgentLoop_GetStartupInfo(t *testing.T) {
|
func TestAgentLoop_GetStartupInfo(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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
|
// TestAgentLoop_Stop verifies Stop() sets running to false
|
||||||
func TestAgentLoop_Stop(t *testing.T) {
|
func TestAgentLoop_Stop(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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
|
// TestToolResult_SilentToolDoesNotSendUserMessage verifies silent tools don't trigger outbound
|
||||||
func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) {
|
func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -642,7 +660,7 @@ func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) {
|
||||||
helper := testHelper{al: al}
|
helper := testHelper{al: al}
|
||||||
|
|
||||||
// ReadFileTool returns SilentResult, which should not send user message
|
// ReadFileTool returns SilentResult, which should not send user message
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
msg := bus.InboundMessage{
|
msg := bus.InboundMessage{
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
SenderID: "user1",
|
SenderID: "user1",
|
||||||
|
|
@ -661,6 +679,7 @@ func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) {
|
||||||
|
|
||||||
// TestToolResult_UserFacingToolDoesSendMessage verifies user-facing tools trigger outbound
|
// TestToolResult_UserFacingToolDoesSendMessage verifies user-facing tools trigger outbound
|
||||||
func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) {
|
func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -684,7 +703,7 @@ func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) {
|
||||||
helper := testHelper{al: al}
|
helper := testHelper{al: al}
|
||||||
|
|
||||||
// ExecTool returns UserResult, which should send user message
|
// ExecTool returns UserResult, which should send user message
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
msg := bus.InboundMessage{
|
msg := bus.InboundMessage{
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
SenderID: "user1",
|
SenderID: "user1",
|
||||||
|
|
@ -702,6 +721,7 @@ func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveFinalContent_RecoversFromPriorStepText(t *testing.T) {
|
func TestResolveFinalContent_RecoversFromPriorStepText(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
al := &AgentLoop{}
|
al := &AgentLoop{}
|
||||||
steps := []fantasy.StepResult{
|
steps := []fantasy.StepResult{
|
||||||
{
|
{
|
||||||
|
|
@ -730,6 +750,7 @@ func TestResolveFinalContent_RecoversFromPriorStepText(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveFinalContent_ErrorsWhenNoTextExists(t *testing.T) {
|
func TestResolveFinalContent_ErrorsWhenNoTextExists(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
al := &AgentLoop{}
|
al := &AgentLoop{}
|
||||||
steps := []fantasy.StepResult{
|
steps := []fantasy.StepResult{
|
||||||
{
|
{
|
||||||
|
|
@ -748,6 +769,7 @@ func TestResolveFinalContent_ErrorsWhenNoTextExists(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveFinalContent_RecoversFromToolResultText(t *testing.T) {
|
func TestResolveFinalContent_RecoversFromToolResultText(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
al := &AgentLoop{}
|
al := &AgentLoop{}
|
||||||
steps := []fantasy.StepResult{
|
steps := []fantasy.StepResult{
|
||||||
{
|
{
|
||||||
|
|
@ -776,6 +798,7 @@ func TestResolveFinalContent_RecoversFromToolResultText(t *testing.T) {
|
||||||
// TestForceCompression_PersistsProvenance verifies that emergency compression
|
// TestForceCompression_PersistsProvenance verifies that emergency compression
|
||||||
// cycles persist provenance metadata to the audit log for postmortem.
|
// cycles persist provenance metadata to the audit log for postmortem.
|
||||||
func TestForceCompression_PersistsProvenance(t *testing.T) {
|
func TestForceCompression_PersistsProvenance(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-provenance-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-provenance-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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)
|
t.Fatalf("test precondition failed: token_estimate=%d threshold=%d", tokenEstimate, criticalThreshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
al.forceCompression(ctx, sessionKey, "", "")
|
al.forceCompression(ctx, sessionKey, "", "")
|
||||||
|
|
||||||
del := al.MemoryDelegate()
|
del := al.MemoryDelegate()
|
||||||
|
|
@ -872,6 +895,7 @@ func TestForceCompression_PersistsProvenance(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPersistOversizedRecoveryRefs_CreatesRecoverableReferences(t *testing.T) {
|
func TestPersistOversizedRecoveryRefs_CreatesRecoverableReferences(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-recovery-ref-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-recovery-ref-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("persistOversizedRecoveryRefs failed: %v", err)
|
t.Fatalf("persistOversizedRecoveryRefs failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -919,7 +943,7 @@ func TestPersistOversizedRecoveryRefs_CreatesRecoverableReferences(t *testing.T)
|
||||||
return "recovery-session"
|
return "recovery-session"
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
res := dagTool.Execute(context.Background(), map[string]interface{}{
|
res := dagTool.Execute(t.Context(), map[string]interface{}{
|
||||||
"node_id": refs[0],
|
"node_id": refs[0],
|
||||||
"session_key": "recovery-session",
|
"session_key": "recovery-session",
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ func makeOffloader(t *testing.T, base fantasy.ToolRuntime, threshold, chunkChars
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
run, err := s.CreateRun(context.Background(), convID)
|
run, err := s.CreateRun(t.Context(), convID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
kv := agent.NewDelegateKV(db.delegate, "offload-test")
|
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
|
// TestOffloading_SmallResult_KeptInline verifies that results below the
|
||||||
// threshold are stored in KV but the inline value is unchanged.
|
// threshold are stored in KV but the inline value is unchanged.
|
||||||
func TestOffloading_SmallResult_KeptInline(t *testing.T) {
|
func TestOffloading_SmallResult_KeptInline(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r, _, _, kv := makeOffloader(t, nil, 1000, 500)
|
r, _, _, kv := makeOffloader(t, nil, 1000, 500)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
calls := makeCalls(1)
|
calls := makeCalls(1)
|
||||||
results, err := r.Execute(ctx, nil, calls, nil)
|
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
|
// TestOffloading_LargeResult_Truncated verifies that results above the
|
||||||
// threshold are truncated inline and chunked in KV.
|
// threshold are truncated inline and chunked in KV.
|
||||||
func TestOffloading_LargeResult_Truncated(t *testing.T) {
|
func TestOffloading_LargeResult_Truncated(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
longText := strings.Repeat("x", 200)
|
longText := strings.Repeat("x", 200)
|
||||||
|
|
||||||
base := staticToolRuntime{results: []fantasy.ToolResultContent{
|
base := staticToolRuntime{results: []fantasy.ToolResultContent{
|
||||||
|
|
@ -106,7 +108,7 @@ func TestOffloading_LargeResult_Truncated(t *testing.T) {
|
||||||
}}
|
}}
|
||||||
|
|
||||||
r, _, _, kv := makeOffloader(t, base, 50, 30)
|
r, _, _, kv := makeOffloader(t, base, 50, 30)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
calls := makeCalls(1)
|
calls := makeCalls(1)
|
||||||
calls[0].ToolCallID = "call-a"
|
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.
|
// TestOffloading_DBMetadata_Inserted verifies that the DB record is created.
|
||||||
func TestOffloading_DBMetadata_Inserted(t *testing.T) {
|
func TestOffloading_DBMetadata_Inserted(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r, convIDPtr, runIDPtr, _ := makeOffloader(t, nil, 1000, 500)
|
r, convIDPtr, runIDPtr, _ := makeOffloader(t, nil, 1000, 500)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
calls := makeCalls(2)
|
calls := makeCalls(2)
|
||||||
_, err := r.Execute(ctx, nil, calls, nil)
|
_, 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.
|
// TestOffloading_NilKV_Errors verifies that a nil KVDelegate returns an error.
|
||||||
func TestOffloading_NilKV_Errors(t *testing.T) {
|
func TestOffloading_NilKV_Errors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
run, err := s.CreateRun(context.Background(), convID)
|
run, err := s.CreateRun(t.Context(), convID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
r := &agent.OffloadingToolRuntime{
|
r := &agent.OffloadingToolRuntime{
|
||||||
|
|
@ -178,12 +182,13 @@ func TestOffloading_NilKV_Errors(t *testing.T) {
|
||||||
RunID: run.ID,
|
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")
|
assert.Error(t, err, "nil KV should fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOffloading_NilQueries_Errors verifies that a nil Queries returns an error.
|
// TestOffloading_NilQueries_Errors verifies that a nil Queries returns an error.
|
||||||
func TestOffloading_NilQueries_Errors(t *testing.T) {
|
func TestOffloading_NilQueries_Errors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
kv := agent.NewDelegateKV(db.delegate, "a")
|
kv := agent.NewDelegateKV(db.delegate, "a")
|
||||||
|
|
||||||
|
|
@ -193,15 +198,16 @@ func TestOffloading_NilQueries_Errors(t *testing.T) {
|
||||||
RunID: ids.New(),
|
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")
|
assert.Error(t, err, "nil queries should fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOffloading_EmptyToolCalls_ReturnsNil verifies no work is done when
|
// TestOffloading_EmptyToolCalls_ReturnsNil verifies no work is done when
|
||||||
// no tool calls are provided.
|
// no tool calls are provided.
|
||||||
func TestOffloading_EmptyToolCalls_ReturnsNil(t *testing.T) {
|
func TestOffloading_EmptyToolCalls_ReturnsNil(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r, _, _, _ := makeOffloader(t, nil, 1000, 500)
|
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)
|
require.NoError(t, err)
|
||||||
assert.Nil(t, results, "no tool calls should produce no results")
|
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
|
// TestOffloading_MultipleCalls_AllStoredInKV verifies that each call gets
|
||||||
// its own full-result KV entry.
|
// its own full-result KV entry.
|
||||||
func TestOffloading_MultipleCalls_AllStoredInKV(t *testing.T) {
|
func TestOffloading_MultipleCalls_AllStoredInKV(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r, _, _, kv := makeOffloader(t, nil, 1000, 500)
|
r, _, _, kv := makeOffloader(t, nil, 1000, 500)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
calls := makeCalls(3)
|
calls := makeCalls(3)
|
||||||
_, err := r.Execute(ctx, nil, calls, nil)
|
_, 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.
|
// TestChunkString verifies the internal chunking logic boundary conditions.
|
||||||
func TestChunkString(t *testing.T) {
|
func TestChunkString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -268,10 +276,10 @@ func TestChunkString(t *testing.T) {
|
||||||
|
|
||||||
r, _, _, kv := makeOffloader(t, base, effectiveThreshold, tt.chunkSize)
|
r, _, _, kv := makeOffloader(t, base, effectiveThreshold, tt.chunkSize)
|
||||||
calls := []fantasy.ToolCallContent{{ToolCallID: "c", ToolName: "t"}}
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
keys, err := kv.Scan(context.Background(), "tool_results/")
|
keys, err := kv.Scan(t.Context(), "tool_results/")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
chunkCount := 0
|
chunkCount := 0
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package agent_test
|
package agent_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"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/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ func newTestQueries(t *testing.T) *testDB {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
d, err := delegate.NewLibSQLInMemory()
|
d, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err, "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() })
|
t.Cleanup(func() { _ = d.Close() })
|
||||||
return &testDB{delegate: d}
|
return &testDB{delegate: d}
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +37,7 @@ func newConversation(t *testing.T, q *sqlc.Queries) ids.UUID {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
id := ids.New()
|
id := ids.New()
|
||||||
title := "test-conv"
|
title := "test-conv"
|
||||||
_, err := q.CreateAgentConversation(context.Background(), sqlc.CreateAgentConversationParams{
|
_, err := q.CreateAgentConversation(t.Context(), sqlc.CreateAgentConversationParams{
|
||||||
ID: id,
|
ID: id,
|
||||||
Title: &title,
|
Title: &title,
|
||||||
})
|
})
|
||||||
|
|
@ -45,34 +46,39 @@ func newConversation(t *testing.T, q *sqlc.Queries) ids.UUID {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateStore_CreateRun(t *testing.T) {
|
func TestStateStore_CreateRun(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
run, err := s.CreateRun(ctx, convID)
|
run, err := s.CreateRun(ctx, convID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.False(t, run.ID.IsZero(), "run ID must be set")
|
assert.False(t, run.ID.IsZero(), "run ID must be set")
|
||||||
assert.Equal(t, convID, run.ConversationID)
|
assert.Empty(t, cmp.Diff(sqlc.AgentRun{
|
||||||
assert.Equal(t, "running", run.Status)
|
ConversationID: convID,
|
||||||
|
Status: "running",
|
||||||
|
}, run, cmpopts.IgnoreFields(sqlc.AgentRun{}, "ID", "MetadataJson", "CreatedAt", "UpdatedAt")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateStore_CreateRun_ZeroConversationID(t *testing.T) {
|
func TestStateStore_CreateRun_ZeroConversationID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
s := agent.NewStateStore(db.delegate.Queries())
|
s := agent.NewStateStore(db.delegate.Queries())
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
_, err := s.CreateRun(ctx, ids.UUID{})
|
_, err := s.CreateRun(ctx, ids.UUID{})
|
||||||
assert.Error(t, err, "zero conversation id should be rejected")
|
assert.Error(t, err, "zero conversation id should be rejected")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateStore_UpdateRunStatus(t *testing.T) {
|
func TestStateStore_UpdateRunStatus(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
run, err := s.CreateRun(ctx, convID)
|
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})
|
updated, err := s.UpdateRunStatus(ctx, run.ID, "completed", map[string]any{"steps": 3})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, run.ID, updated.ID)
|
assert.Empty(t, cmp.Diff(sqlc.AgentRun{
|
||||||
assert.Equal(t, "completed", updated.Status)
|
ID: run.ID,
|
||||||
|
Status: "completed",
|
||||||
|
}, updated, cmpopts.IgnoreFields(sqlc.AgentRun{}, "ConversationID", "MetadataJson", "CreatedAt", "UpdatedAt")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateStore_UpdateRunStatus_EmptyStatus(t *testing.T) {
|
func TestStateStore_UpdateRunStatus_EmptyStatus(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
run, err := s.CreateRun(ctx, convID)
|
run, err := s.CreateRun(ctx, convID)
|
||||||
|
|
@ -100,10 +109,11 @@ func TestStateStore_UpdateRunStatus_EmptyStatus(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateStore_AddRunState(t *testing.T) {
|
func TestStateStore_AddRunState(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
run, err := s.CreateRun(ctx, convID)
|
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"})
|
state, err := s.AddRunState(ctx, run.ID, 0, fantasy.ReActStateLLMCall, map[string]string{"model": "gpt-4o"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.False(t, state.ID.IsZero())
|
assert.Empty(t, cmp.Diff(sqlc.AgentRunState{
|
||||||
assert.Equal(t, run.ID, state.RunID)
|
RunID: run.ID,
|
||||||
assert.Equal(t, int64(0), state.StepIndex)
|
StepIndex: 0,
|
||||||
assert.Equal(t, string(fantasy.ReActStateLLMCall), state.State)
|
State: string(fantasy.ReActStateLLMCall),
|
||||||
|
}, state, cmpopts.IgnoreFields(sqlc.AgentRunState{}, "ID", "SnapshotJson", "CreatedAt", "UpdatedAt")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateStore_AddRunState_NegativeStep(t *testing.T) {
|
func TestStateStore_AddRunState_NegativeStep(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
run, err := s.CreateRun(ctx, convID)
|
run, err := s.CreateRun(ctx, convID)
|
||||||
|
|
@ -133,10 +145,11 @@ func TestStateStore_AddRunState_NegativeStep(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateStore_AddTransition(t *testing.T) {
|
func TestStateStore_AddTransition(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
run, err := s.CreateRun(ctx, convID)
|
run, err := s.CreateRun(ctx, convID)
|
||||||
|
|
@ -152,15 +165,19 @@ func TestStateStore_AddTransition(t *testing.T) {
|
||||||
row, err := s.AddTransition(ctx, run.ID, tr)
|
row, err := s.AddTransition(ctx, run.ID, tr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.False(t, row.ID.IsZero())
|
assert.Empty(t, cmp.Diff(sqlc.AgentStateTransition{
|
||||||
assert.Equal(t, string(fantasy.ReActStateInit), row.FromState)
|
RunID: run.ID,
|
||||||
assert.Equal(t, string(fantasy.ReActStatePrepareStep), row.ToState)
|
StepIndex: 0,
|
||||||
assert.Equal(t, string(fantasy.ReActTriggerStart), row.Trigger)
|
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) {
|
func TestStateStore_NilStore(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var s *agent.StateStore
|
var s *agent.StateStore
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
_, err := s.CreateRun(ctx, ids.New())
|
_, err := s.CreateRun(ctx, ids.New())
|
||||||
assert.Error(t, err, "nil store should error")
|
assert.Error(t, err, "nil store should error")
|
||||||
|
|
@ -169,11 +186,12 @@ func TestStateStore_NilStore(t *testing.T) {
|
||||||
// --- CheckpointStore ---
|
// --- CheckpointStore ---
|
||||||
|
|
||||||
func TestCheckpointStore_CreateAndList(t *testing.T) {
|
func TestCheckpointStore_CreateAndList(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
ss := agent.NewStateStore(q)
|
ss := agent.NewStateStore(q)
|
||||||
cs := agent.NewCheckpointStore(q)
|
cs := agent.NewCheckpointStore(q)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
run, err := ss.CreateRun(ctx, convID)
|
run, err := ss.CreateRun(ctx, convID)
|
||||||
|
|
@ -195,11 +213,12 @@ func TestCheckpointStore_CreateAndList(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckpointStore_GetByName(t *testing.T) {
|
func TestCheckpointStore_GetByName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
ss := agent.NewStateStore(q)
|
ss := agent.NewStateStore(q)
|
||||||
cs := agent.NewCheckpointStore(q)
|
cs := agent.NewCheckpointStore(q)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
run, err := ss.CreateRun(ctx, convID)
|
run, err := ss.CreateRun(ctx, convID)
|
||||||
|
|
@ -217,10 +236,11 @@ func TestCheckpointStore_GetByName(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckpointStore_EmptyName(t *testing.T) {
|
func TestCheckpointStore_EmptyName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
cs := agent.NewCheckpointStore(q)
|
cs := agent.NewCheckpointStore(q)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
_, err := cs.CreateCheckpoint(ctx, convID, " ", ids.New(), nil)
|
_, err := cs.CreateCheckpoint(ctx, convID, " ", ids.New(), nil)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package agent_test
|
package agent_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
@ -21,7 +21,7 @@ func setupSearchFixture(t *testing.T) (fantasy.AgentTool, agent.KVDelegate, stri
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
run, err := s.CreateRun(context.Background(), convID)
|
run, err := s.CreateRun(t.Context(), convID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
kv := agent.NewDelegateKV(db.delegate, "search-test")
|
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: "c2", ToolName: "beta"},
|
||||||
{ToolCallID: "c3", ToolName: "alpha"},
|
{ToolCallID: "c3", ToolName: "alpha"},
|
||||||
}
|
}
|
||||||
_, err = r.Execute(context.Background(), nil, calls, nil)
|
_, err = r.Execute(t.Context(), nil, calls, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tool := agent.NewToolResultSearchTool(q, kv)
|
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.
|
// 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 {
|
func invokeSearch(t *testing.T, tool fantasy.AgentTool, input agent.ToolResultSearchInput) map[string]any {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
resp, err := tool.Run(context.Background(), fantasy.ToolCall{
|
resp, err := tool.Run(t.Context(), fantasy.ToolCall{
|
||||||
Input: marshalInput(t, input),
|
Input: marshalInput(t, input),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -69,89 +69,102 @@ func marshalInput(t *testing.T, v any) string {
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolResultSearch_ByConversationID(t *testing.T) {
|
func TestToolResultSearch_QueryFilters(t *testing.T) {
|
||||||
tool, _, convID, _ := setupSearchFixture(t)
|
t.Parallel()
|
||||||
ctx := context.Background()
|
|
||||||
_ = ctx
|
|
||||||
|
|
||||||
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,
|
ConversationID: convID,
|
||||||
Limit: 10,
|
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) {
|
wantTotal: 3,
|
||||||
tool, _, _, runID := setupSearchFixture(t)
|
},
|
||||||
|
{
|
||||||
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
|
name: "by run ID",
|
||||||
|
build: func(_, runID string) agent.ToolResultSearchInput {
|
||||||
|
return agent.ToolResultSearchInput{
|
||||||
RunID: runID,
|
RunID: runID,
|
||||||
Limit: 10,
|
Limit: 10,
|
||||||
})
|
|
||||||
|
|
||||||
total, _ := out["total"].(float64)
|
|
||||||
assert.Equal(t, float64(3), total)
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
func TestToolResultSearch_ByRunID_AndToolCallID(t *testing.T) {
|
wantTotal: 3,
|
||||||
tool, _, _, runID := setupSearchFixture(t)
|
},
|
||||||
|
{
|
||||||
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
|
name: "by run ID and tool_call_id",
|
||||||
|
build: func(_, runID string) agent.ToolResultSearchInput {
|
||||||
|
return agent.ToolResultSearchInput{
|
||||||
RunID: runID,
|
RunID: runID,
|
||||||
ToolCallID: "c2",
|
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) {
|
wantTotal: 1,
|
||||||
tool, _, convID, _ := setupSearchFixture(t)
|
wantTCID: "c2",
|
||||||
|
},
|
||||||
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
|
{
|
||||||
|
name: "filter by tool_name",
|
||||||
|
build: func(convID, _ string) agent.ToolResultSearchInput {
|
||||||
|
return agent.ToolResultSearchInput{
|
||||||
ConversationID: convID,
|
ConversationID: convID,
|
||||||
ToolName: "alpha",
|
ToolName: "alpha",
|
||||||
Limit: 10,
|
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) {
|
wantTotal: 2,
|
||||||
tool, _, convID, _ := setupSearchFixture(t)
|
},
|
||||||
|
{
|
||||||
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
|
name: "filter by query",
|
||||||
|
build: func(convID, _ string) agent.ToolResultSearchInput {
|
||||||
|
return agent.ToolResultSearchInput{
|
||||||
ConversationID: convID,
|
ConversationID: convID,
|
||||||
Query: "beta",
|
Query: "beta",
|
||||||
Limit: 10,
|
Limit: 10,
|
||||||
})
|
}
|
||||||
|
},
|
||||||
total, _ := out["total"].(float64)
|
wantTotal: 1,
|
||||||
assert.Equal(t, float64(1), total)
|
},
|
||||||
|
{
|
||||||
|
name: "default limit",
|
||||||
|
build: func(convID, _ string) agent.ToolResultSearchInput {
|
||||||
|
return agent.ToolResultSearchInput{
|
||||||
|
ConversationID: convID,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wantTotal: 3,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolResultSearch_DefaultLimit(t *testing.T) {
|
for _, tt := range tests {
|
||||||
tool, _, convID, _ := setupSearchFixture(t)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tool, _, convID, runID := setupSearchFixture(t)
|
||||||
// No limit specified -- default is 5, but we only have 3 items
|
input := tt.build(convID, runID)
|
||||||
out := invokeSearch(t, tool, agent.ToolResultSearchInput{
|
out := invokeSearch(t, tool, input)
|
||||||
ConversationID: convID,
|
|
||||||
})
|
|
||||||
|
|
||||||
total, _ := out["total"].(float64)
|
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) {
|
func TestToolResultSearch_MissingConvAndRunID_ErrorResponse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool, _, _, _ := setupSearchFixture(t)
|
tool, _, _, _ := setupSearchFixture(t)
|
||||||
|
|
||||||
resp, err := tool.Run(context.Background(), fantasy.ToolCall{
|
resp, err := tool.Run(t.Context(), fantasy.ToolCall{
|
||||||
Input: marshalInput(t, agent.ToolResultSearchInput{}),
|
Input: marshalInput(t, agent.ToolResultSearchInput{}),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
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
|
// TestToolResultSearch_LineView verifies that the KV full-result is sliced
|
||||||
// into the requested line range.
|
// into the requested line range.
|
||||||
func TestToolResultSearch_LineView(t *testing.T) {
|
func TestToolResultSearch_LineView(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
run, err := s.CreateRun(context.Background(), convID)
|
run, err := s.CreateRun(t.Context(), convID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
kv := agent.NewDelegateKV(db.delegate, "line-view-test")
|
kv := agent.NewDelegateKV(db.delegate, "line-view-test")
|
||||||
|
|
@ -189,7 +203,7 @@ func TestToolResultSearch_LineView(t *testing.T) {
|
||||||
RunID: run.ID,
|
RunID: run.ID,
|
||||||
ThresholdChars: 100000,
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tool := agent.NewToolResultSearchTool(q, kv)
|
tool := agent.NewToolResultSearchTool(q, kv)
|
||||||
|
|
@ -220,11 +234,12 @@ func TestToolResultSearch_LineView(t *testing.T) {
|
||||||
|
|
||||||
// TestToolResultSearch_ChunkView verifies chunk-range retrieval from KV.
|
// TestToolResultSearch_ChunkView verifies chunk-range retrieval from KV.
|
||||||
func TestToolResultSearch_ChunkView(t *testing.T) {
|
func TestToolResultSearch_ChunkView(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
db := newTestQueries(t)
|
db := newTestQueries(t)
|
||||||
q := db.delegate.Queries()
|
q := db.delegate.Queries()
|
||||||
convID := newConversation(t, q)
|
convID := newConversation(t, q)
|
||||||
s := agent.NewStateStore(q)
|
s := agent.NewStateStore(q)
|
||||||
run, err := s.CreateRun(context.Background(), convID)
|
run, err := s.CreateRun(t.Context(), convID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
kv := agent.NewDelegateKV(db.delegate, "chunk-view-test")
|
kv := agent.NewDelegateKV(db.delegate, "chunk-view-test")
|
||||||
|
|
@ -247,7 +262,7 @@ func TestToolResultSearch_ChunkView(t *testing.T) {
|
||||||
ThresholdChars: 10,
|
ThresholdChars: 10,
|
||||||
ChunkChars: 20,
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tool := agent.NewToolResultSearchTool(q, kv)
|
tool := agent.NewToolResultSearchTool(q, kv)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ func makeJWTForClaims(t *testing.T, claims map[string]interface{}) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildAuthorizeURL(t *testing.T) {
|
func TestBuildAuthorizeURL(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := OAuthProviderConfig{
|
cfg := OAuthProviderConfig{
|
||||||
Issuer: "https://auth.example.com",
|
Issuer: "https://auth.example.com",
|
||||||
ClientID: "test-client-id",
|
ClientID: "test-client-id",
|
||||||
|
|
@ -68,6 +69,7 @@ func TestBuildAuthorizeURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildAuthorizeURLOpenAIExtras(t *testing.T) {
|
func TestBuildAuthorizeURLOpenAIExtras(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := OpenAIOAuthConfig()
|
cfg := OpenAIOAuthConfig()
|
||||||
pkce := PKCECodes{CodeVerifier: "test-verifier", CodeChallenge: "test-challenge"}
|
pkce := PKCECodes{CodeVerifier: "test-verifier", CodeChallenge: "test-challenge"}
|
||||||
|
|
||||||
|
|
@ -90,6 +92,7 @@ func TestBuildAuthorizeURLOpenAIExtras(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTokenResponse(t *testing.T) {
|
func TestParseTokenResponse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
resp := map[string]interface{}{
|
resp := map[string]interface{}{
|
||||||
"access_token": "test-access-token",
|
"access_token": "test-access-token",
|
||||||
"refresh_token": "test-refresh-token",
|
"refresh_token": "test-refresh-token",
|
||||||
|
|
@ -121,6 +124,7 @@ func TestParseTokenResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTokenResponseExtractsAccountIDFromIDToken(t *testing.T) {
|
func TestParseTokenResponseExtractsAccountIDFromIDToken(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
idToken := makeJWTForClaims(t, map[string]interface{}{"chatgpt_account_id": "acc-id-from-id-token"})
|
idToken := makeJWTForClaims(t, map[string]interface{}{"chatgpt_account_id": "acc-id-from-id-token"})
|
||||||
resp := map[string]interface{}{
|
resp := map[string]interface{}{
|
||||||
"access_token": "opaque-access-token",
|
"access_token": "opaque-access-token",
|
||||||
|
|
@ -140,6 +144,7 @@ func TestParseTokenResponseExtractsAccountIDFromIDToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractAccountIDFromOrganizationsFallback(t *testing.T) {
|
func TestExtractAccountIDFromOrganizationsFallback(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
token := makeJWTForClaims(t, map[string]interface{}{
|
token := makeJWTForClaims(t, map[string]interface{}{
|
||||||
"organizations": []interface{}{
|
"organizations": []interface{}{
|
||||||
map[string]interface{}{"id": "org_from_orgs"},
|
map[string]interface{}{"id": "org_from_orgs"},
|
||||||
|
|
@ -152,6 +157,7 @@ func TestExtractAccountIDFromOrganizationsFallback(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTokenResponseNoAccessToken(t *testing.T) {
|
func TestParseTokenResponseNoAccessToken(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
body := []byte(`{"refresh_token": "test"}`)
|
body := []byte(`{"refresh_token": "test"}`)
|
||||||
_, err := parseTokenResponse(body, "openai")
|
_, err := parseTokenResponse(body, "openai")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -160,6 +166,7 @@ func TestParseTokenResponseNoAccessToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTokenResponseAccountIDFromIDToken(t *testing.T) {
|
func TestParseTokenResponseAccountIDFromIDToken(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
idToken := makeJWTWithAccountID("acc-from-id")
|
idToken := makeJWTWithAccountID("acc-from-id")
|
||||||
resp := map[string]interface{}{
|
resp := map[string]interface{}{
|
||||||
"access_token": "not-a-jwt",
|
"access_token": "not-a-jwt",
|
||||||
|
|
@ -186,6 +193,7 @@ func makeJWTWithAccountID(accountID string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExchangeCodeForTokens(t *testing.T) {
|
func TestExchangeCodeForTokens(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/oauth/token" {
|
if r.URL.Path != "/oauth/token" {
|
||||||
http.Error(w, "not found", http.StatusNotFound)
|
http.Error(w, "not found", http.StatusNotFound)
|
||||||
|
|
@ -229,6 +237,7 @@ func TestExchangeCodeForTokens(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefreshAccessToken(t *testing.T) {
|
func TestRefreshAccessToken(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/oauth/token" {
|
if r.URL.Path != "/oauth/token" {
|
||||||
http.Error(w, "not found", http.StatusNotFound)
|
http.Error(w, "not found", http.StatusNotFound)
|
||||||
|
|
@ -276,6 +285,7 @@ func TestRefreshAccessToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefreshAccessTokenNoRefreshToken(t *testing.T) {
|
func TestRefreshAccessTokenNoRefreshToken(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := OpenAIOAuthConfig()
|
cfg := OpenAIOAuthConfig()
|
||||||
cred := &AuthCredential{
|
cred := &AuthCredential{
|
||||||
AccessToken: "old-token",
|
AccessToken: "old-token",
|
||||||
|
|
@ -290,6 +300,7 @@ func TestRefreshAccessTokenNoRefreshToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefreshAccessTokenPreservesRefreshAndAccountID(t *testing.T) {
|
func TestRefreshAccessTokenPreservesRefreshAndAccountID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
resp := map[string]interface{}{
|
resp := map[string]interface{}{
|
||||||
"access_token": "new-access-token-only",
|
"access_token": "new-access-token-only",
|
||||||
|
|
@ -321,6 +332,7 @@ func TestRefreshAccessTokenPreservesRefreshAndAccountID(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenAIOAuthConfig(t *testing.T) {
|
func TestOpenAIOAuthConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := OpenAIOAuthConfig()
|
cfg := OpenAIOAuthConfig()
|
||||||
if cfg.Issuer != "https://auth.openai.com" {
|
if cfg.Issuer != "https://auth.openai.com" {
|
||||||
t.Errorf("Issuer = %q, want %q", 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) {
|
func TestParseDeviceCodeResponseIntervalAsNumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":5}`)
|
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":5}`)
|
||||||
|
|
||||||
resp, err := parseDeviceCodeResponse(body)
|
resp, err := parseDeviceCodeResponse(body)
|
||||||
|
|
@ -353,6 +366,7 @@ func TestParseDeviceCodeResponseIntervalAsNumber(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseDeviceCodeResponseIntervalAsString(t *testing.T) {
|
func TestParseDeviceCodeResponseIntervalAsString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":"5"}`)
|
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":"5"}`)
|
||||||
|
|
||||||
resp, err := parseDeviceCodeResponse(body)
|
resp, err := parseDeviceCodeResponse(body)
|
||||||
|
|
@ -366,6 +380,7 @@ func TestParseDeviceCodeResponseIntervalAsString(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseDeviceCodeResponseInvalidInterval(t *testing.T) {
|
func TestParseDeviceCodeResponseInvalidInterval(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":"abc"}`)
|
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":"abc"}`)
|
||||||
|
|
||||||
if _, err := parseDeviceCodeResponse(body); err == nil {
|
if _, err := parseDeviceCodeResponse(body); err == nil {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGeneratePKCE(t *testing.T) {
|
func TestGeneratePKCE(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
codes, err := GeneratePKCE()
|
codes, err := GeneratePKCE()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GeneratePKCE() error: %v", err)
|
t.Fatalf("GeneratePKCE() error: %v", err)
|
||||||
|
|
@ -35,6 +36,7 @@ func TestGeneratePKCE(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGeneratePKCEUniqueness(t *testing.T) {
|
func TestGeneratePKCEUniqueness(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
codes1, err := GeneratePKCE()
|
codes1, err := GeneratePKCE()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GeneratePKCE() error: %v", err)
|
t.Fatalf("GeneratePKCE() error: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAuthCredentialIsExpired(t *testing.T) {
|
func TestAuthCredentialIsExpired(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
expiresAt time.Time
|
expiresAt time.Time
|
||||||
|
|
@ -29,6 +30,7 @@ func TestAuthCredentialIsExpired(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthCredentialNeedsRefresh(t *testing.T) {
|
func TestAuthCredentialNeedsRefresh(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
expiresAt time.Time
|
expiresAt time.Time
|
||||||
|
|
@ -51,6 +53,7 @@ func TestAuthCredentialNeedsRefresh(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStoreRoundtrip(t *testing.T) {
|
func TestStoreRoundtrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
origHome := os.Getenv("HOME")
|
origHome := os.Getenv("HOME")
|
||||||
t.Setenv("HOME", tmpDir)
|
t.Setenv("HOME", tmpDir)
|
||||||
|
|
@ -114,6 +117,7 @@ func TestStoreFilePermissions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStoreMultiProvider(t *testing.T) {
|
func TestStoreMultiProvider(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
origHome := os.Getenv("HOME")
|
origHome := os.Getenv("HOME")
|
||||||
t.Setenv("HOME", tmpDir)
|
t.Setenv("HOME", tmpDir)
|
||||||
|
|
@ -147,6 +151,7 @@ func TestStoreMultiProvider(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteCredential(t *testing.T) {
|
func TestDeleteCredential(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
origHome := os.Getenv("HOME")
|
origHome := os.Getenv("HOME")
|
||||||
t.Setenv("HOME", tmpDir)
|
t.Setenv("HOME", tmpDir)
|
||||||
|
|
@ -171,6 +176,7 @@ func TestDeleteCredential(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadStoreEmpty(t *testing.T) {
|
func TestLoadStoreEmpty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
origHome := os.Getenv("HOME")
|
origHome := os.Getenv("HOME")
|
||||||
t.Setenv("HOME", tmpDir)
|
t.Setenv("HOME", tmpDir)
|
||||||
|
|
|
||||||
22
pkg/cache/lru_test.go
vendored
22
pkg/cache/lru_test.go
vendored
|
|
@ -33,6 +33,7 @@ func (c *mockClock) Advance(d time.Duration) {
|
||||||
// --- Basic LRU Tests ---
|
// --- Basic LRU Tests ---
|
||||||
|
|
||||||
func TestLRU_SetAndGet(t *testing.T) {
|
func TestLRU_SetAndGet(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[string, int](Options[string, int]{MaxSize: 10})
|
c := New[string, int](Options[string, int]{MaxSize: 10})
|
||||||
|
|
||||||
c.Set("a", 1)
|
c.Set("a", 1)
|
||||||
|
|
@ -55,6 +56,7 @@ func TestLRU_SetAndGet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_Update(t *testing.T) {
|
func TestLRU_Update(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[string, string](Options[string, string]{MaxSize: 10})
|
c := New[string, string](Options[string, string]{MaxSize: 10})
|
||||||
|
|
||||||
c.Set("k", "v1")
|
c.Set("k", "v1")
|
||||||
|
|
@ -71,6 +73,7 @@ func TestLRU_Update(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_EvictionOrder(t *testing.T) {
|
func TestLRU_EvictionOrder(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var evicted []string
|
var evicted []string
|
||||||
c := New[string, int](Options[string, int]{
|
c := New[string, int](Options[string, int]{
|
||||||
MaxSize: 3,
|
MaxSize: 3,
|
||||||
|
|
@ -110,6 +113,7 @@ func TestLRU_EvictionOrder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_MaxSizeZero_Unlimited(t *testing.T) {
|
func TestLRU_MaxSizeZero_Unlimited(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[int, int](Options[int, int]{})
|
c := New[int, int](Options[int, int]{})
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
|
|
@ -122,6 +126,7 @@ func TestLRU_MaxSizeZero_Unlimited(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_Delete(t *testing.T) {
|
func TestLRU_Delete(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var evictCalled bool
|
var evictCalled bool
|
||||||
c := New[string, int](Options[string, int]{
|
c := New[string, int](Options[string, int]{
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
|
|
@ -148,6 +153,7 @@ func TestLRU_Delete(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_Clear(t *testing.T) {
|
func TestLRU_Clear(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var evictCount int
|
var evictCount int
|
||||||
c := New[string, int](Options[string, int]{
|
c := New[string, int](Options[string, int]{
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
|
|
@ -171,6 +177,7 @@ func TestLRU_Clear(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_Peek_DoesNotPromote(t *testing.T) {
|
func TestLRU_Peek_DoesNotPromote(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[string, int](Options[string, int]{MaxSize: 3})
|
c := New[string, int](Options[string, int]{MaxSize: 3})
|
||||||
|
|
||||||
c.Set("a", 1)
|
c.Set("a", 1)
|
||||||
|
|
@ -196,6 +203,7 @@ func TestLRU_Peek_DoesNotPromote(t *testing.T) {
|
||||||
// --- TTL Tests ---
|
// --- TTL Tests ---
|
||||||
|
|
||||||
func TestLRU_TTL_Expiry(t *testing.T) {
|
func TestLRU_TTL_Expiry(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
clock := newMockClock(time.Now())
|
clock := newMockClock(time.Now())
|
||||||
c := New[string, int](Options[string, int]{
|
c := New[string, int](Options[string, int]{
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
|
|
@ -225,6 +233,7 @@ func TestLRU_TTL_Expiry(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_TTL_PerEntry(t *testing.T) {
|
func TestLRU_TTL_PerEntry(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
clock := newMockClock(time.Now())
|
clock := newMockClock(time.Now())
|
||||||
c := New[string, int](Options[string, int]{
|
c := New[string, int](Options[string, int]{
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
|
|
@ -251,6 +260,7 @@ func TestLRU_TTL_PerEntry(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_Peek_ExpiresEntries(t *testing.T) {
|
func TestLRU_Peek_ExpiresEntries(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
clock := newMockClock(time.Now())
|
clock := newMockClock(time.Now())
|
||||||
c := New[string, int](Options[string, int]{
|
c := New[string, int](Options[string, int]{
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
|
|
@ -268,6 +278,7 @@ func TestLRU_Peek_ExpiresEntries(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_Purge(t *testing.T) {
|
func TestLRU_Purge(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
clock := newMockClock(time.Now())
|
clock := newMockClock(time.Now())
|
||||||
c := New[string, int](Options[string, int]{
|
c := New[string, int](Options[string, int]{
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
|
|
@ -300,6 +311,7 @@ func TestLRU_Purge(t *testing.T) {
|
||||||
// --- Stale-While-Revalidate Tests ---
|
// --- Stale-While-Revalidate Tests ---
|
||||||
|
|
||||||
func TestLRU_SWR_ReturnsStaleAndRefreshes(t *testing.T) {
|
func TestLRU_SWR_ReturnsStaleAndRefreshes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
clock := newMockClock(time.Now())
|
clock := newMockClock(time.Now())
|
||||||
var fetchCalls atomic.Int32
|
var fetchCalls atomic.Int32
|
||||||
refreshDone := make(chan struct{}, 1)
|
refreshDone := make(chan struct{}, 1)
|
||||||
|
|
@ -347,6 +359,7 @@ func TestLRU_SWR_ReturnsStaleAndRefreshes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_SWR_HardExpiry(t *testing.T) {
|
func TestLRU_SWR_HardExpiry(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
clock := newMockClock(time.Now())
|
clock := newMockClock(time.Now())
|
||||||
c := New[string, int](Options[string, int]{
|
c := New[string, int](Options[string, int]{
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
|
|
@ -367,6 +380,7 @@ func TestLRU_SWR_HardExpiry(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_SWR_NoFetchFunc_StaleStillReturned(t *testing.T) {
|
func TestLRU_SWR_NoFetchFunc_StaleStillReturned(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
clock := newMockClock(time.Now())
|
clock := newMockClock(time.Now())
|
||||||
c := New[string, int](Options[string, int]{
|
c := New[string, int](Options[string, int]{
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
|
|
@ -388,6 +402,7 @@ func TestLRU_SWR_NoFetchFunc_StaleStillReturned(t *testing.T) {
|
||||||
// --- Tag-Based Invalidation Tests ---
|
// --- Tag-Based Invalidation Tests ---
|
||||||
|
|
||||||
func TestLRU_Tags_InvalidateByTag(t *testing.T) {
|
func TestLRU_Tags_InvalidateByTag(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[string, int](Options[string, int]{MaxSize: 10})
|
c := New[string, int](Options[string, int]{MaxSize: 10})
|
||||||
|
|
||||||
c.SetWithTags("user:1", 1, []string{"users"})
|
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) {
|
func TestLRU_Tags_InvalidateNonexistentTag(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[string, int](Options[string, int]{MaxSize: 10})
|
c := New[string, int](Options[string, int]{MaxSize: 10})
|
||||||
c.Set("a", 1)
|
c.Set("a", 1)
|
||||||
|
|
||||||
|
|
@ -435,6 +451,7 @@ func TestLRU_Tags_InvalidateNonexistentTag(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_Tags_UpdateRemovesOldTags(t *testing.T) {
|
func TestLRU_Tags_UpdateRemovesOldTags(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[string, int](Options[string, int]{MaxSize: 10})
|
c := New[string, int](Options[string, int]{MaxSize: 10})
|
||||||
|
|
||||||
c.SetWithTags("k", 1, []string{"tag-a"})
|
c.SetWithTags("k", 1, []string{"tag-a"})
|
||||||
|
|
@ -458,6 +475,7 @@ func TestLRU_Tags_UpdateRemovesOldTags(t *testing.T) {
|
||||||
// --- Concurrent Access Tests ---
|
// --- Concurrent Access Tests ---
|
||||||
|
|
||||||
func TestLRU_ConcurrentAccess(t *testing.T) {
|
func TestLRU_ConcurrentAccess(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[int, int](Options[int, int]{MaxSize: 100})
|
c := New[int, int](Options[int, int]{MaxSize: 100})
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
@ -493,6 +511,7 @@ func TestLRU_ConcurrentAccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_ConcurrentTags(t *testing.T) {
|
func TestLRU_ConcurrentTags(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[string, int](Options[string, int]{MaxSize: 100})
|
c := New[string, int](Options[string, int]{MaxSize: 100})
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
@ -524,6 +543,7 @@ func TestLRU_ConcurrentTags(t *testing.T) {
|
||||||
// --- Edge Cases ---
|
// --- Edge Cases ---
|
||||||
|
|
||||||
func TestLRU_ZeroTTL_NoExpiry(t *testing.T) {
|
func TestLRU_ZeroTTL_NoExpiry(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
clock := newMockClock(time.Now())
|
clock := newMockClock(time.Now())
|
||||||
c := New[string, int](Options[string, int]{
|
c := New[string, int](Options[string, int]{
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
|
|
@ -540,6 +560,7 @@ func TestLRU_ZeroTTL_NoExpiry(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_MaxSizeOne(t *testing.T) {
|
func TestLRU_MaxSizeOne(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[string, int](Options[string, int]{MaxSize: 1})
|
c := New[string, int](Options[string, int]{MaxSize: 1})
|
||||||
|
|
||||||
c.Set("a", 1)
|
c.Set("a", 1)
|
||||||
|
|
@ -557,6 +578,7 @@ func TestLRU_MaxSizeOne(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRU_Keys_Order(t *testing.T) {
|
func TestLRU_Keys_Order(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := New[string, int](Options[string, int]{MaxSize: 10})
|
c := New[string, int](Options[string, int]{MaxSize: 10})
|
||||||
|
|
||||||
c.Set("a", 1)
|
c.Set("a", 1)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package channels
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestBaseChannelIsAllowed(t *testing.T) {
|
func TestBaseChannelIsAllowed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
allowList []string
|
allowList []string
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseSlackChatID(t *testing.T) {
|
func TestParseSlackChatID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
chatID string
|
chatID string
|
||||||
|
|
@ -54,6 +55,7 @@ func TestParseSlackChatID(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStripBotMention(t *testing.T) {
|
func TestStripBotMention(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
ch := &SlackChannel{botUserID: "U12345BOT"}
|
ch := &SlackChannel{botUserID: "U12345BOT"}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
@ -99,6 +101,7 @@ func TestStripBotMention(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewSlackChannel(t *testing.T) {
|
func TestNewSlackChannel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
|
|
||||||
t.Run("missing bot token", func(t *testing.T) {
|
t.Run("missing bot token", func(t *testing.T) {
|
||||||
|
|
@ -143,6 +146,7 @@ func TestNewSlackChannel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSlackChannelIsAllowed(t *testing.T) {
|
func TestSlackChannelIsAllowed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
|
|
||||||
t.Run("empty allowlist allows all", func(t *testing.T) {
|
t.Run("empty allowlist allows all", func(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
// TestDefaultConfig_HeartbeatEnabled verifies heartbeat is enabled by default
|
// TestDefaultConfig_HeartbeatEnabled verifies heartbeat is enabled by default
|
||||||
func TestDefaultConfig_HeartbeatEnabled(t *testing.T) {
|
func TestDefaultConfig_HeartbeatEnabled(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
if !cfg.Heartbeat.Enabled {
|
if !cfg.Heartbeat.Enabled {
|
||||||
|
|
@ -19,6 +20,7 @@ func TestDefaultConfig_HeartbeatEnabled(t *testing.T) {
|
||||||
|
|
||||||
// TestDefaultConfig_SandboxPath verifies sandbox path is resolvable
|
// TestDefaultConfig_SandboxPath verifies sandbox path is resolvable
|
||||||
func TestDefaultConfig_SandboxPath(t *testing.T) {
|
func TestDefaultConfig_SandboxPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
path := cfg.SandboxPath()
|
path := cfg.SandboxPath()
|
||||||
if path == "" {
|
if path == "" {
|
||||||
|
|
@ -28,6 +30,7 @@ func TestDefaultConfig_SandboxPath(t *testing.T) {
|
||||||
|
|
||||||
// TestDefaultConfig_Model verifies model is set
|
// TestDefaultConfig_Model verifies model is set
|
||||||
func TestDefaultConfig_Model(t *testing.T) {
|
func TestDefaultConfig_Model(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
if cfg.Agents.Defaults.Model == "" {
|
if cfg.Agents.Defaults.Model == "" {
|
||||||
|
|
@ -37,6 +40,7 @@ func TestDefaultConfig_Model(t *testing.T) {
|
||||||
|
|
||||||
// TestDefaultConfig_MaxTokens verifies max tokens has default value
|
// TestDefaultConfig_MaxTokens verifies max tokens has default value
|
||||||
func TestDefaultConfig_MaxTokens(t *testing.T) {
|
func TestDefaultConfig_MaxTokens(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
if cfg.Agents.Defaults.MaxTokens == 0 {
|
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
|
// TestDefaultConfig_MaxToolIterations verifies max tool iterations has default value
|
||||||
func TestDefaultConfig_MaxToolIterations(t *testing.T) {
|
func TestDefaultConfig_MaxToolIterations(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
if cfg.Agents.Defaults.MaxToolIterations == 0 {
|
if cfg.Agents.Defaults.MaxToolIterations == 0 {
|
||||||
|
|
@ -54,6 +59,7 @@ func TestDefaultConfig_MaxToolIterations(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultConfig_ContinuityRetention(t *testing.T) {
|
func TestDefaultConfig_ContinuityRetention(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
if cfg.Agents.Defaults.ContinuityRetention.MinMessages <= 0 {
|
if cfg.Agents.Defaults.ContinuityRetention.MinMessages <= 0 {
|
||||||
|
|
@ -72,6 +78,7 @@ func TestDefaultConfig_ContinuityRetention(t *testing.T) {
|
||||||
|
|
||||||
// TestDefaultConfig_Temperature verifies temperature has default value
|
// TestDefaultConfig_Temperature verifies temperature has default value
|
||||||
func TestDefaultConfig_Temperature(t *testing.T) {
|
func TestDefaultConfig_Temperature(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
if cfg.Agents.Defaults.Temperature == 0 {
|
if cfg.Agents.Defaults.Temperature == 0 {
|
||||||
|
|
@ -81,6 +88,7 @@ func TestDefaultConfig_Temperature(t *testing.T) {
|
||||||
|
|
||||||
// TestDefaultConfig_Gateway verifies gateway defaults
|
// TestDefaultConfig_Gateway verifies gateway defaults
|
||||||
func TestDefaultConfig_Gateway(t *testing.T) {
|
func TestDefaultConfig_Gateway(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
if cfg.Gateway.Host != "0.0.0.0" {
|
if cfg.Gateway.Host != "0.0.0.0" {
|
||||||
|
|
@ -93,6 +101,7 @@ func TestDefaultConfig_Gateway(t *testing.T) {
|
||||||
|
|
||||||
// TestDefaultConfig_Providers verifies provider structure
|
// TestDefaultConfig_Providers verifies provider structure
|
||||||
func TestDefaultConfig_Providers(t *testing.T) {
|
func TestDefaultConfig_Providers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
// Verify all providers are empty by default
|
// 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
|
// TestDefaultConfig_Channels verifies channels are disabled by default
|
||||||
func TestDefaultConfig_Channels(t *testing.T) {
|
func TestDefaultConfig_Channels(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
// Verify all channels are disabled by default
|
// Verify all channels are disabled by default
|
||||||
|
|
@ -152,6 +162,7 @@ func TestDefaultConfig_Channels(t *testing.T) {
|
||||||
|
|
||||||
// TestDefaultConfig_WebTools verifies web tools config
|
// TestDefaultConfig_WebTools verifies web tools config
|
||||||
func TestDefaultConfig_WebTools(t *testing.T) {
|
func TestDefaultConfig_WebTools(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
// Verify web tools defaults
|
// Verify web tools defaults
|
||||||
|
|
@ -167,6 +178,7 @@ func TestDefaultConfig_WebTools(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveConfig_FilePermissions(t *testing.T) {
|
func TestSaveConfig_FilePermissions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("file permission bits are not enforced on 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
|
// TestConfig_Complete verifies all config fields are set
|
||||||
func TestConfig_Complete(t *testing.T) {
|
func TestConfig_Complete(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
// Verify complete config structure
|
// Verify complete config structure
|
||||||
|
|
@ -222,6 +235,7 @@ func TestConfig_Complete(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultConfig_OpenAIWebSearchEnabled(t *testing.T) {
|
func TestDefaultConfig_OpenAIWebSearchEnabled(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
if !cfg.Providers.OpenAI.WebSearch {
|
if !cfg.Providers.OpenAI.WebSearch {
|
||||||
t.Fatal("DefaultConfig().Providers.OpenAI.WebSearch should be true")
|
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) {
|
func TestLoadConfig_OpenAIWebSearchDefaultsTrueWhenUnset(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
configPath := filepath.Join(dir, "config.json")
|
configPath := filepath.Join(dir, "config.json")
|
||||||
if err := os.WriteFile(configPath, []byte(`{"providers":{"openai":{"api_base":""}}}`), 0o600); err != nil {
|
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) {
|
func TestValidate_MemoryConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
mutate func(*Config)
|
mutate func(*Config)
|
||||||
|
|
@ -318,6 +334,7 @@ func TestValidate_MemoryConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidate_ContinuityRetentionConfig(t *testing.T) {
|
func TestValidate_ContinuityRetentionConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
cfg.Agents.Defaults.ContinuityRetention.MinMessages = 8
|
cfg.Agents.Defaults.ContinuityRetention.MinMessages = 8
|
||||||
cfg.Agents.Defaults.ContinuityRetention.MaxMessages = 4
|
cfg.Agents.Defaults.ContinuityRetention.MaxMessages = 4
|
||||||
|
|
@ -342,6 +359,7 @@ func containsMemoryWarning(s string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadConfig_OpenAIWebSearchCanBeDisabled(t *testing.T) {
|
func TestLoadConfig_OpenAIWebSearchCanBeDisabled(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
configPath := filepath.Join(dir, "config.json")
|
configPath := filepath.Join(dir, "config.json")
|
||||||
if err := os.WriteFile(configPath, []byte(`{"providers":{"openai":{"web_search":false}}}`), 0o600); err != nil {
|
if err := os.WriteFile(configPath, []byte(`{"providers":{"openai":{"web_search":false}}}`), 0o600); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSaveStore_FilePermissions(t *testing.T) {
|
func TestSaveStore_FilePermissions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("file permission bits are not enforced on Windows")
|
t.Skip("file permission bits are not enforced on Windows")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ func (t *mockNilResultTool) Execute(_ context.Context, _ map[string]interface{})
|
||||||
// --- PicoToolAdapter.Info() Tests ---
|
// --- PicoToolAdapter.Info() Tests ---
|
||||||
|
|
||||||
func TestAdapter_Info(t *testing.T) {
|
func TestAdapter_Info(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
adapter := &PicoToolAdapter{inner: &mockDualChannelTool{}}
|
adapter := &PicoToolAdapter{inner: &mockDualChannelTool{}}
|
||||||
info := adapter.Info()
|
info := adapter.Info()
|
||||||
|
|
||||||
|
|
@ -127,6 +128,7 @@ func TestAdapter_Info(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdapter_Info_UnwrapsSchemaWithRequired(t *testing.T) {
|
func TestAdapter_Info_UnwrapsSchemaWithRequired(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mock := &mockToolWithRequired{}
|
mock := &mockToolWithRequired{}
|
||||||
adapter := &PicoToolAdapter{inner: mock}
|
adapter := &PicoToolAdapter{inner: mock}
|
||||||
info := adapter.Info()
|
info := adapter.Info()
|
||||||
|
|
@ -159,6 +161,7 @@ func (t *mockToolWithRequired) Execute(_ context.Context, _ map[string]interface
|
||||||
// --- PicoToolAdapter.Run() Tests ---
|
// --- PicoToolAdapter.Run() Tests ---
|
||||||
|
|
||||||
func TestAdapter_Run_SilentTool_NoPublish(t *testing.T) {
|
func TestAdapter_Run_SilentTool_NoPublish(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
adapter := &PicoToolAdapter{
|
adapter := &PicoToolAdapter{
|
||||||
inner: &mockSilentTool{},
|
inner: &mockSilentTool{},
|
||||||
|
|
@ -173,7 +176,7 @@ func TestAdapter_Run_SilentTool_NoPublish(t *testing.T) {
|
||||||
Input: "{}",
|
Input: "{}",
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := adapter.Run(context.Background(), call)
|
resp, err := adapter.Run(t.Context(), call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
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) {
|
func TestAdapter_Run_DualChannel_PublishesForUser(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
adapter := &PicoToolAdapter{
|
adapter := &PicoToolAdapter{
|
||||||
inner: &mockDualChannelTool{},
|
inner: &mockDualChannelTool{},
|
||||||
|
|
@ -201,7 +205,7 @@ func TestAdapter_Run_DualChannel_PublishesForUser(t *testing.T) {
|
||||||
Input: `{"input": "hello"}`,
|
Input: `{"input": "hello"}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := adapter.Run(context.Background(), call)
|
resp, err := adapter.Run(t.Context(), call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
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) {
|
func TestAdapter_Run_ErrorTool_ReturnsErrorResponse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
adapter := &PicoToolAdapter{
|
adapter := &PicoToolAdapter{
|
||||||
inner: &mockErrorTool{},
|
inner: &mockErrorTool{},
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +231,7 @@ func TestAdapter_Run_ErrorTool_ReturnsErrorResponse(t *testing.T) {
|
||||||
Input: "{}",
|
Input: "{}",
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := adapter.Run(context.Background(), call)
|
resp, err := adapter.Run(t.Context(), call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error (adapter should not return Go errors): %v", err)
|
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) {
|
func TestAdapter_Run_NilResult_ReturnsError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
adapter := &PicoToolAdapter{
|
adapter := &PicoToolAdapter{
|
||||||
inner: &mockNilResultTool{},
|
inner: &mockNilResultTool{},
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +256,7 @@ func TestAdapter_Run_NilResult_ReturnsError(t *testing.T) {
|
||||||
Input: "{}",
|
Input: "{}",
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := adapter.Run(context.Background(), call)
|
resp, err := adapter.Run(t.Context(), call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected Go error: %v", err)
|
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) {
|
func TestAdapter_Run_ContextualTool_SetsContext(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
ctxTool := &mockContextualTool{}
|
ctxTool := &mockContextualTool{}
|
||||||
adapter := &PicoToolAdapter{
|
adapter := &PicoToolAdapter{
|
||||||
inner: ctxTool,
|
inner: ctxTool,
|
||||||
|
|
@ -277,7 +284,7 @@ func TestAdapter_Run_ContextualTool_SetsContext(t *testing.T) {
|
||||||
Input: "{}",
|
Input: "{}",
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := adapter.Run(context.Background(), call)
|
resp, err := adapter.Run(t.Context(), call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
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) {
|
func TestAdapter_Run_InvalidJSON_ReturnsError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
adapter := &PicoToolAdapter{
|
adapter := &PicoToolAdapter{
|
||||||
inner: &mockSilentTool{},
|
inner: &mockSilentTool{},
|
||||||
}
|
}
|
||||||
|
|
@ -299,7 +307,7 @@ func TestAdapter_Run_InvalidJSON_ReturnsError(t *testing.T) {
|
||||||
Input: "not valid json{{{",
|
Input: "not valid json{{{",
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := adapter.Run(context.Background(), call)
|
resp, err := adapter.Run(t.Context(), call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected Go error: %v", err)
|
t.Fatalf("Unexpected Go error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -312,6 +320,7 @@ func TestAdapter_Run_InvalidJSON_ReturnsError(t *testing.T) {
|
||||||
// --- BuildAdaptedTools Tests ---
|
// --- BuildAdaptedTools Tests ---
|
||||||
|
|
||||||
func TestBuildAdaptedTools_NilRegistry(t *testing.T) {
|
func TestBuildAdaptedTools_NilRegistry(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
result := BuildAdaptedTools(nil, nil, "", "")
|
result := BuildAdaptedTools(nil, nil, "", "")
|
||||||
if result != nil {
|
if result != nil {
|
||||||
t.Error("Expected nil for nil registry")
|
t.Error("Expected nil for nil registry")
|
||||||
|
|
@ -319,6 +328,7 @@ func TestBuildAdaptedTools_NilRegistry(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildAdaptedTools_WrapsAllTools(t *testing.T) {
|
func TestBuildAdaptedTools_WrapsAllTools(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
registry := tools.NewToolRegistry()
|
registry := tools.NewToolRegistry()
|
||||||
registry.Register(&mockSilentTool{})
|
registry.Register(&mockSilentTool{})
|
||||||
registry.Register(&mockDualChannelTool{})
|
registry.Register(&mockDualChannelTool{})
|
||||||
|
|
@ -348,6 +358,7 @@ func TestBuildAdaptedTools_WrapsAllTools(t *testing.T) {
|
||||||
// --- parseToolArgs Tests ---
|
// --- parseToolArgs Tests ---
|
||||||
|
|
||||||
func TestParseToolArgs_EmptyInput(t *testing.T) {
|
func TestParseToolArgs_EmptyInput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
args, err := parseToolArgs("")
|
args, err := parseToolArgs("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
|
@ -358,6 +369,7 @@ func TestParseToolArgs_EmptyInput(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseToolArgs_EmptyObject(t *testing.T) {
|
func TestParseToolArgs_EmptyObject(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
args, err := parseToolArgs("{}")
|
args, err := parseToolArgs("{}")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
|
@ -368,6 +380,7 @@ func TestParseToolArgs_EmptyObject(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseToolArgs_ValidJSON(t *testing.T) {
|
func TestParseToolArgs_ValidJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
args, err := parseToolArgs(`{"key": "value", "num": 42}`)
|
args, err := parseToolArgs(`{"key": "value", "num": 42}`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
|
@ -378,6 +391,7 @@ func TestParseToolArgs_ValidJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseToolArgs_InvalidJSON(t *testing.T) {
|
func TestParseToolArgs_InvalidJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := parseToolArgs("not json")
|
_, err := parseToolArgs("not json")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error for invalid JSON")
|
t.Error("Expected error for invalid JSON")
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
// --- MessagesToFantasy Tests ---
|
// --- MessagesToFantasy Tests ---
|
||||||
|
|
||||||
func TestMessagesToFantasy_EmptySlice(t *testing.T) {
|
func TestMessagesToFantasy_EmptySlice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
result := MessagesToFantasy(nil)
|
result := MessagesToFantasy(nil)
|
||||||
if len(result) != 0 {
|
if len(result) != 0 {
|
||||||
t.Errorf("Expected empty slice for nil input, got %d", len(result))
|
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) {
|
func TestMessageToFantasy_SimpleTextMessage(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msg := messages.Message{
|
msg := messages.Message{
|
||||||
Role: "user",
|
Role: "user",
|
||||||
Content: "Hello, world",
|
Content: "Hello, world",
|
||||||
|
|
@ -47,6 +49,7 @@ func TestMessageToFantasy_SimpleTextMessage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageToFantasy_AssistantWithMultipleToolCalls(t *testing.T) {
|
func TestMessageToFantasy_AssistantWithMultipleToolCalls(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msg := messages.Message{
|
msg := messages.Message{
|
||||||
Role: "assistant",
|
Role: "assistant",
|
||||||
Content: "Let me run both tools.",
|
Content: "Let me run both tools.",
|
||||||
|
|
@ -113,6 +116,7 @@ func TestMessageToFantasy_AssistantWithMultipleToolCalls(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageToFantasy_ToolCallWithMapArgsFallback(t *testing.T) {
|
func TestMessageToFantasy_ToolCallWithMapArgsFallback(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msg := messages.Message{
|
msg := messages.Message{
|
||||||
Role: "assistant",
|
Role: "assistant",
|
||||||
ToolCalls: []messages.ToolCall{
|
ToolCalls: []messages.ToolCall{
|
||||||
|
|
@ -145,6 +149,7 @@ func TestMessageToFantasy_ToolCallWithMapArgsFallback(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageToFantasy_ToolResultMessage(t *testing.T) {
|
func TestMessageToFantasy_ToolResultMessage(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msg := messages.Message{
|
msg := messages.Message{
|
||||||
Role: "tool",
|
Role: "tool",
|
||||||
Content: "file contents here",
|
Content: "file contents here",
|
||||||
|
|
@ -177,6 +182,7 @@ func TestMessageToFantasy_ToolResultMessage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageToFantasy_EmptyContent(t *testing.T) {
|
func TestMessageToFantasy_EmptyContent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
msg := messages.Message{
|
msg := messages.Message{
|
||||||
Role: "assistant",
|
Role: "assistant",
|
||||||
Content: "",
|
Content: "",
|
||||||
|
|
@ -193,6 +199,7 @@ func TestMessageToFantasy_EmptyContent(t *testing.T) {
|
||||||
// --- StepToMessages Tests ---
|
// --- StepToMessages Tests ---
|
||||||
|
|
||||||
func TestStepToMessages_TextOnly(t *testing.T) {
|
func TestStepToMessages_TextOnly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
step := fantasy.StepResult{
|
step := fantasy.StepResult{
|
||||||
Response: fantasy.Response{
|
Response: fantasy.Response{
|
||||||
Content: fantasy.ResponseContent{
|
Content: fantasy.ResponseContent{
|
||||||
|
|
@ -216,6 +223,7 @@ func TestStepToMessages_TextOnly(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepToMessages_MultipleToolCalls(t *testing.T) {
|
func TestStepToMessages_MultipleToolCalls(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
step := fantasy.StepResult{
|
step := fantasy.StepResult{
|
||||||
Response: fantasy.Response{
|
Response: fantasy.Response{
|
||||||
Content: fantasy.ResponseContent{
|
Content: fantasy.ResponseContent{
|
||||||
|
|
@ -280,6 +288,7 @@ func TestStepToMessages_MultipleToolCalls(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepToMessages_ErrorToolResult(t *testing.T) {
|
func TestStepToMessages_ErrorToolResult(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testErr := errors.New("permission denied")
|
testErr := errors.New("permission denied")
|
||||||
step := fantasy.StepResult{
|
step := fantasy.StepResult{
|
||||||
Response: fantasy.Response{
|
Response: fantasy.Response{
|
||||||
|
|
@ -315,6 +324,7 @@ func TestStepToMessages_ErrorToolResult(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepToMessages_ErrorToolResult_NilError(t *testing.T) {
|
func TestStepToMessages_ErrorToolResult_NilError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
step := fantasy.StepResult{
|
step := fantasy.StepResult{
|
||||||
Response: fantasy.Response{
|
Response: fantasy.Response{
|
||||||
Content: fantasy.ResponseContent{
|
Content: fantasy.ResponseContent{
|
||||||
|
|
@ -339,6 +349,7 @@ func TestStepToMessages_ErrorToolResult_NilError(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepToMessages_ToolCallWithoutText(t *testing.T) {
|
func TestStepToMessages_ToolCallWithoutText(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
step := fantasy.StepResult{
|
step := fantasy.StepResult{
|
||||||
Response: fantasy.Response{
|
Response: fantasy.Response{
|
||||||
Content: fantasy.ResponseContent{
|
Content: fantasy.ResponseContent{
|
||||||
|
|
@ -372,6 +383,7 @@ func TestStepToMessages_ToolCallWithoutText(t *testing.T) {
|
||||||
// --- AgentResultToMessages Tests ---
|
// --- AgentResultToMessages Tests ---
|
||||||
|
|
||||||
func TestAgentResultToMessages_MultipleSteps(t *testing.T) {
|
func TestAgentResultToMessages_MultipleSteps(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
result := &fantasy.AgentResult{
|
result := &fantasy.AgentResult{
|
||||||
Steps: []fantasy.StepResult{
|
Steps: []fantasy.StepResult{
|
||||||
{
|
{
|
||||||
|
|
@ -417,7 +429,10 @@ func TestAgentResultToMessages_MultipleSteps(t *testing.T) {
|
||||||
// --- Round-trip fidelity test ---
|
// --- Round-trip fidelity test ---
|
||||||
|
|
||||||
func TestRoundTrip_PicoClawToFantasyAndBack(t *testing.T) {
|
func TestRoundTrip_PicoClawToFantasyAndBack(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Start with DragonScale messages representing a typical conversation
|
// Start with DragonScale messages representing a typical conversation
|
||||||
|
)
|
||||||
|
|
||||||
original := []messages.Message{
|
original := []messages.Message{
|
||||||
{Role: "user", Content: "Read the file"},
|
{Role: "user", Content: "Read the file"},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExecuteHeartbeat_Async(t *testing.T) {
|
func TestExecuteHeartbeat_Async(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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) {
|
func TestExecuteHeartbeat_Error(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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) {
|
func TestExecuteHeartbeat_Silent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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) {
|
func TestHeartbeatService_StartStop(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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) {
|
func TestHeartbeatService_Disabled(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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) {
|
func TestExecuteHeartbeat_NilResult(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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
|
// TestLogPath verifies heartbeat log is written to workspace directory
|
||||||
func TestLogPath(t *testing.T) {
|
func TestLogPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
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
|
// TestHeartbeatFilePath verifies HEARTBEAT.md is at workspace root
|
||||||
func TestHeartbeatFilePath(t *testing.T) {
|
func TestHeartbeatFilePath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -222,6 +230,7 @@ func TestHeartbeatFilePath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecuteHeartbeat_UsesDueContextWithoutHeartbeatFile(t *testing.T) {
|
func TestExecuteHeartbeat_UsesDueContextWithoutHeartbeatFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
tmpDir, err := os.MkdirTemp("", "heartbeat-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNew_IsV7(t *testing.T) {
|
func TestNew_IsV7(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
u := New()
|
u := New()
|
||||||
|
|
||||||
if u.IsZero() {
|
if u.IsZero() {
|
||||||
|
|
@ -24,6 +25,7 @@ func TestNew_IsV7(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew_Unique(t *testing.T) {
|
func TestNew_Unique(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
seen := make(map[UUID]bool, 1000)
|
seen := make(map[UUID]bool, 1000)
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
u := New()
|
u := New()
|
||||||
|
|
@ -35,6 +37,7 @@ func TestNew_Unique(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew_Monotonic(t *testing.T) {
|
func TestNew_Monotonic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
a := New()
|
a := New()
|
||||||
b := New()
|
b := New()
|
||||||
// UUIDv7 embeds ms timestamp in first 6 bytes. b >= a in timestamp.
|
// 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) {
|
func TestParse_RoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
u := New()
|
u := New()
|
||||||
s := u.String()
|
s := u.String()
|
||||||
|
|
||||||
|
|
@ -63,6 +67,7 @@ func TestParse_RoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParse_Errors(t *testing.T) {
|
func TestParse_Errors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cases := []string{
|
cases := []string{
|
||||||
"",
|
"",
|
||||||
"not-a-uuid",
|
"not-a-uuid",
|
||||||
|
|
@ -78,6 +83,7 @@ func TestParse_Errors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsZero(t *testing.T) {
|
func TestIsZero(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var zero UUID
|
var zero UUID
|
||||||
if !zero.IsZero() {
|
if !zero.IsZero() {
|
||||||
t.Fatal("zero UUID should be zero")
|
t.Fatal("zero UUID should be zero")
|
||||||
|
|
@ -89,6 +95,7 @@ func TestIsZero(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValue_BlobRoundTrip(t *testing.T) {
|
func TestValue_BlobRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
u := New()
|
u := New()
|
||||||
v, err := u.Value()
|
v, err := u.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -112,6 +119,7 @@ func TestValue_BlobRoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValue_ZeroIsNil(t *testing.T) {
|
func TestValue_ZeroIsNil(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var zero UUID
|
var zero UUID
|
||||||
v, err := zero.Value()
|
v, err := zero.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -123,6 +131,7 @@ func TestValue_ZeroIsNil(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScan_NilLeavesZero(t *testing.T) {
|
func TestScan_NilLeavesZero(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var u UUID
|
var u UUID
|
||||||
if err := u.Scan(nil); err != nil {
|
if err := u.Scan(nil); err != nil {
|
||||||
t.Fatalf("Scan(nil): %v", err)
|
t.Fatalf("Scan(nil): %v", err)
|
||||||
|
|
@ -133,6 +142,7 @@ func TestScan_NilLeavesZero(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScan_String(t *testing.T) {
|
func TestScan_String(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
orig := New()
|
orig := New()
|
||||||
var u UUID
|
var u UUID
|
||||||
if err := u.Scan(orig.String()); err != nil {
|
if err := u.Scan(orig.String()); err != nil {
|
||||||
|
|
@ -144,6 +154,7 @@ func TestScan_String(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScan_InvalidBlob(t *testing.T) {
|
func TestScan_InvalidBlob(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var u UUID
|
var u UUID
|
||||||
if err := u.Scan([]byte{1, 2, 3}); err == nil {
|
if err := u.Scan([]byte{1, 2, 3}); err == nil {
|
||||||
t.Fatal("Scan(3-byte blob) should fail")
|
t.Fatal("Scan(3-byte blob) should fail")
|
||||||
|
|
@ -151,6 +162,7 @@ func TestScan_InvalidBlob(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScan_InvalidType(t *testing.T) {
|
func TestScan_InvalidType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var u UUID
|
var u UUID
|
||||||
if err := u.Scan(42); err == nil {
|
if err := u.Scan(42); err == nil {
|
||||||
t.Fatal("Scan(int) should fail")
|
t.Fatal("Scan(int) should fail")
|
||||||
|
|
@ -158,6 +170,7 @@ func TestScan_InvalidType(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSON_RoundTrip(t *testing.T) {
|
func TestJSON_RoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
u := New()
|
u := New()
|
||||||
|
|
||||||
b, err := jsonv2.Marshal(u)
|
b, err := jsonv2.Marshal(u)
|
||||||
|
|
@ -184,6 +197,7 @@ func TestJSON_RoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSON_ZeroUUID(t *testing.T) {
|
func TestJSON_ZeroUUID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var zero UUID
|
var zero UUID
|
||||||
b, err := jsonv2.Marshal(zero)
|
b, err := jsonv2.Marshal(zero)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -196,6 +210,7 @@ func TestJSON_ZeroUUID(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSON_InStruct(t *testing.T) {
|
func TestJSON_InStruct(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type record struct {
|
type record struct {
|
||||||
ID UUID `json:"id"`
|
ID UUID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
@ -217,6 +232,7 @@ func TestJSON_InStruct(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromBytes(t *testing.T) {
|
func TestFromBytes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
u := New()
|
u := New()
|
||||||
b := u.Bytes()
|
b := u.Bytes()
|
||||||
restored := FromBytes(b)
|
restored := FromBytes(b)
|
||||||
|
|
@ -226,6 +242,7 @@ func TestFromBytes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMustParse_Panics(t *testing.T) {
|
func TestMustParse_Panics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r == nil {
|
if r := recover(); r == nil {
|
||||||
t.Fatal("MustParse should panic on bad input")
|
t.Fatal("MustParse should panic on bad input")
|
||||||
|
|
@ -235,6 +252,7 @@ func TestMustParse_Panics(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestString_Format(t *testing.T) {
|
func TestString_Format(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
u := New()
|
u := New()
|
||||||
s := u.String()
|
s := u.String()
|
||||||
if len(s) != 36 {
|
if len(s) != 36 {
|
||||||
|
|
@ -246,6 +264,7 @@ func TestString_Format(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUUIDComparable(t *testing.T) {
|
func TestUUIDComparable(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
a := New()
|
a := New()
|
||||||
b := a // copy
|
b := a // copy
|
||||||
if a != b {
|
if a != b {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
package itr
|
package itr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestToolRequestMarshalRoundTrip(t *testing.T) {
|
func TestToolRequestMarshalRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
req ToolRequest
|
req ToolRequest
|
||||||
|
|
@ -68,16 +71,13 @@ func TestToolRequestMarshalRoundTrip(t *testing.T) {
|
||||||
decoded, err := UnmarshalRequest(data)
|
decoded, err := UnmarshalRequest(data)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, tt.req.ID, decoded.ID)
|
assert.Empty(t, cmp.Diff(tt.req, decoded))
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolExecPayloadPreservation(t *testing.T) {
|
func TestToolExecPayloadPreservation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
req := NewToolExecRequest("id-1", "s", "tc", "shell", `{"cmd":"ls -la"}`)
|
req := NewToolExecRequest("id-1", "s", "tc", "shell", `{"cmd":"ls -la"}`)
|
||||||
data, err := req.Marshal()
|
data, err := req.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -92,6 +92,7 @@ func TestToolExecPayloadPreservation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGrepPayloadPreservation(t *testing.T) {
|
func TestGrepPayloadPreservation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
req := NewGrepRequest("g1", "s", 2, "error.*fatal", 25, true)
|
req := NewGrepRequest("g1", "s", 2, "error.*fatal", 25, true)
|
||||||
data, err := req.Marshal()
|
data, err := req.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -107,6 +108,7 @@ func TestGrepPayloadPreservation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDAGPlanPayloadPreservation(t *testing.T) {
|
func TestDAGPlanPayloadPreservation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
plan := DAGPlan{
|
plan := DAGPlan{
|
||||||
Nodes: []DAGNode{
|
Nodes: []DAGNode{
|
||||||
{ID: "a", Type: CmdToolSearch, Payload: ToolSearch{Query: "files", MaxResults: 5}, DependsOn: nil},
|
{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) {
|
func TestToolResponseMarshalRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
resp ToolResponse
|
resp ToolResponse
|
||||||
|
|
@ -169,17 +172,13 @@ func TestToolResponseMarshalRoundTrip(t *testing.T) {
|
||||||
decoded, err := UnmarshalResponse(data)
|
decoded, err := UnmarshalResponse(data)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, tt.resp.ID, decoded.ID)
|
assert.Empty(t, cmp.Diff(tt.resp, decoded))
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshalRequestJSON_UnknownType(t *testing.T) {
|
func TestUnmarshalRequestJSON_UnknownType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
data, _ := jsonv2.Marshal(map[string]interface{}{
|
data, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"id": "bad",
|
"id": "bad",
|
||||||
"type": "nonexistent_command",
|
"type": "nonexistent_command",
|
||||||
|
|
@ -190,21 +189,25 @@ func TestUnmarshalRequestJSON_UnknownType(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshalRequestJSON_InvalidJSON(t *testing.T) {
|
func TestUnmarshalRequestJSON_InvalidJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := UnmarshalRequestJSON([]byte(`{invalid`))
|
_, err := UnmarshalRequestJSON([]byte(`{invalid`))
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarshalRequestFB_UnknownType(t *testing.T) {
|
func TestMarshalRequestFB_UnknownType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := MarshalRequestFB(ToolRequest{ID: "bad", Type: CommandType("bogus")})
|
_, err := MarshalRequestFB(ToolRequest{ID: "bad", Type: CommandType("bogus")})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshalRequestFB_Garbage(t *testing.T) {
|
func TestUnmarshalRequestFB_Garbage(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := UnmarshalRequestFB([]byte{0, 0, 0, 0})
|
_, err := UnmarshalRequestFB([]byte{0, 0, 0, 0})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestJSON_Roundtrip(t *testing.T) {
|
func TestRequestJSON_Roundtrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
orig := NewToolExecRequest("j1", "s", "tc", "shell", `{"cmd":"ls"}`)
|
orig := NewToolExecRequest("j1", "s", "tc", "shell", `{"cmd":"ls"}`)
|
||||||
data, err := jsonv2.Marshal(orig)
|
data, err := jsonv2.Marshal(orig)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -217,6 +220,7 @@ func TestRequestJSON_Roundtrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResponseJSON_Roundtrip(t *testing.T) {
|
func TestResponseJSON_Roundtrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
orig := NewSuccessResponse("j2", "ok", 10)
|
orig := NewSuccessResponse("j2", "ok", 10)
|
||||||
data, err := jsonv2.Marshal(orig)
|
data, err := jsonv2.Marshal(orig)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@ package dag_test
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/itr/dag"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/itr/dag"
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/security/securebus"
|
"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) {
|
func TestExecutor_LinearDependencyChain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
toolMap := map[string]tools.Tool{
|
toolMap := map[string]tools.Tool{
|
||||||
"step1": &staticTool{name: "step1", result: "r1"},
|
"step1": &staticTool{name: "step1", result: "r1"},
|
||||||
"step2": &staticTool{name: "step2", result: "r2"},
|
"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)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, result.NodeResults["a"], "r1")
|
assert.Contains(t, result.NodeResults["a"], "r1")
|
||||||
assert.Contains(t, result.NodeResults["b"], "r2")
|
assert.Contains(t, result.NodeResults["b"], "r2")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecutor_ParallelNodes(t *testing.T) {
|
func TestExecutor_ParallelNodes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
toolMap := map[string]tools.Tool{
|
toolMap := map[string]tools.Tool{
|
||||||
"alpha": &staticTool{name: "alpha", result: "a-result"},
|
"alpha": &staticTool{name: "alpha", result: "a-result"},
|
||||||
"beta": &staticTool{name: "beta", result: "b-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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "a-result", result.NodeResults["n1"])
|
assert.Equal(t, "a-result", result.NodeResults["n1"])
|
||||||
assert.Equal(t, "b-result", result.NodeResults["n2"])
|
assert.Equal(t, "b-result", result.NodeResults["n2"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecutor_CycleDetection(t *testing.T) {
|
func TestExecutor_CycleDetection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
bus := makeBus(t, map[string]tools.Tool{})
|
bus := makeBus(t, map[string]tools.Tool{})
|
||||||
defer bus.Close()
|
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)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "cycle")
|
assert.Contains(t, err.Error(), "cycle")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecutor_WithJoiner(t *testing.T) {
|
func TestExecutor_WithJoiner(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
toolMap := map[string]tools.Tool{
|
toolMap := map[string]tools.Tool{
|
||||||
"tool1": &staticTool{name: "tool1", result: "data-A"},
|
"tool1": &staticTool{name: "tool1", result: "data-A"},
|
||||||
"tool2": &staticTool{name: "tool2", result: "data-B"},
|
"tool2": &staticTool{name: "tool2", result: "data-B"},
|
||||||
|
|
@ -140,24 +145,26 @@ func TestExecutor_WithJoiner(t *testing.T) {
|
||||||
JoinerQuery: "Combine the results into a summary",
|
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)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, result.FinalAnswer, "synthesized:")
|
assert.Contains(t, result.FinalAnswer, "synthesized:")
|
||||||
assert.Equal(t, uint32(50), result.TotalTokens)
|
assert.Equal(t, uint32(50), result.TotalTokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecutor_EmptyPlan(t *testing.T) {
|
func TestExecutor_EmptyPlan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
bus := makeBus(t, map[string]tools.Tool{})
|
bus := makeBus(t, map[string]tools.Tool{})
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
executor := dag.NewExecutor(bus, nil)
|
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)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, result.NodeResults)
|
assert.Empty(t, result.NodeResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_NodeRefSubstitution(t *testing.T) {
|
func TestResolver_NodeRefSubstitution(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
argsJSON := `{"query": "search for #nodeprev results"}`
|
argsJSON := `{"query": "search for #nodeprev results"}`
|
||||||
toolMap := map[string]tools.Tool{
|
toolMap := map[string]tools.Tool{
|
||||||
"search": &staticTool{name: "search", result: "found"},
|
"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)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, result.NodeResults["prev"], "previous-output")
|
assert.Contains(t, result.NodeResults["prev"], "previous-output")
|
||||||
assert.Contains(t, result.NodeResults["search"], "found")
|
assert.Contains(t, result.NodeResults["search"], "found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouter_SimpleQuerySelectsReAct(t *testing.T) {
|
func TestRouter_SimpleQuerySelectsReAct(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := dag.DefaultRouterConfig()
|
cfg := dag.DefaultRouterConfig()
|
||||||
mode := dag.Route(dag.ModeAuto, "What is the weather?", cfg)
|
mode := dag.Route(dag.ModeAuto, "What is the weather?", cfg)
|
||||||
assert.Equal(t, dag.ModeReAct, mode)
|
assert.Equal(t, dag.ModeReAct, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouter_ComplexQuerySelectsDAG(t *testing.T) {
|
func TestRouter_ComplexQuerySelectsDAG(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := dag.DefaultRouterConfig()
|
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)
|
mode := dag.Route(dag.ModeAuto, "Search for the latest news about AI, read the top 3 articles, and compare their viewpoints to create a summary report with aggregate statistics", cfg)
|
||||||
assert.Equal(t, dag.ModeDAG, mode)
|
assert.Equal(t, dag.ModeDAG, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouter_ExplicitModeOverridesAuto(t *testing.T) {
|
func TestRouter_ExplicitModeOverridesAuto(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := dag.DefaultRouterConfig()
|
cfg := dag.DefaultRouterConfig()
|
||||||
mode := dag.Route(dag.ModeReAct, "Do many complex parallel things simultaneously", cfg)
|
mode := dag.Route(dag.ModeReAct, "Do many complex parallel things simultaneously", cfg)
|
||||||
assert.Equal(t, dag.ModeReAct, mode)
|
assert.Equal(t, dag.ModeReAct, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlanner_ValidatePlan(t *testing.T) {
|
func TestPlanner_ValidatePlan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
plan string
|
plan string
|
||||||
|
|
@ -238,7 +249,7 @@ func TestPlanner_ValidatePlan(t *testing.T) {
|
||||||
return tt.plan, 10, nil
|
return tt.plan, 10, nil
|
||||||
}
|
}
|
||||||
planner := dag.NewPlanner(mockModel, nil, dag.DefaultPlannerConfig())
|
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 {
|
if tt.wantErr {
|
||||||
assert.Error(t, planErr)
|
assert.Error(t, planErr)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,41 @@ package dag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExtractJSON_PlainJSON(t *testing.T) {
|
func TestExtractJSON_PlainJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := `{"nodes": [{"id": "n1"}]}`
|
input := `{"nodes": [{"id": "n1"}]}`
|
||||||
assert.Equal(t, input, extractJSON(input))
|
assert.Equal(t, input, extractJSON(input))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractJSON_MarkdownFenced(t *testing.T) {
|
func TestExtractJSON_MarkdownFenced(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := "Here is the plan:\n```json\n{\"nodes\": [{\"id\": \"n1\"}]}\n```\nDone."
|
input := "Here is the plan:\n```json\n{\"nodes\": [{\"id\": \"n1\"}]}\n```\nDone."
|
||||||
assert.Equal(t, `{"nodes": [{"id": "n1"}]}`, extractJSON(input))
|
assert.Equal(t, `{"nodes": [{"id": "n1"}]}`, extractJSON(input))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractJSON_GenericFenced(t *testing.T) {
|
func TestExtractJSON_GenericFenced(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := "```\n{\"nodes\": []}\n```"
|
input := "```\n{\"nodes\": []}\n```"
|
||||||
assert.Equal(t, `{"nodes": []}`, extractJSON(input))
|
assert.Equal(t, `{"nodes": []}`, extractJSON(input))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractJSON_LeadingText(t *testing.T) {
|
func TestExtractJSON_LeadingText(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := "The plan is: {\"nodes\":[]}"
|
input := "The plan is: {\"nodes\":[]}"
|
||||||
assert.Equal(t, `{"nodes":[]}`, extractJSON(input))
|
assert.Equal(t, `{"nodes":[]}`, extractJSON(input))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindIndex(t *testing.T) {
|
func TestFindIndex(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, 0, findIndex("abc", "a"))
|
assert.Equal(t, 0, findIndex("abc", "a"))
|
||||||
assert.Equal(t, 2, findIndex("abc", "c"))
|
assert.Equal(t, 2, findIndex("abc", "c"))
|
||||||
assert.Equal(t, -1, findIndex("abc", "z"))
|
assert.Equal(t, -1, findIndex("abc", "z"))
|
||||||
|
|
@ -39,6 +45,7 @@ func TestFindIndex(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePlan_Valid(t *testing.T) {
|
func TestValidatePlan_Valid(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
plan := &itr.DAGPlan{
|
plan := &itr.DAGPlan{
|
||||||
Nodes: []itr.DAGNode{
|
Nodes: []itr.DAGNode{
|
||||||
{ID: "a", Type: itr.CmdToolExec},
|
{ID: "a", Type: itr.CmdToolExec},
|
||||||
|
|
@ -49,6 +56,7 @@ func TestValidatePlan_Valid(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePlan_Empty(t *testing.T) {
|
func TestValidatePlan_Empty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
plan := &itr.DAGPlan{Nodes: nil}
|
plan := &itr.DAGPlan{Nodes: nil}
|
||||||
err := validatePlan(plan)
|
err := validatePlan(plan)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
@ -56,6 +64,7 @@ func TestValidatePlan_Empty(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePlan_DuplicateID(t *testing.T) {
|
func TestValidatePlan_DuplicateID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
plan := &itr.DAGPlan{
|
plan := &itr.DAGPlan{
|
||||||
Nodes: []itr.DAGNode{
|
Nodes: []itr.DAGNode{
|
||||||
{ID: "x", Type: itr.CmdToolExec},
|
{ID: "x", Type: itr.CmdToolExec},
|
||||||
|
|
@ -68,6 +77,7 @@ func TestValidatePlan_DuplicateID(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePlan_EmptyID(t *testing.T) {
|
func TestValidatePlan_EmptyID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
plan := &itr.DAGPlan{
|
plan := &itr.DAGPlan{
|
||||||
Nodes: []itr.DAGNode{{ID: "", Type: itr.CmdToolExec}},
|
Nodes: []itr.DAGNode{{ID: "", Type: itr.CmdToolExec}},
|
||||||
}
|
}
|
||||||
|
|
@ -77,6 +87,7 @@ func TestValidatePlan_EmptyID(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePlan_UnknownDependency(t *testing.T) {
|
func TestValidatePlan_UnknownDependency(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
plan := &itr.DAGPlan{
|
plan := &itr.DAGPlan{
|
||||||
Nodes: []itr.DAGNode{
|
Nodes: []itr.DAGNode{
|
||||||
{ID: "a", Type: itr.CmdToolExec, DependsOn: []string{"missing"}},
|
{ID: "a", Type: itr.CmdToolExec, DependsOn: []string{"missing"}},
|
||||||
|
|
@ -88,6 +99,7 @@ func TestValidatePlan_UnknownDependency(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePlan_SelfDependency(t *testing.T) {
|
func TestValidatePlan_SelfDependency(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
plan := &itr.DAGPlan{
|
plan := &itr.DAGPlan{
|
||||||
Nodes: []itr.DAGNode{
|
Nodes: []itr.DAGNode{
|
||||||
{ID: "a", Type: itr.CmdToolExec, DependsOn: []string{"a"}},
|
{ID: "a", Type: itr.CmdToolExec, DependsOn: []string{"a"}},
|
||||||
|
|
@ -99,6 +111,7 @@ func TestValidatePlan_SelfDependency(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePlan_CyclicDependency(t *testing.T) {
|
func TestValidatePlan_CyclicDependency(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
plan := &itr.DAGPlan{
|
plan := &itr.DAGPlan{
|
||||||
Nodes: []itr.DAGNode{
|
Nodes: []itr.DAGNode{
|
||||||
{ID: "a", Type: itr.CmdToolExec, DependsOn: []string{"b"}},
|
{ID: "a", Type: itr.CmdToolExec, DependsOn: []string{"b"}},
|
||||||
|
|
@ -111,6 +124,7 @@ func TestValidatePlan_CyclicDependency(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePlanResponse_ValidJSON(t *testing.T) {
|
func TestParsePlanResponse_ValidJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := `{
|
input := `{
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{"id": "n1", "type": "tool_exec", "payload": {"tool_name": "read_file", "args_json": "{\"path\":\"/tmp/a\"}"}}
|
{"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) {
|
func TestParsePlanResponse_WithMarkdownFence(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := "```json\n" + `{"nodes": [{"id": "x", "type": "tool_search", "payload": {"query": "files"}}]}` + "\n```"
|
input := "```json\n" + `{"nodes": [{"id": "x", "type": "tool_search", "payload": {"query": "files"}}]}` + "\n```"
|
||||||
|
|
||||||
plan, err := parsePlanResponse(input)
|
plan, err := parsePlanResponse(input)
|
||||||
|
|
@ -143,11 +158,13 @@ func TestParsePlanResponse_WithMarkdownFence(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePlanResponse_InvalidJSON(t *testing.T) {
|
func TestParsePlanResponse_InvalidJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := parsePlanResponse("not json at all")
|
_, err := parsePlanResponse("not json at all")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlannerPlanE2E(t *testing.T) {
|
func TestPlannerPlanE2E(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockLLM := func(ctx context.Context, systemPrompt, userQuery string) (string, uint32, error) {
|
mockLLM := func(ctx context.Context, systemPrompt, userQuery string) (string, uint32, error) {
|
||||||
plan := itr.DAGPlan{
|
plan := itr.DAGPlan{
|
||||||
Nodes: []itr.DAGNode{
|
Nodes: []itr.DAGNode{
|
||||||
|
|
@ -164,7 +181,7 @@ func TestPlannerPlanE2E(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
planner := NewPlanner(mockLLM, nil, DefaultPlannerConfig())
|
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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint32(100), tokens)
|
assert.Equal(t, uint32(100), tokens)
|
||||||
require.Len(t, plan.Nodes, 2)
|
require.Len(t, plan.Nodes, 2)
|
||||||
|
|
@ -173,12 +190,13 @@ func TestPlannerPlanE2E(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlannerPlanLLMError(t *testing.T) {
|
func TestPlannerPlanLLMError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockLLM := func(ctx context.Context, systemPrompt, userQuery string) (string, uint32, error) {
|
mockLLM := func(ctx context.Context, systemPrompt, userQuery string) (string, uint32, error) {
|
||||||
return "", 50, assert.AnError
|
return "", 50, assert.AnError
|
||||||
}
|
}
|
||||||
|
|
||||||
planner := NewPlanner(mockLLM, nil, DefaultPlannerConfig())
|
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.Error(t, err)
|
||||||
assert.Equal(t, uint32(50), tokens)
|
assert.Equal(t, uint32(50), tokens)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNeedsReplan(t *testing.T) {
|
func TestNeedsReplan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.True(t, needsReplan("The task is incomplete [NEEDS_MORE_STEPS]"))
|
assert.True(t, needsReplan("The task is incomplete [NEEDS_MORE_STEPS]"))
|
||||||
assert.True(t, needsReplan("[NEEDS_MORE_STEPS]"))
|
assert.True(t, needsReplan("[NEEDS_MORE_STEPS]"))
|
||||||
assert.False(t, needsReplan("Task complete. Here is the answer."))
|
assert.False(t, needsReplan("Task complete. Here is the answer."))
|
||||||
|
|
@ -14,5 +15,6 @@ func TestNeedsReplan(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplanSentinelIsConsistent(t *testing.T) {
|
func TestReplanSentinelIsConsistent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, "[NEEDS_MORE_STEPS]", replanSentinel)
|
assert.Equal(t, "[NEEDS_MORE_STEPS]", replanSentinel)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTopologicalOrderLinear(t *testing.T) {
|
func TestTopologicalOrderLinear(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
states := map[string]*nodeState{
|
states := map[string]*nodeState{
|
||||||
"a": newNodeState("a", nil),
|
"a": newNodeState("a", nil),
|
||||||
"b": newNodeState("b", []string{"a"}),
|
"b": newNodeState("b", []string{"a"}),
|
||||||
|
|
@ -23,6 +24,7 @@ func TestTopologicalOrderLinear(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTopologicalOrderParallel(t *testing.T) {
|
func TestTopologicalOrderParallel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
states := map[string]*nodeState{
|
states := map[string]*nodeState{
|
||||||
"a": newNodeState("a", nil),
|
"a": newNodeState("a", nil),
|
||||||
"b": newNodeState("b", nil),
|
"b": newNodeState("b", nil),
|
||||||
|
|
@ -40,6 +42,7 @@ func TestTopologicalOrderParallel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTopologicalOrderCycleDetection(t *testing.T) {
|
func TestTopologicalOrderCycleDetection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
states := map[string]*nodeState{
|
states := map[string]*nodeState{
|
||||||
"a": newNodeState("a", []string{"c"}),
|
"a": newNodeState("a", []string{"c"}),
|
||||||
"b": newNodeState("b", []string{"a"}),
|
"b": newNodeState("b", []string{"a"}),
|
||||||
|
|
@ -52,6 +55,7 @@ func TestTopologicalOrderCycleDetection(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTopologicalOrderSingleNode(t *testing.T) {
|
func TestTopologicalOrderSingleNode(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
states := map[string]*nodeState{
|
states := map[string]*nodeState{
|
||||||
"only": newNodeState("only", nil),
|
"only": newNodeState("only", nil),
|
||||||
}
|
}
|
||||||
|
|
@ -63,6 +67,7 @@ func TestTopologicalOrderSingleNode(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveRefs(t *testing.T) {
|
func TestResolveRefs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
states := map[string]*nodeState{
|
states := map[string]*nodeState{
|
||||||
"search": newNodeState("search", nil),
|
"search": newNodeState("search", nil),
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +80,7 @@ func TestResolveRefs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveRefsNoMatch(t *testing.T) {
|
func TestResolveRefsNoMatch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
states := map[string]*nodeState{}
|
states := map[string]*nodeState{}
|
||||||
input := `{"path":"#nodemissing"}`
|
input := `{"path":"#nodemissing"}`
|
||||||
result := resolveRefs(input, states)
|
result := resolveRefs(input, states)
|
||||||
|
|
@ -82,6 +88,7 @@ func TestResolveRefsNoMatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveToolExecArgsNoRefs(t *testing.T) {
|
func TestResolveToolExecArgsNoRefs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
states := map[string]*nodeState{}
|
states := map[string]*nodeState{}
|
||||||
input := `{"path":"/tmp/plain.txt"}`
|
input := `{"path":"/tmp/plain.txt"}`
|
||||||
result := resolveToolExecArgs(input, states)
|
result := resolveToolExecArgs(input, states)
|
||||||
|
|
@ -89,6 +96,7 @@ func TestResolveToolExecArgsNoRefs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEscapeForJSON(t *testing.T) {
|
func TestEscapeForJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
expected string
|
expected string
|
||||||
|
|
@ -106,6 +114,7 @@ func TestEscapeForJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNodeStateSetAndGetResult(t *testing.T) {
|
func TestNodeStateSetAndGetResult(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
ns := newNodeState("test", nil)
|
ns := newNodeState("test", nil)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRouteExplicitModes(t *testing.T) {
|
func TestRouteExplicitModes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultRouterConfig()
|
cfg := DefaultRouterConfig()
|
||||||
|
|
||||||
assert.Equal(t, ModeReAct, Route(ModeReAct, "anything", cfg))
|
assert.Equal(t, ModeReAct, Route(ModeReAct, "anything", cfg))
|
||||||
|
|
@ -15,17 +16,20 @@ func TestRouteExplicitModes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteAutoSimpleQuery(t *testing.T) {
|
func TestRouteAutoSimpleQuery(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultRouterConfig()
|
cfg := DefaultRouterConfig()
|
||||||
assert.Equal(t, ModeReAct, Route(ModeAuto, "what is the weather?", cfg))
|
assert.Equal(t, ModeReAct, Route(ModeAuto, "what is the weather?", cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteAutoComplexQuery(t *testing.T) {
|
func TestRouteAutoComplexQuery(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultRouterConfig()
|
cfg := DefaultRouterConfig()
|
||||||
longQuery := strings.Repeat("word ", 35)
|
longQuery := strings.Repeat("word ", 35)
|
||||||
assert.Equal(t, ModeDAG, Route(ModeAuto, longQuery, cfg))
|
assert.Equal(t, ModeDAG, Route(ModeAuto, longQuery, cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteAutoParallelKeywords(t *testing.T) {
|
func TestRouteAutoParallelKeywords(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultRouterConfig()
|
cfg := DefaultRouterConfig()
|
||||||
|
|
||||||
keywords := []string{
|
keywords := []string{
|
||||||
|
|
@ -42,12 +46,14 @@ func TestRouteAutoParallelKeywords(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteAutoToolSignals(t *testing.T) {
|
func TestRouteAutoToolSignals(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultRouterConfig()
|
cfg := DefaultRouterConfig()
|
||||||
q := "search the codebase, read the file, then execute the command"
|
q := "search the codebase, read the file, then execute the command"
|
||||||
assert.Equal(t, ModeDAG, Route(ModeAuto, q, cfg))
|
assert.Equal(t, ModeDAG, Route(ModeAuto, q, cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolLoopModeString(t *testing.T) {
|
func TestToolLoopModeString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, "react", ModeReAct.String())
|
assert.Equal(t, "react", ModeReAct.String())
|
||||||
assert.Equal(t, "dag", ModeDAG.String())
|
assert.Equal(t, "dag", ModeDAG.String())
|
||||||
assert.Equal(t, "auto", ModeAuto.String())
|
assert.Equal(t, "auto", ModeAuto.String())
|
||||||
|
|
@ -55,6 +61,7 @@ func TestToolLoopModeString(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClassifyQueryDefault(t *testing.T) {
|
func TestClassifyQueryDefault(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultRouterConfig()
|
cfg := DefaultRouterConfig()
|
||||||
assert.Equal(t, ModeReAct, classifyQuery("hello", cfg))
|
assert.Equal(t, ModeReAct, classifyQuery("hello", cfg))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,165 +5,63 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFBCodec_RequestRoundtrip_Peek(t *testing.T) {
|
func TestFBCodec_RequestRoundtrip(t *testing.T) {
|
||||||
orig := itr.NewPeekRequest("req-1", "sess-A", 2, 1024, 4096)
|
t.Parallel()
|
||||||
|
|
||||||
data, err := itr.MarshalRequestFB(orig)
|
tests := []struct {
|
||||||
require.NoError(t, err)
|
name string
|
||||||
|
req itr.ToolRequest
|
||||||
got, err := itr.UnmarshalRequestFB(data)
|
}{
|
||||||
require.NoError(t, err)
|
{
|
||||||
|
name: "peek",
|
||||||
assert.Equal(t, orig.ID, got.ID)
|
req: itr.NewPeekRequest("req-1", "sess-A", 2, 1024, 4096),
|
||||||
assert.Equal(t, orig.Type, got.Type)
|
},
|
||||||
assert.Equal(t, orig.Depth, got.Depth)
|
{
|
||||||
assert.Equal(t, orig.SessionKey, got.SessionKey)
|
name: "grep",
|
||||||
origP, ok := orig.Payload.(itr.Peek)
|
req: itr.NewGrepRequest("req-2", "sess-B", 1, "error.*fatal", 25, true),
|
||||||
require.True(t, ok, "orig payload should be Peek")
|
},
|
||||||
gotP, ok := got.Payload.(itr.Peek)
|
{
|
||||||
require.True(t, ok, "got payload should be Peek")
|
name: "partition",
|
||||||
assert.Equal(t, origP, gotP)
|
req: itr.NewPartitionRequest("req-3", "sess-C", 0, 8, "semantic", 100, true),
|
||||||
}
|
},
|
||||||
|
{
|
||||||
func TestFBCodec_RequestRoundtrip_Grep(t *testing.T) {
|
name: "recurse",
|
||||||
orig := itr.NewGrepRequest("req-2", "sess-B", 1, "error.*fatal", 25, true)
|
req: itr.NewRecurseRequest("req-4", "sess-D", 3, "summarize this", "ctx-key-7", 5),
|
||||||
|
},
|
||||||
data, err := itr.MarshalRequestFB(orig)
|
{
|
||||||
require.NoError(t, err)
|
name: "tool exec",
|
||||||
|
req: itr.NewToolExecRequest("req-5", "sess-E", "tc-1", "read_file", `{"path":"/etc/hosts"}`),
|
||||||
got, err := itr.UnmarshalRequestFB(data)
|
},
|
||||||
require.NoError(t, err)
|
{
|
||||||
|
name: "exec wasm",
|
||||||
assert.Equal(t, orig.Type, got.Type)
|
req: itr.ToolRequest{
|
||||||
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{
|
|
||||||
ID: "req-6",
|
ID: "req-6",
|
||||||
Type: itr.CmdExecWasm,
|
Type: itr.CmdExecWasm,
|
||||||
Payload: itr.ExecWasm{ModuleKey: "mod-1", Entry: "main", InputJSON: `{"x":1}`},
|
Payload: itr.ExecWasm{ModuleKey: "mod-1", Entry: "main", InputJSON: `{"x":1}`},
|
||||||
Timestamp: time.Now().UnixNano(),
|
Timestamp: time.Now().UnixNano(),
|
||||||
SessionKey: "sess-F",
|
SessionKey: "sess-F",
|
||||||
}
|
},
|
||||||
|
},
|
||||||
data, err := itr.MarshalRequestFB(orig)
|
{
|
||||||
require.NoError(t, err)
|
name: "final",
|
||||||
|
req: itr.NewFinalRequest("req-7", "sess-G", 2, "The answer is 42", "ans_var"),
|
||||||
got, err := itr.UnmarshalRequestFB(data)
|
},
|
||||||
require.NoError(t, err)
|
{
|
||||||
|
name: "tool search",
|
||||||
p, ok := got.Payload.(itr.ExecWasm)
|
req: itr.NewToolSearchRequest("req-8", "sess-H", "find file tools", 5),
|
||||||
require.True(t, ok, "payload should be ExecWasm")
|
},
|
||||||
assert.Equal(t, "mod-1", p.ModuleKey)
|
{
|
||||||
assert.Equal(t, "main", p.Entry)
|
name: "code exec",
|
||||||
assert.Equal(t, `{"x":1}`, p.InputJSON)
|
req: itr.NewCodeExecRequest("req-9", "sess-I", "console.log('hi')", "javascript"),
|
||||||
}
|
},
|
||||||
|
{
|
||||||
func TestFBCodec_RequestRoundtrip_Final(t *testing.T) {
|
name: "dag plan",
|
||||||
orig := itr.NewFinalRequest("req-7", "sess-G", 2, "The answer is 42", "ans_var")
|
req: itr.NewDAGPlanRequest("req-10", "sess-J", itr.DAGPlan{
|
||||||
|
|
||||||
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{
|
|
||||||
Nodes: []itr.DAGNode{
|
Nodes: []itr.DAGNode{
|
||||||
{
|
{
|
||||||
ID: "a",
|
ID: "a",
|
||||||
|
|
@ -180,106 +78,75 @@ func TestFBCodec_RequestRoundtrip_DAGPlan(t *testing.T) {
|
||||||
MaxParallel: 4,
|
MaxParallel: 4,
|
||||||
TokenBudget: 10000,
|
TokenBudget: 10000,
|
||||||
JoinerQuery: "Summarize the results",
|
JoinerQuery: "Summarize the results",
|
||||||
}
|
}),
|
||||||
orig := itr.NewDAGPlanRequest("req-10", "sess-J", plan)
|
},
|
||||||
|
{
|
||||||
data, err := itr.MarshalRequestFB(orig)
|
name: "timestamp preserved",
|
||||||
require.NoError(t, err)
|
req: itr.ToolRequest{
|
||||||
|
|
||||||
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{
|
|
||||||
ID: "ts-test",
|
ID: "ts-test",
|
||||||
Type: itr.CmdPeek,
|
Type: itr.CmdPeek,
|
||||||
Payload: itr.Peek{Start: 0, Length: 10},
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
got, err := itr.UnmarshalRequestFB(data)
|
got, err := itr.UnmarshalRequestFB(data)
|
||||||
require.NoError(t, err)
|
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) {
|
func TestFBCodec_UnknownCommandType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := itr.MarshalRequestFB(itr.ToolRequest{
|
_, err := itr.MarshalRequestFB(itr.ToolRequest{
|
||||||
ID: "bad",
|
ID: "bad",
|
||||||
Type: itr.CommandType("nonexistent"),
|
Type: itr.CommandType("nonexistent"),
|
||||||
})
|
})
|
||||||
assert.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package wasm
|
package wasm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -47,14 +46,16 @@ var minimalWASM = []byte{
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewRuntime(t *testing.T) {
|
func TestNewRuntime(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
|
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer rt.Close(ctx)
|
defer rt.Close(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecuteMinimalModule(t *testing.T) {
|
func TestExecuteMinimalModule(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
|
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer rt.Close(ctx)
|
defer rt.Close(ctx)
|
||||||
|
|
@ -67,7 +68,8 @@ func TestExecuteMinimalModule(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecuteTimeout(t *testing.T) {
|
func TestExecuteTimeout(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
cfg := DefaultRuntimeConfig()
|
cfg := DefaultRuntimeConfig()
|
||||||
cfg.ExecTimeout = 1 * time.Millisecond
|
cfg.ExecTimeout = 1 * time.Millisecond
|
||||||
rt, err := NewRuntime(ctx, cfg)
|
rt, err := NewRuntime(ctx, cfg)
|
||||||
|
|
@ -83,7 +85,8 @@ func TestExecuteTimeout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecuteInvalidWASM(t *testing.T) {
|
func TestExecuteInvalidWASM(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
|
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer rt.Close(ctx)
|
defer rt.Close(ctx)
|
||||||
|
|
@ -94,7 +97,8 @@ func TestExecuteInvalidWASM(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRuntimeCloseIdempotent(t *testing.T) {
|
func TestRuntimeCloseIdempotent(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
|
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
@ -103,7 +107,8 @@ func TestRuntimeCloseIdempotent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecuteAfterClose(t *testing.T) {
|
func TestExecuteAfterClose(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
|
rt, err := NewRuntime(ctx, DefaultRuntimeConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
rt.Close(ctx)
|
rt.Close(ctx)
|
||||||
|
|
@ -114,6 +119,7 @@ func TestExecuteAfterClose(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLimitedBuffer(t *testing.T) {
|
func TestLimitedBuffer(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
lb := &limitedBuffer{max: 5}
|
lb := &limitedBuffer{max: 5}
|
||||||
n, err := lb.Write([]byte("hello world"))
|
n, err := lb.Write([]byte("hello world"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -122,6 +128,7 @@ func TestLimitedBuffer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLimitedBufferExactFit(t *testing.T) {
|
func TestLimitedBufferExactFit(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
lb := &limitedBuffer{max: 5}
|
lb := &limitedBuffer{max: 5}
|
||||||
n, err := lb.Write([]byte("hello"))
|
n, err := lb.Write([]byte("hello"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTransportNonCodeExecForwarded(t *testing.T) {
|
func TestTransportNonCodeExecForwarded(t *testing.T) {
|
||||||
rt, err := NewRuntime(context.Background(), DefaultRuntimeConfig())
|
t.Parallel()
|
||||||
|
rt, err := NewRuntime(t.Context(), DefaultRuntimeConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer rt.Close(context.Background())
|
defer rt.Close(t.Context())
|
||||||
|
|
||||||
forwarded := false
|
forwarded := false
|
||||||
transport := NewTransport(rt, func(ctx context.Context, req itr.ToolRequest) (itr.ToolResponse, error) {
|
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"}`)
|
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)
|
require.NoError(t, err)
|
||||||
assert.True(t, forwarded)
|
assert.True(t, forwarded)
|
||||||
assert.Equal(t, "forwarded", resp.Result)
|
assert.Equal(t, "forwarded", resp.Result)
|
||||||
|
|
@ -29,35 +30,38 @@ func TestTransportNonCodeExecForwarded(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransportNonCodeExecNoFallback(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)
|
require.NoError(t, err)
|
||||||
defer rt.Close(context.Background())
|
defer rt.Close(t.Context())
|
||||||
|
|
||||||
transport := NewTransport(rt, nil)
|
transport := NewTransport(rt, nil)
|
||||||
|
|
||||||
req := itr.NewToolExecRequest("id-1", "sess", "tc", "read_file", `{"path":"/tmp"}`)
|
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)
|
require.NoError(t, err)
|
||||||
assert.True(t, resp.IsError)
|
assert.True(t, resp.IsError)
|
||||||
assert.Contains(t, resp.Result, "unsupported command type")
|
assert.Contains(t, resp.Result, "unsupported command type")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransportCodeExecEmptyCode(t *testing.T) {
|
func TestTransportCodeExecEmptyCode(t *testing.T) {
|
||||||
rt, err := NewRuntime(context.Background(), DefaultRuntimeConfig())
|
t.Parallel()
|
||||||
|
rt, err := NewRuntime(t.Context(), DefaultRuntimeConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer rt.Close(context.Background())
|
defer rt.Close(t.Context())
|
||||||
|
|
||||||
transport := NewTransport(rt, nil)
|
transport := NewTransport(rt, nil)
|
||||||
|
|
||||||
req := itr.NewCodeExecRequest("id-1", "sess", "", "javascript")
|
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)
|
require.NoError(t, err)
|
||||||
assert.True(t, resp.IsError)
|
assert.True(t, resp.IsError)
|
||||||
assert.Contains(t, resp.Result, "empty code")
|
assert.Contains(t, resp.Result, "empty code")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransportClose(t *testing.T) {
|
func TestTransportClose(t *testing.T) {
|
||||||
rt, err := NewRuntime(context.Background(), DefaultRuntimeConfig())
|
t.Parallel()
|
||||||
|
rt, err := NewRuntime(t.Context(), DefaultRuntimeConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
transport := NewTransport(rt, nil)
|
transport := NewTransport(rt, nil)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogLevelFiltering(t *testing.T) {
|
func TestLogLevelFiltering(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
initialLevel := GetLevel()
|
initialLevel := GetLevel()
|
||||||
defer SetLevel(initialLevel)
|
defer SetLevel(initialLevel)
|
||||||
|
|
||||||
|
|
@ -45,6 +46,7 @@ func TestLogLevelFiltering(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoggerWithComponent(t *testing.T) {
|
func TestLoggerWithComponent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
initialLevel := GetLevel()
|
initialLevel := GetLevel()
|
||||||
defer SetLevel(initialLevel)
|
defer SetLevel(initialLevel)
|
||||||
|
|
||||||
|
|
@ -81,6 +83,7 @@ func TestLoggerWithComponent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogLevels(t *testing.T) {
|
func TestLogLevels(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
level LogLevel
|
level LogLevel
|
||||||
|
|
@ -103,6 +106,7 @@ func TestLogLevels(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetGetLevel(t *testing.T) {
|
func TestSetGetLevel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
initialLevel := GetLevel()
|
initialLevel := GetLevel()
|
||||||
defer SetLevel(initialLevel)
|
defer SetLevel(initialLevel)
|
||||||
|
|
||||||
|
|
@ -117,6 +121,7 @@ func TestSetGetLevel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoggerHelperFunctions(t *testing.T) {
|
func TestLoggerHelperFunctions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
initialLevel := GetLevel()
|
initialLevel := GetLevel()
|
||||||
defer SetLevel(initialLevel)
|
defer SetLevel(initialLevel)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package dag_test
|
package dag_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -18,7 +17,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBackfillMissingSessionDAGs_CreatesSnapshotsAndPersistsStatus(t *testing.T) {
|
func TestBackfillMissingSessionDAGs_CreatesSnapshotsAndPersistsStatus(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
d, err := delegate.NewLibSQLInMemory()
|
d, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, d.Init(ctx))
|
require.NoError(t, d.Init(ctx))
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
@ -24,6 +26,7 @@ func makeMessages(n int) []Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompressor_EmptyInput(t *testing.T) {
|
func TestCompressor_EmptyInput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := NewCompressor(DefaultCompressorConfig())
|
c := NewCompressor(DefaultCompressorConfig())
|
||||||
d := c.Compress(nil)
|
d := c.Compress(nil)
|
||||||
assert.Empty(t, d.Nodes)
|
assert.Empty(t, d.Nodes)
|
||||||
|
|
@ -31,6 +34,7 @@ func TestCompressor_EmptyInput(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompressor_SmallInput(t *testing.T) {
|
func TestCompressor_SmallInput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := NewCompressor(DefaultCompressorConfig())
|
c := NewCompressor(DefaultCompressorConfig())
|
||||||
msgs := []Message{
|
msgs := []Message{
|
||||||
{Role: "user", Content: "Hello, how are you?"},
|
{Role: "user", Content: "Hello, how are you?"},
|
||||||
|
|
@ -48,6 +52,7 @@ func TestCompressor_SmallInput(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompressor_ChunkSplitting(t *testing.T) {
|
func TestCompressor_ChunkSplitting(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultCompressorConfig()
|
cfg := DefaultCompressorConfig()
|
||||||
cfg.ChunkSize = 4
|
cfg.ChunkSize = 4
|
||||||
c := NewCompressor(cfg)
|
c := NewCompressor(cfg)
|
||||||
|
|
@ -67,6 +72,7 @@ func TestCompressor_ChunkSplitting(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompressor_SectionBuilding(t *testing.T) {
|
func TestCompressor_SectionBuilding(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultCompressorConfig()
|
cfg := DefaultCompressorConfig()
|
||||||
cfg.ChunkSize = 4
|
cfg.ChunkSize = 4
|
||||||
cfg.SectionSize = 2
|
cfg.SectionSize = 2
|
||||||
|
|
@ -89,6 +95,7 @@ func TestCompressor_SectionBuilding(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompressor_SessionSummary(t *testing.T) {
|
func TestCompressor_SessionSummary(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultCompressorConfig()
|
cfg := DefaultCompressorConfig()
|
||||||
cfg.ChunkSize = 4
|
cfg.ChunkSize = 4
|
||||||
cfg.SectionSize = 2
|
cfg.SectionSize = 2
|
||||||
|
|
@ -109,6 +116,7 @@ func TestCompressor_SessionSummary(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractSentences(t *testing.T) {
|
func TestExtractSentences(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
text string
|
text string
|
||||||
|
|
@ -133,6 +141,7 @@ func TestExtractSentences(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractSentences_Truncation(t *testing.T) {
|
func TestExtractSentences_Truncation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
long := strings.Repeat("This is a very long sentence with many words. ", 20)
|
long := strings.Repeat("This is a very long sentence with many words. ", 20)
|
||||||
result := extractSentences(long, 5)
|
result := extractSentences(long, 5)
|
||||||
assert.LessOrEqual(t, len([]rune(result)), 210)
|
assert.LessOrEqual(t, len([]rune(result)), 210)
|
||||||
|
|
@ -140,6 +149,7 @@ func TestExtractSentences_Truncation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNode_FormatForPrompt(t *testing.T) {
|
func TestNode_FormatForPrompt(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
n := &Node{
|
n := &Node{
|
||||||
ID: "chunk-1",
|
ID: "chunk-1",
|
||||||
Level: LevelChunk,
|
Level: LevelChunk,
|
||||||
|
|
@ -153,6 +163,7 @@ func TestNode_FormatForPrompt(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDAG_FormatLevel(t *testing.T) {
|
func TestDAG_FormatLevel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultCompressorConfig()
|
cfg := DefaultCompressorConfig()
|
||||||
cfg.ChunkSize = 4
|
cfg.ChunkSize = 4
|
||||||
c := NewCompressor(cfg)
|
c := NewCompressor(cfg)
|
||||||
|
|
@ -167,6 +178,7 @@ func TestDAG_FormatLevel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDAG_TotalTokens(t *testing.T) {
|
func TestDAG_TotalTokens(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultCompressorConfig()
|
cfg := DefaultCompressorConfig()
|
||||||
cfg.ChunkSize = 4
|
cfg.ChunkSize = 4
|
||||||
c := NewCompressor(cfg)
|
c := NewCompressor(cfg)
|
||||||
|
|
@ -179,25 +191,30 @@ func TestDAG_TotalTokens(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComputeBudget(t *testing.T) {
|
func TestComputeBudget(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultBudgetConfig()
|
cfg := DefaultBudgetConfig()
|
||||||
b := ComputeBudget(100000, cfg)
|
b := ComputeBudget(100000, cfg)
|
||||||
|
|
||||||
assert.Equal(t, 100000, b.Total)
|
assert.Empty(t, cmp.Diff(Budget{
|
||||||
assert.Equal(t, 20000, b.SystemPrompt)
|
Total: 100000,
|
||||||
assert.Equal(t, 10000, b.Observations)
|
SystemPrompt: 20000,
|
||||||
assert.Equal(t, 5000, b.Knowledge)
|
Observations: 10000,
|
||||||
assert.Equal(t, 25000, b.DAGSummaries)
|
Knowledge: 5000,
|
||||||
assert.Equal(t, 30000, b.RawTail)
|
DAGSummaries: 25000,
|
||||||
assert.Equal(t, 10000, b.ToolResults)
|
RawTail: 30000,
|
||||||
|
ToolResults: 10000,
|
||||||
|
}, b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBudget_Remaining(t *testing.T) {
|
func TestBudget_Remaining(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
b := Budget{Total: 10000}
|
b := Budget{Total: 10000}
|
||||||
assert.Equal(t, 7000, b.Remaining(1000, 500, 500, 500, 500, 0))
|
assert.Equal(t, 7000, b.Remaining(1000, 500, 500, 500, 500, 0))
|
||||||
assert.Equal(t, 0, b.Remaining(5000, 3000, 1000, 1000, 1000, 0))
|
assert.Equal(t, 0, b.Remaining(5000, 3000, 1000, 1000, 1000, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectDAGLevel(t *testing.T) {
|
func TestSelectDAGLevel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultCompressorConfig()
|
cfg := DefaultCompressorConfig()
|
||||||
cfg.ChunkSize = 4
|
cfg.ChunkSize = 4
|
||||||
cfg.SectionSize = 2
|
cfg.SectionSize = 2
|
||||||
|
|
@ -219,6 +236,7 @@ func TestSelectDAGLevel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTailMessageCount(t *testing.T) {
|
func TestTailMessageCount(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, 4, TailMessageCount(100)) // Minimum
|
assert.Equal(t, 4, TailMessageCount(100)) // Minimum
|
||||||
assert.Equal(t, 20, TailMessageCount(1000)) // 1000/50
|
assert.Equal(t, 20, TailMessageCount(1000)) // 1000/50
|
||||||
assert.Equal(t, 4, TailMessageCount(0)) // Zero budget
|
assert.Equal(t, 4, TailMessageCount(0)) // Zero budget
|
||||||
|
|
@ -226,6 +244,7 @@ func TestTailMessageCount(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderDAGForBudget(t *testing.T) {
|
func TestRenderDAGForBudget(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Empty(t, RenderDAGForBudget(nil, 1000))
|
assert.Empty(t, RenderDAGForBudget(nil, 1000))
|
||||||
|
|
||||||
cfg := DefaultCompressorConfig()
|
cfg := DefaultCompressorConfig()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package delegate
|
package delegate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -12,6 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewFromConfig_LocalOnly_DefaultPath(t *testing.T) {
|
func TestNewFromConfig_LocalOnly_DefaultPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
defaultPath := filepath.Join(tmpDir, "test.db")
|
defaultPath := filepath.Join(tmpDir, "test.db")
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ func TestNewFromConfig_LocalOnly_DefaultPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
if err := d.Init(context.Background()); err != nil {
|
if err := d.Init(t.Context()); err != nil {
|
||||||
t.Fatalf("Init: %v", err)
|
t.Fatalf("Init: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,13 +32,14 @@ func TestNewFromConfig_LocalOnly_DefaultPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's functional
|
// 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 {
|
if err != nil {
|
||||||
t.Fatalf("UpsertWorkingContext: %v", err)
|
t.Fatalf("UpsertWorkingContext: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_LocalOnly_CustomPath(t *testing.T) {
|
func TestNewFromConfig_LocalOnly_CustomPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
customPath := filepath.Join(tmpDir, "custom.db")
|
customPath := filepath.Join(tmpDir, "custom.db")
|
||||||
|
|
||||||
|
|
@ -52,7 +53,7 @@ func TestNewFromConfig_LocalOnly_CustomPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
if err := d.Init(context.Background()); err != nil {
|
if err := d.Init(t.Context()); err != nil {
|
||||||
t.Fatalf("Init: %v", err)
|
t.Fatalf("Init: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,6 +64,7 @@ func TestNewFromConfig_LocalOnly_CustomPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_CustomDims(t *testing.T) {
|
func TestNewFromConfig_CustomDims(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
dbPath := filepath.Join(tmpDir, "test.db")
|
dbPath := filepath.Join(tmpDir, "test.db")
|
||||||
|
|
||||||
|
|
@ -82,6 +84,7 @@ func TestNewFromConfig_CustomDims(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_DefaultDims(t *testing.T) {
|
func TestNewFromConfig_DefaultDims(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
dbPath := filepath.Join(tmpDir, "test.db")
|
dbPath := filepath.Join(tmpDir, "test.db")
|
||||||
|
|
||||||
|
|
@ -99,6 +102,7 @@ func TestNewFromConfig_DefaultDims(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_ReplicaFallback(t *testing.T) {
|
func TestNewFromConfig_ReplicaFallback(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
dbPath := filepath.Join(tmpDir, "test.db")
|
dbPath := filepath.Join(tmpDir, "test.db")
|
||||||
|
|
||||||
|
|
@ -121,12 +125,13 @@ func TestNewFromConfig_ReplicaFallback(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should still be functional in local mode
|
// 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)
|
t.Fatalf("Init: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromConfig_LocalFullRoundTrip(t *testing.T) {
|
func TestNewFromConfig_LocalFullRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
dbPath := filepath.Join(tmpDir, "roundtrip.db")
|
dbPath := filepath.Join(tmpDir, "roundtrip.db")
|
||||||
|
|
||||||
|
|
@ -140,11 +145,11 @@ func TestNewFromConfig_LocalFullRoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
if err := d.Init(context.Background()); err != nil {
|
if err := d.Init(t.Context()); err != nil {
|
||||||
t.Fatalf("Init: %v", err)
|
t.Fatalf("Init: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Working context round-trip
|
// Working context round-trip
|
||||||
if err := d.UpsertWorkingContext(ctx, "a1", "s1", "hello"); err != nil {
|
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) {
|
func TestSyncConfig_Defaults(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := config.DefaultConfig()
|
cfg := config.DefaultConfig()
|
||||||
// Memory is always enabled -- no Enabled field to check.
|
// Memory is always enabled -- no Enabled field to check.
|
||||||
if cfg.Memory.EmbeddingDims != 768 {
|
if cfg.Memory.EmbeddingDims != 768 {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ func makeAuditEntry(agentID, sessionKey, action, target string) *memory.AuditEnt
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_InsertAuditEntry(t *testing.T) {
|
func TestLibSQLDelegate_InsertAuditEntry(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
entry *memory.AuditEntry
|
entry *memory.AuditEntry
|
||||||
|
|
@ -54,7 +55,7 @@ func TestLibSQLDelegate_InsertAuditEntry(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
require.NoError(t, d.InsertAuditEntry(ctx, tt.entry))
|
require.NoError(t, d.InsertAuditEntry(ctx, tt.entry))
|
||||||
|
|
||||||
count, err := d.CountAuditEntries(ctx, tt.entry.AgentID)
|
count, err := d.CountAuditEntries(ctx, tt.entry.AgentID)
|
||||||
|
|
@ -65,6 +66,7 @@ func TestLibSQLDelegate_InsertAuditEntry(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_ListAuditEntries(t *testing.T) {
|
func TestLibSQLDelegate_ListAuditEntries(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -114,7 +116,7 @@ func TestLibSQLDelegate_ListAuditEntries(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +128,7 @@ func TestLibSQLDelegate_ListAuditEntries(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_ListAuditEntriesByAction(t *testing.T) {
|
func TestLibSQLDelegate_ListAuditEntriesByAction(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -160,7 +163,7 @@ func TestLibSQLDelegate_ListAuditEntriesByAction(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -177,6 +180,7 @@ func TestLibSQLDelegate_ListAuditEntriesByAction(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_ListAuditEntriesBySession(t *testing.T) {
|
func TestLibSQLDelegate_ListAuditEntriesBySession(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -209,7 +213,7 @@ func TestLibSQLDelegate_ListAuditEntriesBySession(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -221,8 +225,9 @@ func TestLibSQLDelegate_ListAuditEntriesBySession(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_PruneOldAuditEntries(t *testing.T) {
|
func TestLibSQLDelegate_PruneOldAuditEntries(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
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", "t1")))
|
||||||
require.NoError(t, d.InsertAuditEntry(ctx, makeAuditEntry("a1", "s1", "tool_call", "t2")))
|
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) {
|
func TestLibSQLDelegate_CountAuditEntriesByAction(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -269,7 +275,7 @@ func TestLibSQLDelegate_CountAuditEntriesByAction(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package delegate
|
package delegate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
||||||
|
|
@ -10,7 +9,7 @@ import (
|
||||||
|
|
||||||
func BenchmarkListRecallItems(b *testing.B) {
|
func BenchmarkListRecallItems(b *testing.B) {
|
||||||
d := newBenchDelegate(b)
|
d := newBenchDelegate(b)
|
||||||
ctx := context.Background()
|
ctx := b.Context()
|
||||||
agent := "bench-agent"
|
agent := "bench-agent"
|
||||||
session := "bench-sess"
|
session := "bench-sess"
|
||||||
|
|
||||||
|
|
@ -36,7 +35,7 @@ func BenchmarkListRecallItems(b *testing.B) {
|
||||||
|
|
||||||
func BenchmarkGetWorkingContext(b *testing.B) {
|
func BenchmarkGetWorkingContext(b *testing.B) {
|
||||||
d := newBenchDelegate(b)
|
d := newBenchDelegate(b)
|
||||||
ctx := context.Background()
|
ctx := b.Context()
|
||||||
agent := "bench-agent"
|
agent := "bench-agent"
|
||||||
session := "bench-sess"
|
session := "bench-sess"
|
||||||
|
|
||||||
|
|
@ -51,7 +50,7 @@ func BenchmarkGetWorkingContext(b *testing.B) {
|
||||||
|
|
||||||
func BenchmarkUpsertKV(b *testing.B) {
|
func BenchmarkUpsertKV(b *testing.B) {
|
||||||
d := newBenchDelegate(b)
|
d := newBenchDelegate(b)
|
||||||
ctx := context.Background()
|
ctx := b.Context()
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
@ -62,7 +61,7 @@ func BenchmarkUpsertKV(b *testing.B) {
|
||||||
|
|
||||||
func BenchmarkGetKV(b *testing.B) {
|
func BenchmarkGetKV(b *testing.B) {
|
||||||
d := newBenchDelegate(b)
|
d := newBenchDelegate(b)
|
||||||
ctx := context.Background()
|
ctx := b.Context()
|
||||||
_ = d.UpsertKV(ctx, "bench-agent", "bench-key", "bench-value")
|
_ = d.UpsertKV(ctx, "bench-agent", "bench-key", "bench-value")
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
@ -74,7 +73,7 @@ func BenchmarkGetKV(b *testing.B) {
|
||||||
|
|
||||||
func BenchmarkInsertAuditEntry(b *testing.B) {
|
func BenchmarkInsertAuditEntry(b *testing.B) {
|
||||||
d := newBenchDelegate(b)
|
d := newBenchDelegate(b)
|
||||||
ctx := context.Background()
|
ctx := b.Context()
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
@ -93,7 +92,7 @@ func BenchmarkInsertAuditEntry(b *testing.B) {
|
||||||
// BenchmarkInsertRecallItems_Sequential measures sequential single-insert performance.
|
// BenchmarkInsertRecallItems_Sequential measures sequential single-insert performance.
|
||||||
func BenchmarkInsertRecallItems_Sequential(b *testing.B) {
|
func BenchmarkInsertRecallItems_Sequential(b *testing.B) {
|
||||||
d := newBenchDelegate(b)
|
d := newBenchDelegate(b)
|
||||||
ctx := context.Background()
|
ctx := b.Context()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
|
|
@ -116,7 +115,7 @@ func BenchmarkInsertRecallItems_Sequential(b *testing.B) {
|
||||||
// Compare with BenchmarkInsertRecallItems_Sequential to quantify WAL savings.
|
// Compare with BenchmarkInsertRecallItems_Sequential to quantify WAL savings.
|
||||||
func BenchmarkInsertRecallItems_Batch(b *testing.B) {
|
func BenchmarkInsertRecallItems_Batch(b *testing.B) {
|
||||||
d := newBenchDelegate(b)
|
d := newBenchDelegate(b)
|
||||||
ctx := context.Background()
|
ctx := b.Context()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
|
|
@ -140,7 +139,7 @@ func BenchmarkInsertRecallItems_Batch(b *testing.B) {
|
||||||
// BenchmarkInsertArchivalChunks_Sequential measures sequential chunk inserts.
|
// BenchmarkInsertArchivalChunks_Sequential measures sequential chunk inserts.
|
||||||
func BenchmarkInsertArchivalChunks_Sequential(b *testing.B) {
|
func BenchmarkInsertArchivalChunks_Sequential(b *testing.B) {
|
||||||
d := newBenchDelegate(b)
|
d := newBenchDelegate(b)
|
||||||
ctx := context.Background()
|
ctx := b.Context()
|
||||||
recallID := ids.New()
|
recallID := ids.New()
|
||||||
_ = d.InsertRecallItem(ctx, &memory.RecallItem{
|
_ = d.InsertRecallItem(ctx, &memory.RecallItem{
|
||||||
ID: recallID, AgentID: "bench-agent", SessionKey: "s",
|
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.
|
// BenchmarkInsertArchivalChunks_Batch measures batch-tx chunk inserts for 5 chunks.
|
||||||
func BenchmarkInsertArchivalChunks_Batch(b *testing.B) {
|
func BenchmarkInsertArchivalChunks_Batch(b *testing.B) {
|
||||||
d := newBenchDelegate(b)
|
d := newBenchDelegate(b)
|
||||||
ctx := context.Background()
|
ctx := b.Context()
|
||||||
recallID := ids.New()
|
recallID := ids.New()
|
||||||
_ = d.InsertRecallItem(ctx, &memory.RecallItem{
|
_ = d.InsertRecallItem(ctx, &memory.RecallItem{
|
||||||
ID: recallID, AgentID: "bench-agent", SessionKey: "s",
|
ID: recallID, AgentID: "bench-agent", SessionKey: "s",
|
||||||
|
|
@ -185,11 +184,12 @@ func BenchmarkInsertArchivalChunks_Batch(b *testing.B) {
|
||||||
|
|
||||||
func newBenchDelegate(b *testing.B) *LibSQLDelegate {
|
func newBenchDelegate(b *testing.B) *LibSQLDelegate {
|
||||||
b.Helper()
|
b.Helper()
|
||||||
|
ctx := b.Context()
|
||||||
d, err := NewLibSQLInMemory()
|
d, err := NewLibSQLInMemory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := d.Init(context.Background()); err != nil {
|
if err := d.Init(ctx); err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
b.Cleanup(func() { d.Close() })
|
b.Cleanup(func() { d.Close() })
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package delegate
|
package delegate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/memory/dag"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/memory/dag"
|
||||||
|
|
@ -10,8 +9,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLibSQLDelegate_PersistDAG(t *testing.T) {
|
func TestLibSQLDelegate_PersistDAG(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
compressor := dag.NewCompressor(dag.DefaultCompressorConfig())
|
compressor := dag.NewCompressor(dag.DefaultCompressorConfig())
|
||||||
msgs := make([]dag.Message, 16)
|
msgs := make([]dag.Message, 16)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ func makeDoc(agentID, name, category, content string) *memory.AgentDocument {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_GetDocument(t *testing.T) {
|
func TestLibSQLDelegate_GetDocument(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -67,7 +68,7 @@ func TestLibSQLDelegate_GetDocument(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +87,7 @@ func TestLibSQLDelegate_GetDocument(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_UpsertDocument(t *testing.T) {
|
func TestLibSQLDelegate_UpsertDocument(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
ops []*memory.AgentDocument
|
ops []*memory.AgentDocument
|
||||||
|
|
@ -139,7 +141,7 @@ func TestLibSQLDelegate_UpsertDocument(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
for _, doc := range tt.ops {
|
for _, doc := range tt.ops {
|
||||||
require.NoError(t, d.UpsertDocument(ctx, doc))
|
require.NoError(t, d.UpsertDocument(ctx, doc))
|
||||||
}
|
}
|
||||||
|
|
@ -152,6 +154,7 @@ func TestLibSQLDelegate_UpsertDocument(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_DeleteDocument(t *testing.T) {
|
func TestLibSQLDelegate_DeleteDocument(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -185,7 +188,7 @@ func TestLibSQLDelegate_DeleteDocument(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -206,6 +209,7 @@ func TestLibSQLDelegate_DeleteDocument(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_ListDocumentsByCategory(t *testing.T) {
|
func TestLibSQLDelegate_ListDocumentsByCategory(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -256,7 +260,7 @@ func TestLibSQLDelegate_ListDocumentsByCategory(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -271,6 +275,7 @@ func TestLibSQLDelegate_ListDocumentsByCategory(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_ListAllDocuments(t *testing.T) {
|
func TestLibSQLDelegate_ListAllDocuments(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -318,7 +323,7 @@ func TestLibSQLDelegate_ListAllDocuments(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package delegate
|
package delegate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
@ -14,8 +13,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCronKVBackend_Roundtrip(t *testing.T) {
|
func TestCronKVBackend_Roundtrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
agentID := pkg.NAME
|
agentID := pkg.NAME
|
||||||
kvKey := "cron:store"
|
kvKey := "cron:store"
|
||||||
|
|
||||||
|
|
@ -61,8 +61,9 @@ func TestCronKVBackend_Roundtrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCronKVBackend_UpdatePreservesShape(t *testing.T) {
|
func TestCronKVBackend_UpdatePreservesShape(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
agentID := pkg.NAME
|
agentID := pkg.NAME
|
||||||
kvKey := "cron:store"
|
kvKey := "cron:store"
|
||||||
|
|
||||||
|
|
@ -78,8 +79,9 @@ func TestCronKVBackend_UpdatePreservesShape(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCronKVBackend_PrefixScan(t *testing.T) {
|
func TestCronKVBackend_PrefixScan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
agentID := pkg.NAME
|
agentID := pkg.NAME
|
||||||
|
|
||||||
require.NoError(t, d.UpsertKV(ctx, agentID, "cron:store", "{}"))
|
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) {
|
func TestEndToEnd_SessionAndAuditFlow(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
agentID := "a1"
|
agentID := "a1"
|
||||||
sessionKey := "sess-integration"
|
sessionKey := "sess-integration"
|
||||||
|
|
||||||
|
|
@ -171,8 +174,9 @@ func TestEndToEnd_SessionAndAuditFlow(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEndToEnd_WorkingContextAndRecallRoundtrip(t *testing.T) {
|
func TestEndToEnd_WorkingContextAndRecallRoundtrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
agentID := "a1"
|
agentID := "a1"
|
||||||
sessionKey := "sess-wc"
|
sessionKey := "sess-wc"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLibSQLDelegate_GetKV(t *testing.T) {
|
func TestLibSQLDelegate_GetKV(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -73,7 +74,7 @@ func TestLibSQLDelegate_GetKV(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -89,6 +90,7 @@ func TestLibSQLDelegate_GetKV(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_UpsertKV(t *testing.T) {
|
func TestLibSQLDelegate_UpsertKV(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
ops []kvOp
|
ops []kvOp
|
||||||
|
|
@ -147,7 +149,7 @@ func TestLibSQLDelegate_UpsertKV(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
for _, op := range tt.ops {
|
for _, op := range tt.ops {
|
||||||
require.NoError(t, d.UpsertKV(ctx, op.agent, op.key, op.val))
|
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) {
|
func TestLibSQLDelegate_DeleteKV(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -193,7 +196,7 @@ func TestLibSQLDelegate_DeleteKV(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +221,7 @@ func TestLibSQLDelegate_DeleteKV(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_ListKVByPrefix(t *testing.T) {
|
func TestLibSQLDelegate_ListKVByPrefix(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -286,7 +290,7 @@ func TestLibSQLDelegate_ListKVByPrefix(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ func makeRecallItem(agentID, sessionKey, role, content, tags string) *memory.Rec
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_InsertSessionMessage(t *testing.T) {
|
func TestLibSQLDelegate_InsertSessionMessage(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
agentID string
|
agentID string
|
||||||
|
|
@ -70,7 +71,7 @@ func TestLibSQLDelegate_InsertSessionMessage(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
require.NoError(t, d.InsertSessionMessage(ctx, tt.agentID, tt.sessionKey, tt.role, tt.content))
|
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) {
|
func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -182,7 +184,7 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -206,6 +208,7 @@ func TestLibSQLDelegate_ListSessionMessages(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_CountSessionMessages(t *testing.T) {
|
func TestLibSQLDelegate_CountSessionMessages(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
setup func(t *testing.T, d *LibSQLDelegate, ctx context.Context)
|
||||||
|
|
@ -257,7 +260,7 @@ func TestLibSQLDelegate_CountSessionMessages(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
tt.setup(t, d, ctx)
|
tt.setup(t, d, ctx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package delegate
|
package delegate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -15,7 +14,7 @@ func newTestDelegate(t *testing.T) *LibSQLDelegate {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewLibSQLInMemory: %v", err)
|
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.Fatalf("Init: %v", err)
|
||||||
}
|
}
|
||||||
t.Cleanup(func() { d.Close() })
|
t.Cleanup(func() { d.Close() })
|
||||||
|
|
@ -23,8 +22,9 @@ func newTestDelegate(t *testing.T) *LibSQLDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_WorkingContext(t *testing.T) {
|
func TestLibSQLDelegate_WorkingContext(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Initially nil
|
// Initially nil
|
||||||
wc, err := d.GetWorkingContext(ctx, "agent-1", "sess-1")
|
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) {
|
func TestLibSQLDelegate_RecallItemCRUD(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
item := &memory.RecallItem{
|
item := &memory.RecallItem{
|
||||||
ID: ids.New(),
|
ID: ids.New(),
|
||||||
|
|
@ -156,8 +157,9 @@ func testEmbedding768(seed ...float32) []float32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_ArchivalChunkCRUD(t *testing.T) {
|
func TestLibSQLDelegate_ArchivalChunkCRUD(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
parentRecall := &memory.RecallItem{
|
parentRecall := &memory.RecallItem{
|
||||||
ID: ids.New(), AgentID: "agent-1", SessionKey: "sess-1",
|
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) {
|
func TestLibSQLDelegate_SummaryCRUD(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
summary := &memory.MemorySummary{
|
summary := &memory.MemorySummary{
|
||||||
ID: ids.New(),
|
ID: ids.New(),
|
||||||
|
|
@ -266,8 +269,9 @@ func TestLibSQLDelegate_SummaryCRUD(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_KeywordSearch(t *testing.T) {
|
func TestLibSQLDelegate_KeywordSearch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
items := []*memory.RecallItem{
|
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"},
|
{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) {
|
func TestLibSQLDelegate_Counts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Initial counts should be zero
|
// Initial counts should be zero
|
||||||
rc, err := d.CountRecallItems(ctx, "agent-1", "")
|
rc, err := d.CountRecallItems(ctx, "agent-1", "")
|
||||||
|
|
@ -328,8 +333,9 @@ func TestLibSQLDelegate_Counts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_FTSSearch(t *testing.T) {
|
func TestLibSQLDelegate_FTSSearch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Insert recall items with searchable content
|
// Insert recall items with searchable content
|
||||||
items := []*memory.RecallItem{
|
items := []*memory.RecallItem{
|
||||||
|
|
@ -382,8 +388,9 @@ func TestLibSQLDelegate_FTSSearch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_VectorSearch(t *testing.T) {
|
func TestLibSQLDelegate_VectorSearch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Insert archival chunks with 768-dim embeddings (schema requires F32_BLOB(768))
|
// Insert archival chunks with 768-dim embeddings (schema requires F32_BLOB(768))
|
||||||
embData := [][]float32{
|
embData := [][]float32{
|
||||||
|
|
@ -439,6 +446,7 @@ func TestLibSQLDelegate_VectorSearch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildFTSMatchExpr(t *testing.T) {
|
func TestBuildFTSMatchExpr(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
expected string
|
expected string
|
||||||
|
|
@ -465,6 +473,7 @@ func TestBuildFTSMatchExpr(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVectorToString(t *testing.T) {
|
func TestVectorToString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input memory.Embedding
|
input memory.Embedding
|
||||||
expected string
|
expected string
|
||||||
|
|
@ -485,7 +494,10 @@ func TestVectorToString(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractVector(t *testing.T) {
|
func TestExtractVector(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Round-trip test: Embedding.Value() -> blob -> extractVector
|
// Round-trip test: Embedding.Value() -> blob -> extractVector
|
||||||
|
)
|
||||||
|
|
||||||
original := memory.Embedding{0.1, -0.2, 0.3, 0.99, -0.01}
|
original := memory.Embedding{0.1, -0.2, 0.3, 0.99, -0.01}
|
||||||
dv, err := original.Value()
|
dv, err := original.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -510,6 +522,7 @@ func TestExtractVector(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLibSQLDelegate_Capabilities(t *testing.T) {
|
func TestLibSQLDelegate_Capabilities(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
|
|
||||||
// After Init(), capabilities should have been probed
|
// 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)
|
// Calling detect again should be a no-op (idempotent)
|
||||||
d.detectCapabilities(context.Background())
|
d.detectCapabilities(t.Context())
|
||||||
if d.HasFTS() != hasFTS || d.HasVectorSearch() != hasVec {
|
if d.HasFTS() != hasFTS || d.HasVectorSearch() != hasVec {
|
||||||
t.Error("detectCapabilities changed results on second call — not idempotent")
|
t.Error("detectCapabilities changed results on second call — not idempotent")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmbeddingValueScanRoundTrip(t *testing.T) {
|
func TestEmbeddingValueScanRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
vectors := []memory.Embedding{
|
vectors := []memory.Embedding{
|
||||||
{0.0, 1.0, -1.0, 0.5, -0.5},
|
{0.0, 1.0, -1.0, 0.5, -0.5},
|
||||||
{3.4028235e+38, -3.4028235e+38}, // max float32
|
{3.4028235e+38, -3.4028235e+38}, // max float32
|
||||||
|
|
@ -578,8 +592,9 @@ func TestEmbeddingValueScanRoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegration_FullStackNoDisk(t *testing.T) {
|
func TestIntegration_FullStackNoDisk(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := newTestDelegate(t)
|
d := newTestDelegate(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
agentID := "integration-agent"
|
agentID := "integration-agent"
|
||||||
|
|
||||||
t.Run("KV_Store", func(t *testing.T) {
|
t.Run("KV_Store", func(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
package memory_test
|
package memory_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
||||||
|
|
@ -23,7 +22,7 @@ func setupFullStack(t *testing.T) (*memstore.MemoryStore, *delegate.LibSQLDelega
|
||||||
t.Helper()
|
t.Helper()
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err, "create in-memory delegate")
|
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() })
|
t.Cleanup(func() { del.Close() })
|
||||||
|
|
||||||
chunker := memstore.NewMarkdownChunker(memstore.DefaultMarkdownChunkerConfig())
|
chunker := memstore.NewMarkdownChunker(memstore.DefaultMarkdownChunkerConfig())
|
||||||
|
|
@ -33,21 +32,23 @@ func setupFullStack(t *testing.T) (*memstore.MemoryStore, *delegate.LibSQLDelega
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegration_GooseMigrationIdempotent(t *testing.T) {
|
func TestIntegration_GooseMigrationIdempotent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
require.NoError(t, del.Init(ctx), "first migration up")
|
require.NoError(t, del.Init(ctx), "first migration up")
|
||||||
require.NoError(t, del.Init(ctx), "idempotent re-Init")
|
require.NoError(t, del.Init(ctx), "idempotent re-Init")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegration_GooseMigration_DownUpRoundTrip(t *testing.T) {
|
func TestIntegration_GooseMigration_DownUpRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
require.NoError(t, del.Init(ctx), "initial up")
|
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) {
|
func TestIntegration_BlobPK_RoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
store, _ := setupFullStack(t)
|
store, _ := setupFullStack(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
item := &memory.RecallItem{
|
item := &memory.RecallItem{
|
||||||
AgentID: testAgent,
|
AgentID: testAgent,
|
||||||
|
|
@ -106,8 +108,9 @@ func TestIntegration_BlobPK_RoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegration_ArchivalChunking_EndToEnd(t *testing.T) {
|
func TestIntegration_ArchivalChunking_EndToEnd(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
store, del := setupFullStack(t)
|
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 := "# 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"
|
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) {
|
func TestIntegration_FTS5Search(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
store, _ := setupFullStack(t)
|
store, _ := setupFullStack(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
items := []struct {
|
items := []struct {
|
||||||
content string
|
content string
|
||||||
|
|
@ -172,8 +176,9 @@ func TestIntegration_FTS5Search(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegration_CascadeDelete(t *testing.T) {
|
func TestIntegration_CascadeDelete(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
store, del := setupFullStack(t)
|
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{
|
recallID, err := store.StoreArchival(ctx, "Archival content to be cascade deleted", testAgent, map[string]string{
|
||||||
"agent_id": testAgent,
|
"agent_id": testAgent,
|
||||||
|
|
@ -195,8 +200,9 @@ func TestIntegration_CascadeDelete(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegration_ToolOffload(t *testing.T) {
|
func TestIntegration_ToolOffload(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
store, _ := setupFullStack(t)
|
store, _ := setupFullStack(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
largeResult := ""
|
largeResult := ""
|
||||||
for i := 0; i < 500; i++ {
|
for i := 0; i < 500; i++ {
|
||||||
|
|
@ -211,8 +217,9 @@ func TestIntegration_ToolOffload(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegration_WorkingContext_Persistence(t *testing.T) {
|
func TestIntegration_WorkingContext_Persistence(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, del := setupFullStack(t)
|
_, del := setupFullStack(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
require.NoError(t, del.UpsertWorkingContext(ctx, testAgent, testSession, "initial state"))
|
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) {
|
func TestIntegration_Summary_CRUD(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
store, del := setupFullStack(t)
|
store, del := setupFullStack(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
summary := &memory.MemorySummary{
|
summary := &memory.MemorySummary{
|
||||||
AgentID: testAgent,
|
AgentID: testAgent,
|
||||||
|
|
@ -249,8 +257,9 @@ func TestIntegration_Summary_CRUD(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegration_IDUniqueness_AcrossEntities(t *testing.T) {
|
func TestIntegration_IDUniqueness_AcrossEntities(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
store, _ := setupFullStack(t)
|
store, _ := setupFullStack(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
seenIDs := make(map[ids.UUID]string)
|
seenIDs := make(map[ids.UUID]string)
|
||||||
|
|
||||||
|
|
@ -284,8 +293,9 @@ func TestIntegration_IDUniqueness_AcrossEntities(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegration_ContextPressure(t *testing.T) {
|
func TestIntegration_ContextPressure(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
store, _ := setupFullStack(t)
|
store, _ := setupFullStack(t)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
pressure, err := store.ContextUsage(ctx, testAgent, testSession)
|
pressure, err := store.ContextUsage(ctx, testAgent, testSession)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestActiveContextProjection_TotalTokens(t *testing.T) {
|
func TestActiveContextProjection_TotalTokens(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
p := &ActiveContextProjection{
|
p := &ActiveContextProjection{
|
||||||
Segments: []ProjectionSegment{
|
Segments: []ProjectionSegment{
|
||||||
{Tokens: 120},
|
{Tokens: 120},
|
||||||
|
|
@ -20,6 +21,7 @@ func TestActiveContextProjection_TotalTokens(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestActiveContextProjection_HasLosslessRefs(t *testing.T) {
|
func TestActiveContextProjection_HasLosslessRefs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
p := &ActiveContextProjection{
|
p := &ActiveContextProjection{
|
||||||
Segments: []ProjectionSegment{
|
Segments: []ProjectionSegment{
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ func writeSessionFile(t *testing.T, dir, name string, sess SessionFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMigrateFileSessions_Basic(t *testing.T) {
|
func TestMigrateFileSessions_Basic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sessDir := t.TempDir()
|
sessDir := t.TempDir()
|
||||||
del := newMockDelegate()
|
del := newMockDelegate()
|
||||||
|
|
||||||
|
|
@ -182,6 +183,7 @@ func TestMigrateFileSessions_Basic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMigrateFileSessions_Idempotent(t *testing.T) {
|
func TestMigrateFileSessions_Idempotent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sessDir := t.TempDir()
|
sessDir := t.TempDir()
|
||||||
del := newMockDelegate()
|
del := newMockDelegate()
|
||||||
|
|
||||||
|
|
@ -217,6 +219,7 @@ func TestMigrateFileSessions_Idempotent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMigrateFileSessions_EmptyDir(t *testing.T) {
|
func TestMigrateFileSessions_EmptyDir(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sessDir := t.TempDir()
|
sessDir := t.TempDir()
|
||||||
del := newMockDelegate()
|
del := newMockDelegate()
|
||||||
|
|
||||||
|
|
@ -230,6 +233,7 @@ func TestMigrateFileSessions_EmptyDir(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMigrateFileSessions_NonexistentDir(t *testing.T) {
|
func TestMigrateFileSessions_NonexistentDir(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
del := newMockDelegate()
|
del := newMockDelegate()
|
||||||
|
|
||||||
result, err := MigrateFileSessions(t.Context(), del, pkg.NAME, "/nonexistent/path")
|
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) {
|
func TestMigrateFileSessions_SkipsEmptyMessages(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sessDir := t.TempDir()
|
sessDir := t.TempDir()
|
||||||
del := newMockDelegate()
|
del := newMockDelegate()
|
||||||
|
|
||||||
|
|
@ -264,6 +269,7 @@ func TestMigrateFileSessions_SkipsEmptyMessages(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMigrateFileSessions_FallbackKey(t *testing.T) {
|
func TestMigrateFileSessions_FallbackKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sessDir := t.TempDir()
|
sessDir := t.TempDir()
|
||||||
del := newMockDelegate()
|
del := newMockDelegate()
|
||||||
|
|
||||||
|
|
@ -288,6 +294,7 @@ func TestMigrateFileSessions_FallbackKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMigrateFileSessions_MalformedJSON(t *testing.T) {
|
func TestMigrateFileSessions_MalformedJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sessDir := t.TempDir()
|
sessDir := t.TempDir()
|
||||||
del := newMockDelegate()
|
del := newMockDelegate()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,24 +7,30 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewObservation_ThreeDateModel(t *testing.T) {
|
func TestNewObservation_ThreeDateModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
ref := time.Date(2026, 2, 16, 10, 0, 0, 0, time.UTC)
|
ref := time.Date(2026, 2, 16, 10, 0, 0, 0, time.UTC)
|
||||||
obs := time.Date(2026, 2, 18, 14, 30, 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)
|
o := NewObservation("User prefers Go over Rust", PriorityNotable, ref, obs)
|
||||||
|
|
||||||
assert.Equal(t, "User prefers Go over Rust", o.Content)
|
assert.Empty(t, cmp.Diff(Observation{
|
||||||
assert.Equal(t, PriorityNotable, o.Priority)
|
Content: "User prefers Go over Rust",
|
||||||
assert.Equal(t, ref.Unix(), o.ReferencedAt)
|
Priority: PriorityNotable,
|
||||||
assert.Equal(t, obs.Unix(), o.ObservedAt)
|
ObservedAt: obs.Unix(),
|
||||||
assert.Equal(t, "2 days ago", o.RelativeDate)
|
ReferencedAt: ref.Unix(),
|
||||||
|
RelativeDate: "2 days ago",
|
||||||
|
}, o))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRelativeDate(t *testing.T) {
|
func TestRelativeDate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
now := time.Date(2026, 2, 18, 14, 0, 0, 0, time.UTC)
|
now := time.Date(2026, 2, 18, 14, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
@ -53,6 +59,7 @@ func TestRelativeDate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPriorityEmoji(t *testing.T) {
|
func TestPriorityEmoji(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, "🔴", PriorityCritical.Emoji())
|
assert.Equal(t, "🔴", PriorityCritical.Emoji())
|
||||||
assert.Equal(t, "🟡", PriorityNotable.Emoji())
|
assert.Equal(t, "🟡", PriorityNotable.Emoji())
|
||||||
assert.Equal(t, "🔵", PriorityInformational.Emoji())
|
assert.Equal(t, "🔵", PriorityInformational.Emoji())
|
||||||
|
|
@ -60,6 +67,7 @@ func TestPriorityEmoji(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatBlock(t *testing.T) {
|
func TestFormatBlock(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
now := time.Date(2026, 2, 18, 14, 30, 0, 0, time.UTC)
|
now := time.Date(2026, 2, 18, 14, 30, 0, 0, time.UTC)
|
||||||
obs := []Observation{
|
obs := []Observation{
|
||||||
NewObservation("Decision: use SQLite", PriorityCritical, now, now),
|
NewObservation("Decision: use SQLite", PriorityCritical, now, now),
|
||||||
|
|
@ -75,11 +83,13 @@ func TestFormatBlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatBlock_Empty(t *testing.T) {
|
func TestFormatBlock_Empty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, "", FormatBlock(nil))
|
assert.Equal(t, "", FormatBlock(nil))
|
||||||
assert.Equal(t, "", FormatBlock([]Observation{}))
|
assert.Equal(t, "", FormatBlock([]Observation{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarshalUnmarshalRoundTrip(t *testing.T) {
|
func TestMarshalUnmarshalRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
obs := []Observation{
|
obs := []Observation{
|
||||||
NewObservation("Fact A", PriorityCritical, now, now),
|
NewObservation("Fact A", PriorityCritical, now, now),
|
||||||
|
|
@ -93,23 +103,25 @@ func TestMarshalUnmarshalRoundTrip(t *testing.T) {
|
||||||
parsed, err := UnmarshalObservations(data)
|
parsed, err := UnmarshalObservations(data)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, parsed, 2)
|
assert.Len(t, parsed, 2)
|
||||||
assert.Equal(t, "Fact A", parsed[0].Content)
|
assert.Empty(t, cmp.Diff(obs[0], parsed[0]))
|
||||||
assert.Equal(t, PriorityCritical, parsed[0].Priority)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshalObservations_Empty(t *testing.T) {
|
func TestUnmarshalObservations_Empty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
obs, err := UnmarshalObservations("")
|
obs, err := UnmarshalObservations("")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, obs)
|
assert.Nil(t, obs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEstimateTokens(t *testing.T) {
|
func TestEstimateTokens(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tokens := EstimateTokens("Hello, world!")
|
tokens := EstimateTokens("Hello, world!")
|
||||||
assert.True(t, tokens > 0)
|
assert.True(t, tokens > 0)
|
||||||
assert.True(t, tokens < 20)
|
assert.True(t, tokens < 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseObservations(t *testing.T) {
|
func TestParseObservations(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
response := `critical|User decided to migrate to SQLite
|
response := `critical|User decided to migrate to SQLite
|
||||||
notable|Prefers hexagonal architecture
|
notable|Prefers hexagonal architecture
|
||||||
|
|
@ -119,13 +131,31 @@ notable|`
|
||||||
|
|
||||||
obs := parseObservations(response, now)
|
obs := parseObservations(response, now)
|
||||||
assert.Len(t, obs, 3)
|
assert.Len(t, obs, 3)
|
||||||
assert.Equal(t, PriorityCritical, obs[0].Priority)
|
assert.Empty(t, cmp.Diff(Observation{
|
||||||
assert.Equal(t, "User decided to migrate to SQLite", obs[0].Content)
|
Content: "User decided to migrate to SQLite",
|
||||||
assert.Equal(t, PriorityNotable, obs[1].Priority)
|
Priority: PriorityCritical,
|
||||||
assert.Equal(t, PriorityInformational, obs[2].Priority)
|
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) {
|
func TestObserver_ShouldObserve(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockModel := func(_ context.Context, _ string) (string, error) {
|
mockModel := func(_ context.Context, _ string) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
@ -140,6 +170,7 @@ func TestObserver_ShouldObserve(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestObserver_Observe(t *testing.T) {
|
func TestObserver_Observe(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockModel := func(_ context.Context, prompt string) (string, error) {
|
mockModel := func(_ context.Context, prompt string) (string, error) {
|
||||||
return "critical|Important decision made\nnotable|User preference noted", nil
|
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"},
|
{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)
|
require.NoError(t, err)
|
||||||
assert.Len(t, obs, 2)
|
assert.Len(t, obs, 2)
|
||||||
assert.Equal(t, PriorityCritical, obs[0].Priority)
|
assert.Equal(t, PriorityCritical, obs[0].Priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReflector_ShouldReflect(t *testing.T) {
|
func TestReflector_ShouldReflect(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockModel := func(_ context.Context, _ string) (string, error) {
|
mockModel := func(_ context.Context, _ string) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
@ -175,6 +207,7 @@ func TestReflector_ShouldReflect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReflector_Reflect(t *testing.T) {
|
func TestReflector_Reflect(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockModel := func(_ context.Context, prompt string) (string, error) {
|
mockModel := func(_ context.Context, prompt string) (string, error) {
|
||||||
return "KEEP 0\nDROP 1\nKEEP 2", nil
|
return "KEEP 0\nDROP 1\nKEEP 2", nil
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +221,7 @@ func TestReflector_Reflect(t *testing.T) {
|
||||||
NewObservation("Notable thing", PriorityNotable, now, now),
|
NewObservation("Notable thing", PriorityNotable, now, now),
|
||||||
}
|
}
|
||||||
|
|
||||||
kept, err := r.Reflect(context.Background(), obs)
|
kept, err := r.Reflect(t.Context(), obs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, kept, 2)
|
assert.Len(t, kept, 2)
|
||||||
assert.Equal(t, "Critical fact", kept[0].Content)
|
assert.Equal(t, "Critical fact", kept[0].Content)
|
||||||
|
|
@ -196,6 +229,7 @@ func TestReflector_Reflect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReflector_ReflectFallbackKeepsCritical(t *testing.T) {
|
func TestReflector_ReflectFallbackKeepsCritical(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockModel := func(_ context.Context, _ string) (string, error) {
|
mockModel := func(_ context.Context, _ string) (string, error) {
|
||||||
return "garbage output", nil
|
return "garbage output", nil
|
||||||
}
|
}
|
||||||
|
|
@ -208,13 +242,14 @@ func TestReflector_ReflectFallbackKeepsCritical(t *testing.T) {
|
||||||
NewObservation("Can drop", PriorityInformational, now, now),
|
NewObservation("Can drop", PriorityInformational, now, now),
|
||||||
}
|
}
|
||||||
|
|
||||||
kept, err := r.Reflect(context.Background(), obs)
|
kept, err := r.Reflect(t.Context(), obs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, kept, 1)
|
assert.Len(t, kept, 1)
|
||||||
assert.Equal(t, "Must keep", kept[0].Content)
|
assert.Equal(t, "Must keep", kept[0].Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePriority(t *testing.T) {
|
func TestParsePriority(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, PriorityCritical, parsePriority("critical"))
|
assert.Equal(t, PriorityCritical, parsePriority("critical"))
|
||||||
assert.Equal(t, PriorityCritical, parsePriority("CRITICAL"))
|
assert.Equal(t, PriorityCritical, parsePriority("CRITICAL"))
|
||||||
assert.Equal(t, PriorityNotable, parsePriority("notable"))
|
assert.Equal(t, PriorityNotable, parsePriority("notable"))
|
||||||
|
|
@ -223,6 +258,7 @@ func TestParsePriority(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeptIndices(t *testing.T) {
|
func TestParseKeptIndices(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
obs := []Observation{
|
obs := []Observation{
|
||||||
NewObservation("A", PriorityCritical, now, now),
|
NewObservation("A", PriorityCritical, now, now),
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,10 @@ func (e *countingEmbedder) Dimensions() int { return e.dims }
|
||||||
func (e *countingEmbedder) Model() string { return "test-model" }
|
func (e *countingEmbedder) Model() string { return "test-model" }
|
||||||
|
|
||||||
func TestCachedEmbedder_CachesIdenticalText(t *testing.T) {
|
func TestCachedEmbedder_CachesIdenticalText(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
inner := &countingEmbedder{dims: 8}
|
inner := &countingEmbedder{dims: 8}
|
||||||
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// First call — should hit inner
|
// First call — should hit inner
|
||||||
vec1, err := cached.Embed(ctx, "hello world")
|
vec1, err := cached.Embed(ctx, "hello world")
|
||||||
|
|
@ -76,9 +77,10 @@ func TestCachedEmbedder_CachesIdenticalText(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedEmbedder_DifferentTextHitsInner(t *testing.T) {
|
func TestCachedEmbedder_DifferentTextHitsInner(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
inner := &countingEmbedder{dims: 4}
|
inner := &countingEmbedder{dims: 4}
|
||||||
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
cached.Embed(ctx, "text A")
|
cached.Embed(ctx, "text A")
|
||||||
cached.Embed(ctx, "text B")
|
cached.Embed(ctx, "text B")
|
||||||
|
|
@ -93,9 +95,10 @@ func TestCachedEmbedder_DifferentTextHitsInner(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedEmbedder_BatchPartialCache(t *testing.T) {
|
func TestCachedEmbedder_BatchPartialCache(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
inner := &countingEmbedder{dims: 4}
|
inner := &countingEmbedder{dims: 4}
|
||||||
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Pre-cache one text
|
// Pre-cache one text
|
||||||
cached.Embed(ctx, "cached text")
|
cached.Embed(ctx, "cached text")
|
||||||
|
|
@ -126,9 +129,10 @@ func TestCachedEmbedder_BatchPartialCache(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedEmbedder_BatchAllCached(t *testing.T) {
|
func TestCachedEmbedder_BatchAllCached(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
inner := &countingEmbedder{dims: 4}
|
inner := &countingEmbedder{dims: 4}
|
||||||
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Pre-cache all texts
|
// Pre-cache all texts
|
||||||
cached.Embed(ctx, "A")
|
cached.Embed(ctx, "A")
|
||||||
|
|
@ -148,6 +152,7 @@ func TestCachedEmbedder_BatchAllCached(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedEmbedder_Dimensions(t *testing.T) {
|
func TestCachedEmbedder_Dimensions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
inner := &countingEmbedder{dims: 768}
|
inner := &countingEmbedder{dims: 768}
|
||||||
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
||||||
if cached.Dimensions() != 768 {
|
if cached.Dimensions() != 768 {
|
||||||
|
|
@ -156,6 +161,7 @@ func TestCachedEmbedder_Dimensions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedEmbedder_Model(t *testing.T) {
|
func TestCachedEmbedder_Model(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
inner := &countingEmbedder{dims: 4}
|
inner := &countingEmbedder{dims: 4}
|
||||||
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
cached := NewCachedEmbedder(inner, DefaultCachedEmbedderConfig())
|
||||||
if cached.Model() != "test-model" {
|
if cached.Model() != "test-model" {
|
||||||
|
|
@ -164,12 +170,13 @@ func TestCachedEmbedder_Model(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedEmbedder_MaxEntries(t *testing.T) {
|
func TestCachedEmbedder_MaxEntries(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
inner := &countingEmbedder{dims: 4}
|
inner := &countingEmbedder{dims: 4}
|
||||||
cached := NewCachedEmbedder(inner, CachedEmbedderConfig{
|
cached := NewCachedEmbedder(inner, CachedEmbedderConfig{
|
||||||
MaxEntries: 3,
|
MaxEntries: 3,
|
||||||
TTL: time.Hour,
|
TTL: time.Hour,
|
||||||
})
|
})
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Fill cache
|
// Fill cache
|
||||||
cached.Embed(ctx, "A")
|
cached.Embed(ctx, "A")
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarkdownChunker_BasicSplit(t *testing.T) {
|
func TestMarkdownChunker_BasicSplit(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
chunker := NewMarkdownChunker(MarkdownChunkerConfig{
|
chunker := NewMarkdownChunker(MarkdownChunkerConfig{
|
||||||
ChunkSize: 100,
|
ChunkSize: 100,
|
||||||
ChunkOverlap: 20,
|
ChunkOverlap: 20,
|
||||||
|
|
@ -26,6 +27,7 @@ func TestMarkdownChunker_BasicSplit(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkdownChunker_SmallContent(t *testing.T) {
|
func TestMarkdownChunker_SmallContent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
chunker := NewMarkdownChunker(DefaultMarkdownChunkerConfig())
|
chunker := NewMarkdownChunker(DefaultMarkdownChunkerConfig())
|
||||||
|
|
||||||
chunks, err := chunker.Chunk("Short text.")
|
chunks, err := chunker.Chunk("Short text.")
|
||||||
|
|
@ -35,6 +37,7 @@ func TestMarkdownChunker_SmallContent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkdownChunker_PreservesMarkdownStructure(t *testing.T) {
|
func TestMarkdownChunker_PreservesMarkdownStructure(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
chunker := NewMarkdownChunker(MarkdownChunkerConfig{
|
chunker := NewMarkdownChunker(MarkdownChunkerConfig{
|
||||||
ChunkSize: 200,
|
ChunkSize: 200,
|
||||||
ChunkOverlap: 40,
|
ChunkOverlap: 40,
|
||||||
|
|
@ -81,6 +84,7 @@ Even more content follows here with additional details and explanations that mak
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkdownChunker_EmptyContent(t *testing.T) {
|
func TestMarkdownChunker_EmptyContent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
chunker := NewMarkdownChunker(DefaultMarkdownChunkerConfig())
|
chunker := NewMarkdownChunker(DefaultMarkdownChunkerConfig())
|
||||||
chunks, err := chunker.Chunk("")
|
chunks, err := chunker.Chunk("")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -88,6 +92,7 @@ func TestMarkdownChunker_EmptyContent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkdownChunker_DefaultConfig(t *testing.T) {
|
func TestMarkdownChunker_DefaultConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultMarkdownChunkerConfig()
|
cfg := DefaultMarkdownChunkerConfig()
|
||||||
assert.Equal(t, 1600, cfg.ChunkSize)
|
assert.Equal(t, 1600, cfg.ChunkSize)
|
||||||
assert.Equal(t, 320, cfg.ChunkOverlap)
|
assert.Equal(t, 320, cfg.ChunkOverlap)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/config"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewEmbedderFromConfig_EmptyProvider(t *testing.T) {
|
func TestNewEmbedderFromConfig_EmptyProvider(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
emb, err := NewEmbedderFromConfig(config.EmbeddingConfig{}, config.ProvidersConfig{})
|
emb, err := NewEmbedderFromConfig(config.EmbeddingConfig{}, config.ProvidersConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
|
@ -20,6 +22,7 @@ func TestNewEmbedderFromConfig_EmptyProvider(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewEmbedderFromConfig_UnknownProvider(t *testing.T) {
|
func TestNewEmbedderFromConfig_UnknownProvider(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := NewEmbedderFromConfig(config.EmbeddingConfig{Provider: "nonexistent"}, config.ProvidersConfig{})
|
_, err := NewEmbedderFromConfig(config.EmbeddingConfig{Provider: "nonexistent"}, config.ProvidersConfig{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for unknown provider")
|
t.Error("expected error for unknown provider")
|
||||||
|
|
@ -27,6 +30,7 @@ func TestNewEmbedderFromConfig_UnknownProvider(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewEmbedderFromConfig_OpenAI_NoKey(t *testing.T) {
|
func TestNewEmbedderFromConfig_OpenAI_NoKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := NewEmbedderFromConfig(
|
_, err := NewEmbedderFromConfig(
|
||||||
config.EmbeddingConfig{Provider: "openai"},
|
config.EmbeddingConfig{Provider: "openai"},
|
||||||
config.ProvidersConfig{},
|
config.ProvidersConfig{},
|
||||||
|
|
@ -37,6 +41,7 @@ func TestNewEmbedderFromConfig_OpenAI_NoKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewEmbedderFromConfig_OpenAI_FallbackKey(t *testing.T) {
|
func TestNewEmbedderFromConfig_OpenAI_FallbackKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"data": []map[string]interface{}{
|
"data": []map[string]interface{}{
|
||||||
|
|
@ -70,6 +75,7 @@ func TestNewEmbedderFromConfig_OpenAI_FallbackKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewEmbedderFromConfig_Ollama(t *testing.T) {
|
func TestNewEmbedderFromConfig_Ollama(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"embeddings": [][]float32{{0.1, 0.2, 0.3}},
|
"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) {
|
func TestNewEmbedderFromConfig_Ollama_FallbackBase(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"embeddings": [][]float32{{0.5}},
|
"embeddings": [][]float32{{0.5}},
|
||||||
|
|
@ -121,6 +128,7 @@ func TestNewEmbedderFromConfig_Ollama_FallbackBase(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewEmbedderFromConfig_CaseInsensitive(t *testing.T) {
|
func TestNewEmbedderFromConfig_CaseInsensitive(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"embeddings": [][]float32{{0.1}},
|
"embeddings": [][]float32{{0.1}},
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOllamaEmbedder_Embed(t *testing.T) {
|
func TestOllamaEmbedder_Embed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/api/embed" {
|
if r.URL.Path != "/api/embed" {
|
||||||
t.Errorf("expected /api/embed, got %s", r.URL.Path)
|
t.Errorf("expected /api/embed, got %s", r.URL.Path)
|
||||||
|
|
@ -35,7 +36,7 @@ func TestOllamaEmbedder_Embed(t *testing.T) {
|
||||||
Model: "test-model",
|
Model: "test-model",
|
||||||
})
|
})
|
||||||
|
|
||||||
vec, err := e.Embed(context.Background(), "hello world")
|
vec, err := e.Embed(t.Context(), "hello world")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Embed: %v", err)
|
t.Fatalf("Embed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -53,6 +54,7 @@ func TestOllamaEmbedder_Embed(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOllamaEmbedder_EmbedBatch(t *testing.T) {
|
func TestOllamaEmbedder_EmbedBatch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
callCount := 0
|
callCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
|
|
@ -65,7 +67,7 @@ func TestOllamaEmbedder_EmbedBatch(t *testing.T) {
|
||||||
|
|
||||||
e := NewOllamaEmbedder(OllamaEmbedderConfig{Base: srv.URL})
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("EmbedBatch: %v", err)
|
t.Fatalf("EmbedBatch: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +80,7 @@ func TestOllamaEmbedder_EmbedBatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOllamaEmbedder_ServerError(t *testing.T) {
|
func TestOllamaEmbedder_ServerError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte("model not found"))
|
w.Write([]byte("model not found"))
|
||||||
|
|
@ -85,13 +88,14 @@ func TestOllamaEmbedder_ServerError(t *testing.T) {
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
e := NewOllamaEmbedder(OllamaEmbedderConfig{Base: srv.URL})
|
e := NewOllamaEmbedder(OllamaEmbedderConfig{Base: srv.URL})
|
||||||
_, err := e.Embed(context.Background(), "test")
|
_, err := e.Embed(t.Context(), "test")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for server error response")
|
t.Error("expected error for server error response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOllamaEmbedder_EmptyResponse(t *testing.T) {
|
func TestOllamaEmbedder_EmptyResponse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
respData, _ := jsonv2.Marshal(ollamaEmbedResponse{Embeddings: [][]float32{}})
|
respData, _ := jsonv2.Marshal(ollamaEmbedResponse{Embeddings: [][]float32{}})
|
||||||
w.Write(respData)
|
w.Write(respData)
|
||||||
|
|
@ -99,13 +103,14 @@ func TestOllamaEmbedder_EmptyResponse(t *testing.T) {
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
e := NewOllamaEmbedder(OllamaEmbedderConfig{Base: srv.URL})
|
e := NewOllamaEmbedder(OllamaEmbedderConfig{Base: srv.URL})
|
||||||
_, err := e.Embed(context.Background(), "test")
|
_, err := e.Embed(t.Context(), "test")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for empty embeddings")
|
t.Error("expected error for empty embeddings")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOllamaEmbedder_DefaultModel(t *testing.T) {
|
func TestOllamaEmbedder_DefaultModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
e := NewOllamaEmbedder(OllamaEmbedderConfig{})
|
e := NewOllamaEmbedder(OllamaEmbedderConfig{})
|
||||||
if e.Model() != defaultOllamaModel {
|
if e.Model() != defaultOllamaModel {
|
||||||
t.Errorf("expected %q, got %q", defaultOllamaModel, e.Model())
|
t.Errorf("expected %q, got %q", defaultOllamaModel, e.Model())
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOpenAIEmbedder_Embed(t *testing.T) {
|
func TestOpenAIEmbedder_Embed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/embeddings" {
|
if r.URL.Path != "/embeddings" {
|
||||||
t.Errorf("expected /embeddings, got %s", r.URL.Path)
|
t.Errorf("expected /embeddings, got %s", r.URL.Path)
|
||||||
|
|
@ -40,7 +41,7 @@ func TestOpenAIEmbedder_Embed(t *testing.T) {
|
||||||
APIKey: "test-key",
|
APIKey: "test-key",
|
||||||
})
|
})
|
||||||
|
|
||||||
vec, err := e.Embed(context.Background(), "hello")
|
vec, err := e.Embed(t.Context(), "hello")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Embed: %v", err)
|
t.Fatalf("Embed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -56,6 +57,7 @@ func TestOpenAIEmbedder_Embed(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
|
func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req openAIEmbedRequest
|
var req openAIEmbedRequest
|
||||||
jsonv2.UnmarshalRead(r.Body, &req)
|
jsonv2.UnmarshalRead(r.Body, &req)
|
||||||
|
|
@ -84,7 +86,7 @@ func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
|
||||||
APIKey: "k",
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("EmbedBatch: %v", err)
|
t.Fatalf("EmbedBatch: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +96,7 @@ func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenAIEmbedder_SingleTextNotArray(t *testing.T) {
|
func TestOpenAIEmbedder_SingleTextNotArray(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req openAIEmbedRequest
|
var req openAIEmbedRequest
|
||||||
jsonv2.UnmarshalRead(r.Body, &req)
|
jsonv2.UnmarshalRead(r.Body, &req)
|
||||||
|
|
@ -111,13 +114,14 @@ func TestOpenAIEmbedder_SingleTextNotArray(t *testing.T) {
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{Base: srv.URL, APIKey: "k"})
|
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{Base: srv.URL, APIKey: "k"})
|
||||||
_, err := e.Embed(context.Background(), "single")
|
_, err := e.Embed(t.Context(), "single")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Embed: %v", err)
|
t.Fatalf("Embed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenAIEmbedder_ServerError(t *testing.T) {
|
func TestOpenAIEmbedder_ServerError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
w.Write([]byte(`{"error":{"message":"invalid api key"}}`))
|
w.Write([]byte(`{"error":{"message":"invalid api key"}}`))
|
||||||
|
|
@ -125,13 +129,14 @@ func TestOpenAIEmbedder_ServerError(t *testing.T) {
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{Base: srv.URL, APIKey: "bad"})
|
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{Base: srv.URL, APIKey: "bad"})
|
||||||
_, err := e.Embed(context.Background(), "test")
|
_, err := e.Embed(t.Context(), "test")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for 401 response")
|
t.Error("expected error for 401 response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenAIEmbedder_DefaultModel(t *testing.T) {
|
func TestOpenAIEmbedder_DefaultModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{APIKey: "k"})
|
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{APIKey: "k"})
|
||||||
if e.Model() != defaultOpenAIModel {
|
if e.Model() != defaultOpenAIModel {
|
||||||
t.Errorf("expected %q, got %q", defaultOpenAIModel, e.Model())
|
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) {
|
func TestOpenAIEmbedder_NoAuth(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Header.Get("Authorization") != "" {
|
if r.Header.Get("Authorization") != "" {
|
||||||
t.Error("expected no auth header when key is empty")
|
t.Error("expected no auth header when key is empty")
|
||||||
|
|
@ -151,7 +157,7 @@ func TestOpenAIEmbedder_NoAuth(t *testing.T) {
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{Base: srv.URL})
|
e := NewOpenAIEmbedder(OpenAIEmbedderConfig{Base: srv.URL})
|
||||||
_, err := e.Embed(context.Background(), "test")
|
_, err := e.Embed(t.Context(), "test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Embed: %v", err)
|
t.Fatalf("Embed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ func deterministicVec(text string, dim int) memory.Embedding {
|
||||||
|
|
||||||
func newTestStore(t *testing.T, withEmbedder bool) *MemoryStore {
|
func newTestStore(t *testing.T, withEmbedder bool) *MemoryStore {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -95,7 +95,8 @@ func newTestStore(t *testing.T, withEmbedder bool) *MemoryStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkingContext_SetAndGet(t *testing.T) {
|
func TestWorkingContext_SetAndGet(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
// Initially empty
|
// Initially empty
|
||||||
|
|
@ -122,7 +123,8 @@ func TestWorkingContext_SetAndGet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkingContext_IsolatedBySessions(t *testing.T) {
|
func TestWorkingContext_IsolatedBySessions(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
err := store.SetWorkingContext(ctx, "agent-1", "session-a", "Context A")
|
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) {
|
func TestRecall_CRUD(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
item := &memory.RecallItem{
|
item := &memory.RecallItem{
|
||||||
|
|
@ -187,7 +190,8 @@ func TestRecall_CRUD(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArchival_StoreAndRetrieve(t *testing.T) {
|
func TestArchival_StoreAndRetrieve(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, true)
|
store := newTestStore(t, true)
|
||||||
|
|
||||||
// Store a multi-chunk document
|
// Store a multi-chunk document
|
||||||
|
|
@ -209,7 +213,8 @@ func TestArchival_StoreAndRetrieve(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArchival_WithoutEmbedder(t *testing.T) {
|
func TestArchival_WithoutEmbedder(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false) // no embedder
|
store := newTestStore(t, false) // no embedder
|
||||||
|
|
||||||
content := "Short archival content for testing without embeddings."
|
content := "Short archival content for testing without embeddings."
|
||||||
|
|
@ -226,7 +231,8 @@ func TestArchival_WithoutEmbedder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearch_KeywordOnly(t *testing.T) {
|
func TestSearch_KeywordOnly(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
// Seed some recall items
|
// Seed some recall items
|
||||||
|
|
@ -251,7 +257,8 @@ func TestSearch_KeywordOnly(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearch_HybridWithEmbeddings(t *testing.T) {
|
func TestSearch_HybridWithEmbeddings(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, true)
|
store := newTestStore(t, true)
|
||||||
|
|
||||||
// Seed recall items
|
// Seed recall items
|
||||||
|
|
@ -283,7 +290,8 @@ func TestSearch_HybridWithEmbeddings(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearch_ShadowModeUsesBaselineAndTracksParity(t *testing.T) {
|
func TestSearch_ShadowModeUsesBaselineAndTracksParity(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
// Baseline recall hit.
|
// Baseline recall hit.
|
||||||
|
|
@ -319,7 +327,8 @@ func TestSearch_ShadowModeUsesBaselineAndTracksParity(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearch_DoesNotPromoteWithoutAugmentedSignals(t *testing.T) {
|
func TestSearch_DoesNotPromoteWithoutAugmentedSignals(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
gates := retrievalPromotionGates{
|
gates := retrievalPromotionGates{
|
||||||
|
|
@ -361,7 +370,8 @@ func TestSearch_DoesNotPromoteWithoutAugmentedSignals(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearch_PromoteOnlyOnGateWin(t *testing.T) {
|
func TestSearch_PromoteOnlyOnGateWin(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
gates := retrievalPromotionGates{
|
gates := retrievalPromotionGates{
|
||||||
|
|
@ -406,7 +416,8 @@ func TestSearch_PromoteOnlyOnGateWin(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearch_FastRollbackPreservesBaselinePath(t *testing.T) {
|
func TestSearch_FastRollbackPreservesBaselinePath(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
del, ok := store.delegate.(*delegate.LibSQLDelegate)
|
del, ok := store.delegate.(*delegate.LibSQLDelegate)
|
||||||
|
|
@ -492,7 +503,8 @@ func TestSearch_FastRollbackPreservesBaselinePath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateRetrievalPolicy_PersistFailuresDoNotBlockTransitions(t *testing.T) {
|
func TestUpdateRetrievalPolicy_PersistFailuresDoNotBlockTransitions(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, true)
|
store := newTestStore(t, true)
|
||||||
|
|
||||||
baseDelegate := store.delegate
|
baseDelegate := store.delegate
|
||||||
|
|
@ -530,7 +542,8 @@ func TestUpdateRetrievalPolicy_PersistFailuresDoNotBlockTransitions(t *testing.T
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearch_ConcurrentRetrievalPolicyUpdates(t *testing.T) {
|
func TestSearch_ConcurrentRetrievalPolicyUpdates(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, true)
|
store := newTestStore(t, true)
|
||||||
|
|
||||||
require.NoError(t, store.StoreRecall(ctx, &memory.RecallItem{
|
require.NoError(t, store.StoreRecall(ctx, &memory.RecallItem{
|
||||||
|
|
@ -576,7 +589,8 @@ func TestSearch_ConcurrentRetrievalPolicyUpdates(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextUsage(t *testing.T) {
|
func TestContextUsage(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
// Empty system — should be normal pressure
|
// Empty system — should be normal pressure
|
||||||
|
|
@ -597,7 +611,8 @@ func TestContextUsage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextUsage_RecallTokenEstimateUsesContent(t *testing.T) {
|
func TestContextUsage_RecallTokenEstimateUsesContent(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
contentA := strings.Repeat("schedule follow-up reminder ", 80)
|
contentA := strings.Repeat("schedule follow-up reminder ", 80)
|
||||||
|
|
@ -631,7 +646,8 @@ func TestContextUsage_RecallTokenEstimateUsesContent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextUsage_PressureLevels(t *testing.T) {
|
func TestContextUsage_PressureLevels(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -656,6 +672,7 @@ func TestContextUsage_PressureLevels(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldOffload(t *testing.T) {
|
func TestShouldOffload(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
store := &MemoryStore{cfg: Config{OffloadThresholdTokens: 100}}
|
store := &MemoryStore{cfg: Config{OffloadThresholdTokens: 100}}
|
||||||
|
|
||||||
assert.False(t, store.ShouldOffload("short"))
|
assert.False(t, store.ShouldOffload("short"))
|
||||||
|
|
@ -663,7 +680,8 @@ func TestShouldOffload(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOffloadToolResult(t *testing.T) {
|
func TestOffloadToolResult(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
largeContent := strings.Repeat("This is a large tool result that should be offloaded. ", 20)
|
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) {
|
func TestStoreSummary(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, false)
|
store := newTestStore(t, false)
|
||||||
|
|
||||||
summary := &memory.MemorySummary{
|
summary := &memory.MemorySummary{
|
||||||
|
|
@ -699,7 +718,8 @@ func TestStoreSummary(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteRecall_CascadesArchival(t *testing.T) {
|
func TestDeleteRecall_CascadesArchival(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
store := newTestStore(t, true)
|
store := newTestStore(t, true)
|
||||||
|
|
||||||
// Store archival content (creates recall item + archival chunks)
|
// Store archival content (creates recall item + archival chunks)
|
||||||
|
|
@ -726,6 +746,7 @@ func TestDeleteRecall_CascadesArchival(t *testing.T) {
|
||||||
// --- Retrieval pipeline unit tests ---
|
// --- Retrieval pipeline unit tests ---
|
||||||
|
|
||||||
func TestCosineSimilarity(t *testing.T) {
|
func TestCosineSimilarity(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
a, b memory.Embedding
|
a, b memory.Embedding
|
||||||
|
|
@ -747,6 +768,7 @@ func TestCosineSimilarity(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRRF_MergesTwoSets(t *testing.T) {
|
func TestRRF_MergesTwoSets(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
idA, idB, idC := ids.New(), ids.New(), ids.New()
|
idA, idB, idC := ids.New(), ids.New(), ids.New()
|
||||||
set1 := []memory.SearchResult{
|
set1 := []memory.SearchResult{
|
||||||
{ID: idA, Content: "a", Score: 1.0},
|
{ID: idA, Content: "a", Score: 1.0},
|
||||||
|
|
@ -764,7 +786,10 @@ func TestRRF_MergesTwoSets(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRecencyDecay(t *testing.T) {
|
func TestRecencyDecay(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// 0 hours age → decay = 1.0
|
// 0 hours age → decay = 1.0
|
||||||
|
)
|
||||||
|
|
||||||
assert.InDelta(t, 1.0, RecencyDecay(0, 168), 0.001)
|
assert.InDelta(t, 1.0, RecencyDecay(0, 168), 0.001)
|
||||||
|
|
||||||
// 168 hours (1 half-life) → decay = 0.5
|
// 168 hours (1 half-life) → decay = 0.5
|
||||||
|
|
@ -775,6 +800,7 @@ func TestRecencyDecay(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyRecencyDecay_ReordersByAge(t *testing.T) {
|
func TestApplyRecencyDecay_ReordersByAge(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
idOld, idNew := ids.New(), ids.New()
|
idOld, idNew := ids.New(), ids.New()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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 {
|
func executeAndParse(t *testing.T, tool *MemoryTool, input string) *MemoryToolResponse {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
raw, err := tool.Execute(ctx, input)
|
raw, err := tool.Execute(ctx, input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
@ -27,6 +27,7 @@ func executeAndParse(t *testing.T, tool *MemoryTool, input string) *MemoryToolRe
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryTool_WriteAndRead(t *testing.T) {
|
func TestMemoryTool_WriteAndRead(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := newTestMemoryTool(t)
|
tool := newTestMemoryTool(t)
|
||||||
|
|
||||||
// Write
|
// Write
|
||||||
|
|
@ -52,6 +53,7 @@ func TestMemoryTool_WriteAndRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryTool_WriteArchival(t *testing.T) {
|
func TestMemoryTool_WriteArchival(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := newTestMemoryTool(t)
|
tool := newTestMemoryTool(t)
|
||||||
|
|
||||||
resp := executeAndParse(t, tool, `{"action":"write","content":"Large document content for archival.","tier":"archival","source":"test"}`)
|
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) {
|
func TestMemoryTool_Search(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := newTestMemoryTool(t)
|
tool := newTestMemoryTool(t)
|
||||||
|
|
||||||
// Seed data
|
// Seed data
|
||||||
|
|
@ -74,6 +77,7 @@ func TestMemoryTool_Search(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryTool_Update(t *testing.T) {
|
func TestMemoryTool_Update(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := newTestMemoryTool(t)
|
tool := newTestMemoryTool(t)
|
||||||
|
|
||||||
// Write
|
// Write
|
||||||
|
|
@ -91,6 +95,7 @@ func TestMemoryTool_Update(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryTool_Delete(t *testing.T) {
|
func TestMemoryTool_Delete(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := newTestMemoryTool(t)
|
tool := newTestMemoryTool(t)
|
||||||
|
|
||||||
// Write
|
// Write
|
||||||
|
|
@ -108,6 +113,7 @@ func TestMemoryTool_Delete(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryTool_Status(t *testing.T) {
|
func TestMemoryTool_Status(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := newTestMemoryTool(t)
|
tool := newTestMemoryTool(t)
|
||||||
|
|
||||||
resp := executeAndParse(t, tool, `{"action":"status"}`)
|
resp := executeAndParse(t, tool, `{"action":"status"}`)
|
||||||
|
|
@ -118,6 +124,7 @@ func TestMemoryTool_Status(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryTool_InvalidAction(t *testing.T) {
|
func TestMemoryTool_InvalidAction(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := newTestMemoryTool(t)
|
tool := newTestMemoryTool(t)
|
||||||
|
|
||||||
resp := executeAndParse(t, tool, `{"action":"explode"}`)
|
resp := executeAndParse(t, tool, `{"action":"explode"}`)
|
||||||
|
|
@ -126,6 +133,7 @@ func TestMemoryTool_InvalidAction(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryTool_InvalidJSON(t *testing.T) {
|
func TestMemoryTool_InvalidJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := newTestMemoryTool(t)
|
tool := newTestMemoryTool(t)
|
||||||
|
|
||||||
resp := executeAndParse(t, tool, `not json`)
|
resp := executeAndParse(t, tool, `not json`)
|
||||||
|
|
@ -134,6 +142,7 @@ func TestMemoryTool_InvalidJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryTool_MissingRequiredFields(t *testing.T) {
|
func TestMemoryTool_MissingRequiredFields(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := newTestMemoryTool(t)
|
tool := newTestMemoryTool(t)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -13,7 +12,7 @@ import (
|
||||||
|
|
||||||
func newTestQueueManager(t *testing.T, contextWindow int) (*QueueManager, *MemoryStore) {
|
func newTestQueueManager(t *testing.T, contextWindow int) (*QueueManager, *MemoryStore) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -32,7 +31,8 @@ func newTestQueueManager(t *testing.T, contextWindow int) (*QueueManager, *Memor
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueueManager_NormalPressure(t *testing.T) {
|
func TestQueueManager_NormalPressure(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
qm, _ := newTestQueueManager(t, 100000)
|
qm, _ := newTestQueueManager(t, 100000)
|
||||||
|
|
||||||
decision, err := qm.Evaluate(ctx, "agent-1", "session-1")
|
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) {
|
func TestQueueManager_WarnPressure(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
qm, store := newTestQueueManager(t, 100) // tiny window
|
qm, store := newTestQueueManager(t, 100) // tiny window
|
||||||
|
|
||||||
// Fill working context to ~75% of context 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) {
|
func TestQueueManager_OffloadPressure(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
qm, store := newTestQueueManager(t, 100) // tiny window
|
qm, store := newTestQueueManager(t, 100) // tiny window
|
||||||
|
|
||||||
// Fill to ~82% of context window.
|
// Fill to ~82% of context window.
|
||||||
|
|
@ -70,7 +72,8 @@ func TestQueueManager_OffloadPressure(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueueManager_FlushPressure(t *testing.T) {
|
func TestQueueManager_FlushPressure(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
qm, store := newTestQueueManager(t, 100) // tiny window
|
qm, store := newTestQueueManager(t, 100) // tiny window
|
||||||
|
|
||||||
// Fill to ~88% of context window.
|
// Fill to ~88% of context window.
|
||||||
|
|
@ -84,7 +87,8 @@ func TestQueueManager_FlushPressure(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueueManager_EvictOldest(t *testing.T) {
|
func TestQueueManager_EvictOldest(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
qm, store := newTestQueueManager(t, 100)
|
qm, store := newTestQueueManager(t, 100)
|
||||||
|
|
||||||
// Seed recall items
|
// Seed recall items
|
||||||
|
|
@ -108,7 +112,8 @@ func TestQueueManager_EvictOldest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueueManager_EvictEmpty(t *testing.T) {
|
func TestQueueManager_EvictEmpty(t *testing.T) {
|
||||||
ctx := context.Background()
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
qm, _ := newTestQueueManager(t, 100)
|
qm, _ := newTestQueueManager(t, 100)
|
||||||
|
|
||||||
evicted, summary, err := qm.EvictOldest(ctx, "agent-1", "empty-session")
|
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) {
|
func TestDefaultQueueManagerConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := DefaultQueueManagerConfig()
|
cfg := DefaultQueueManagerConfig()
|
||||||
assert.InDelta(t, 0.70, cfg.WarnThreshold, 0.001)
|
assert.InDelta(t, 0.70, cfg.WarnThreshold, 0.001)
|
||||||
assert.InDelta(t, 0.80, cfg.OffloadThreshold, 0.001)
|
assert.InDelta(t, 0.80, cfg.OffloadThreshold, 0.001)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
|
||||||
|
|
@ -10,8 +9,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHeuristicScorer_BasicScoring(t *testing.T) {
|
func TestHeuristicScorer_BasicScoring(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
scorer := NewHeuristicScorer()
|
scorer := NewHeuristicScorer()
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -38,8 +38,9 @@ func TestHeuristicScorer_BasicScoring(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHeuristicScorer_SectorClassification(t *testing.T) {
|
func TestHeuristicScorer_SectorClassification(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
scorer := NewHeuristicScorer()
|
scorer := NewHeuristicScorer()
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -78,6 +79,7 @@ func TestHeuristicScorer_SectorClassification(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseScoringResponse_ValidJSON(t *testing.T) {
|
func TestParseScoringResponse_ValidJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := `{"importance": 0.85, "salience": 0.6, "sector": "semantic"}`
|
input := `{"importance": 0.85, "salience": 0.6, "sector": "semantic"}`
|
||||||
result, err := parseScoringResponse(input)
|
result, err := parseScoringResponse(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -87,6 +89,7 @@ func TestParseScoringResponse_ValidJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseScoringResponse_WithCodeFences(t *testing.T) {
|
func TestParseScoringResponse_WithCodeFences(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := "```json\n{\"importance\": 0.9, \"salience\": 0.3, \"sector\": \"procedural\"}\n```"
|
input := "```json\n{\"importance\": 0.9, \"salience\": 0.3, \"sector\": \"procedural\"}\n```"
|
||||||
result, err := parseScoringResponse(input)
|
result, err := parseScoringResponse(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -95,6 +98,7 @@ func TestParseScoringResponse_WithCodeFences(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseScoringResponse_ClampsValues(t *testing.T) {
|
func TestParseScoringResponse_ClampsValues(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := `{"importance": 1.5, "salience": -0.3, "sector": "episodic"}`
|
input := `{"importance": 1.5, "salience": -0.3, "sector": "episodic"}`
|
||||||
result, err := parseScoringResponse(input)
|
result, err := parseScoringResponse(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -103,6 +107,7 @@ func TestParseScoringResponse_ClampsValues(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseScoringResponse_UnknownSector(t *testing.T) {
|
func TestParseScoringResponse_UnknownSector(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := `{"importance": 0.5, "salience": 0.5, "sector": "unknown_sector"}`
|
input := `{"importance": 0.5, "salience": 0.5, "sector": "unknown_sector"}`
|
||||||
result, err := parseScoringResponse(input)
|
result, err := parseScoringResponse(input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -110,6 +115,7 @@ func TestParseScoringResponse_UnknownSector(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNormalizeSector(t *testing.T) {
|
func TestNormalizeSector(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
expected memory.Sector
|
expected memory.Sector
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCamelToSnake(t *testing.T) {
|
func TestCamelToSnake(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -41,6 +42,7 @@ func TestCamelToSnake(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConvertKeysToSnake(t *testing.T) {
|
func TestConvertKeysToSnake(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := map[string]interface{}{
|
input := map[string]interface{}{
|
||||||
"apiKey": "test-key",
|
"apiKey": "test-key",
|
||||||
"apiBase": "https://example.com",
|
"apiBase": "https://example.com",
|
||||||
|
|
@ -87,6 +89,7 @@ func TestConvertKeysToSnake(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadOpenClawConfig(t *testing.T) {
|
func TestLoadOpenClawConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configPath := filepath.Join(tmpDir, "openclaw.json")
|
configPath := filepath.Join(tmpDir, "openclaw.json")
|
||||||
|
|
||||||
|
|
@ -144,6 +147,7 @@ func TestLoadOpenClawConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConvertConfig(t *testing.T) {
|
func TestConvertConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("providers mapping", func(t *testing.T) {
|
t.Run("providers mapping", func(t *testing.T) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"providers": map[string]interface{}{
|
"providers": map[string]interface{}{
|
||||||
|
|
@ -301,6 +305,7 @@ func TestConvertConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeConfig(t *testing.T) {
|
func TestMergeConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("fills empty fields", func(t *testing.T) {
|
t.Run("fills empty fields", func(t *testing.T) {
|
||||||
existing := config.DefaultConfig()
|
existing := config.DefaultConfig()
|
||||||
incoming := config.DefaultConfig()
|
incoming := config.DefaultConfig()
|
||||||
|
|
@ -365,6 +370,7 @@ func TestMergeConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlanWorkspaceMigration(t *testing.T) {
|
func TestPlanWorkspaceMigration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("copies available files", func(t *testing.T) {
|
t.Run("copies available files", func(t *testing.T) {
|
||||||
srcDir := t.TempDir()
|
srcDir := t.TempDir()
|
||||||
dstDir := t.TempDir()
|
dstDir := t.TempDir()
|
||||||
|
|
@ -495,6 +501,7 @@ func TestPlanWorkspaceMigration(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindOpenClawConfig(t *testing.T) {
|
func TestFindOpenClawConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("finds openclaw.json", func(t *testing.T) {
|
t.Run("finds openclaw.json", func(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configPath := filepath.Join(tmpDir, "openclaw.json")
|
configPath := filepath.Join(tmpDir, "openclaw.json")
|
||||||
|
|
@ -549,6 +556,7 @@ func TestFindOpenClawConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteWorkspacePath(t *testing.T) {
|
func TestRewriteWorkspacePath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -569,6 +577,7 @@ func TestRewriteWorkspacePath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunDryRun(t *testing.T) {
|
func TestRunDryRun(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
openclawHome := t.TempDir()
|
openclawHome := t.TempDir()
|
||||||
picoClawHome := t.TempDir()
|
picoClawHome := t.TempDir()
|
||||||
|
|
||||||
|
|
@ -610,6 +619,7 @@ func TestRunDryRun(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunFullMigration(t *testing.T) {
|
func TestRunFullMigration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
openclawHome := t.TempDir()
|
openclawHome := t.TempDir()
|
||||||
picoClawHome := t.TempDir()
|
picoClawHome := t.TempDir()
|
||||||
|
|
||||||
|
|
@ -708,6 +718,7 @@ func TestRunFullMigration(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunOpenClawNotFound(t *testing.T) {
|
func TestRunOpenClawNotFound(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
opts := Options{
|
opts := Options{
|
||||||
OpenClawHome: "/nonexistent/path/to/openclaw",
|
OpenClawHome: "/nonexistent/path/to/openclaw",
|
||||||
PicoClawHome: t.TempDir(),
|
PicoClawHome: t.TempDir(),
|
||||||
|
|
@ -720,6 +731,7 @@ func TestRunOpenClawNotFound(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunMutuallyExclusiveFlags(t *testing.T) {
|
func TestRunMutuallyExclusiveFlags(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
opts := Options{
|
opts := Options{
|
||||||
ConfigOnly: true,
|
ConfigOnly: true,
|
||||||
WorkspaceOnly: true,
|
WorkspaceOnly: true,
|
||||||
|
|
@ -732,6 +744,7 @@ func TestRunMutuallyExclusiveFlags(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackupFile(t *testing.T) {
|
func TestBackupFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
filePath := filepath.Join(tmpDir, "test.md")
|
filePath := filepath.Join(tmpDir, "test.md")
|
||||||
os.WriteFile(filePath, []byte("original content"), 0644)
|
os.WriteFile(filePath, []byte("original content"), 0644)
|
||||||
|
|
@ -751,6 +764,7 @@ func TestBackupFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCopyFile(t *testing.T) {
|
func TestCopyFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
srcPath := filepath.Join(tmpDir, "src.md")
|
srcPath := filepath.Join(tmpDir, "src.md")
|
||||||
dstPath := filepath.Join(tmpDir, "dst.md")
|
dstPath := filepath.Join(tmpDir, "dst.md")
|
||||||
|
|
@ -771,6 +785,7 @@ func TestCopyFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunConfigOnly(t *testing.T) {
|
func TestRunConfigOnly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
openclawHome := t.TempDir()
|
openclawHome := t.TempDir()
|
||||||
picoClawHome := t.TempDir()
|
picoClawHome := t.TempDir()
|
||||||
|
|
||||||
|
|
@ -811,6 +826,7 @@ func TestRunConfigOnly(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunWorkspaceOnly(t *testing.T) {
|
func TestRunWorkspaceOnly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
openclawHome := t.TempDir()
|
openclawHome := t.TempDir()
|
||||||
picoClawHome := t.TempDir()
|
picoClawHome := t.TempDir()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,18 +27,22 @@ func errorModel(_ context.Context, _, _ string) (string, uint32, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEngine_SmallContext_AnswerDirectly(t *testing.T) {
|
func TestEngine_SmallContext_AnswerDirectly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := rlm.DefaultEngineConfig()
|
cfg := rlm.DefaultEngineConfig()
|
||||||
cfg.Strategy.DirectThreshold = 10000 // larger than our test context
|
cfg.Strategy.DirectThreshold = 10000 // larger than our test context
|
||||||
engine := rlm.NewEngine(cfg, nil, echoModel)
|
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)
|
require.NoError(t, err)
|
||||||
assert.NotEmpty(t, answer)
|
assert.NotEmpty(t, answer)
|
||||||
assert.Greater(t, tokens, uint32(0))
|
assert.Greater(t, tokens, uint32(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEngine_LargeContext_Partitions(t *testing.T) {
|
func TestEngine_LargeContext_Partitions(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Force partitioning by setting DirectThreshold very low.
|
// Force partitioning by setting DirectThreshold very low.
|
||||||
|
)
|
||||||
|
|
||||||
cfg := rlm.DefaultEngineConfig()
|
cfg := rlm.DefaultEngineConfig()
|
||||||
cfg.Strategy.DirectThreshold = 10
|
cfg.Strategy.DirectThreshold = 10
|
||||||
cfg.Strategy.DefaultPartitionK = 2
|
cfg.Strategy.DefaultPartitionK = 2
|
||||||
|
|
@ -48,13 +52,14 @@ func TestEngine_LargeContext_Partitions(t *testing.T) {
|
||||||
largeCtx := strings.Repeat("hello world ", 100) // ~1200 bytes
|
largeCtx := strings.Repeat("hello world ", 100) // ~1200 bytes
|
||||||
engine := rlm.NewEngine(cfg, nil, echoModel)
|
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)
|
require.NoError(t, err)
|
||||||
assert.NotEmpty(t, answer)
|
assert.NotEmpty(t, answer)
|
||||||
assert.Greater(t, tokens, uint32(0))
|
assert.Greater(t, tokens, uint32(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEngine_MaxDepthTerminates(t *testing.T) {
|
func TestEngine_MaxDepthTerminates(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := rlm.DefaultEngineConfig()
|
cfg := rlm.DefaultEngineConfig()
|
||||||
cfg.Strategy.DirectThreshold = 0 // always partition
|
cfg.Strategy.DirectThreshold = 0 // always partition
|
||||||
cfg.Strategy.MaxDepth = 3
|
cfg.Strategy.MaxDepth = 3
|
||||||
|
|
@ -64,20 +69,22 @@ func TestEngine_MaxDepthTerminates(t *testing.T) {
|
||||||
engine := rlm.NewEngine(cfg, nil, echoModel)
|
engine := rlm.NewEngine(cfg, nil, echoModel)
|
||||||
|
|
||||||
// Should terminate without stack overflow.
|
// 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)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEngine_ModelError_Propagates(t *testing.T) {
|
func TestEngine_ModelError_Propagates(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := rlm.DefaultEngineConfig()
|
cfg := rlm.DefaultEngineConfig()
|
||||||
cfg.Strategy.DirectThreshold = 10000
|
cfg.Strategy.DirectThreshold = 10000
|
||||||
engine := rlm.NewEngine(cfg, nil, errorModel)
|
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)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEngine_GrepQuery_NarrowsContext(t *testing.T) {
|
func TestEngine_GrepQuery_NarrowsContext(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := rlm.DefaultEngineConfig()
|
cfg := rlm.DefaultEngineConfig()
|
||||||
cfg.Strategy.DirectThreshold = 10
|
cfg.Strategy.DirectThreshold = 10
|
||||||
cfg.Strategy.DefaultPartitionK = 2
|
cfg.Strategy.DefaultPartitionK = 2
|
||||||
|
|
@ -88,14 +95,15 @@ func TestEngine_GrepQuery_NarrowsContext(t *testing.T) {
|
||||||
content := "foo\nfunc myFunction() {}\nbar\nbaz"
|
content := "foo\nfunc myFunction() {}\nbar\nbaz"
|
||||||
engine := rlm.NewEngine(cfg, nil, echoModel)
|
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)
|
require.NoError(t, err)
|
||||||
assert.NotEmpty(t, answer)
|
assert.NotEmpty(t, answer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFanOut_AllPartitions_Processed(t *testing.T) {
|
func TestFanOut_AllPartitions_Processed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
partitions := []string{"A", "B", "C", "D"}
|
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 {
|
func(_ context.Context, idx int, key, part string) rlm.PartitionResult {
|
||||||
return rlm.PartitionResult{PartitionIdx: idx, ContextKey: key, Answer: "ans-" + part, Tokens: 1}
|
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) {
|
func TestFanOut_UnboundedConcurrency(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
parts := make([]string, 20)
|
parts := make([]string, 20)
|
||||||
for i := range parts {
|
for i := range parts {
|
||||||
parts[i] = fmt.Sprintf("part-%d", i)
|
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 {
|
func(_ context.Context, idx int, key, part string) rlm.PartitionResult {
|
||||||
return rlm.PartitionResult{PartitionIdx: idx, Answer: part, Tokens: 2}
|
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) {
|
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 {
|
func(_ context.Context, _ int, _, _ string) rlm.PartitionResult {
|
||||||
return rlm.PartitionResult{}
|
return rlm.PartitionResult{}
|
||||||
})
|
})
|
||||||
|
|
@ -129,6 +139,7 @@ func TestFanOut_Empty(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeResults_Deduplication(t *testing.T) {
|
func TestMergeResults_Deduplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
results := []rlm.PartitionResult{
|
results := []rlm.PartitionResult{
|
||||||
{Answer: "alpha"},
|
{Answer: "alpha"},
|
||||||
{Answer: "beta"},
|
{Answer: "beta"},
|
||||||
|
|
@ -140,6 +151,7 @@ func TestMergeResults_Deduplication(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeResults_WithErrors(t *testing.T) {
|
func TestMergeResults_WithErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
results := []rlm.PartitionResult{
|
results := []rlm.PartitionResult{
|
||||||
{Answer: "good"},
|
{Answer: "good"},
|
||||||
{Err: fmt.Errorf("failed"), Answer: "should be skipped"},
|
{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) {
|
func TestStrategyPlanner_SmallContext_OpFinal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := rlm.DefaultStrategyConfig()
|
cfg := rlm.DefaultStrategyConfig()
|
||||||
cfg.DirectThreshold = 1000
|
cfg.DirectThreshold = 1000
|
||||||
planner := rlm.NewStrategyPlanner(cfg)
|
planner := rlm.NewStrategyPlanner(cfg)
|
||||||
|
|
@ -158,6 +171,7 @@ func TestStrategyPlanner_SmallContext_OpFinal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrategyPlanner_MaxDepth_OpFinal(t *testing.T) {
|
func TestStrategyPlanner_MaxDepth_OpFinal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cfg := rlm.DefaultStrategyConfig()
|
cfg := rlm.DefaultStrategyConfig()
|
||||||
cfg.MaxDepth = 3
|
cfg.MaxDepth = 3
|
||||||
planner := rlm.NewStrategyPlanner(cfg)
|
planner := rlm.NewStrategyPlanner(cfg)
|
||||||
|
|
@ -167,6 +181,7 @@ func TestStrategyPlanner_MaxDepth_OpFinal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrategyPlanner_KeywordQuery_OpGrep(t *testing.T) {
|
func TestStrategyPlanner_KeywordQuery_OpGrep(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
planner := rlm.NewStrategyPlanner(rlm.DefaultStrategyConfig())
|
planner := rlm.NewStrategyPlanner(rlm.DefaultStrategyConfig())
|
||||||
|
|
||||||
// "find" prefix should trigger grep.
|
// "find" prefix should trigger grep.
|
||||||
|
|
@ -176,6 +191,7 @@ func TestStrategyPlanner_KeywordQuery_OpGrep(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrategyPlanner_LargeContext_OpPartition(t *testing.T) {
|
func TestStrategyPlanner_LargeContext_OpPartition(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
planner := rlm.NewStrategyPlanner(rlm.DefaultStrategyConfig())
|
planner := rlm.NewStrategyPlanner(rlm.DefaultStrategyConfig())
|
||||||
|
|
||||||
op := planner.PlanNext(100000, "summarise everything", 0)
|
op := planner.PlanNext(100000, "summarise everything", 0)
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFanOutEmpty(t *testing.T) {
|
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)
|
assert.Nil(t, results)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFanOutUnbounded(t *testing.T) {
|
func TestFanOutUnbounded(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
partitions := []string{"part-0", "part-1", "part-2"}
|
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{
|
return PartitionResult{
|
||||||
PartitionIdx: idx,
|
PartitionIdx: idx,
|
||||||
ContextKey: key,
|
ContextKey: key,
|
||||||
|
|
@ -37,6 +39,7 @@ func TestFanOutUnbounded(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFanOutBounded(t *testing.T) {
|
func TestFanOutBounded(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
partitions := make([]string, 10)
|
partitions := make([]string, 10)
|
||||||
for i := range partitions {
|
for i := range partitions {
|
||||||
partitions[i] = fmt.Sprintf("chunk-%d", i)
|
partitions[i] = fmt.Sprintf("chunk-%d", i)
|
||||||
|
|
@ -45,7 +48,7 @@ func TestFanOutBounded(t *testing.T) {
|
||||||
var maxConcurrent int64
|
var maxConcurrent int64
|
||||||
var current 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(¤t, 1)
|
c := atomic.AddInt64(¤t, 1)
|
||||||
for {
|
for {
|
||||||
old := atomic.LoadInt64(&maxConcurrent)
|
old := atomic.LoadInt64(&maxConcurrent)
|
||||||
|
|
@ -71,9 +74,10 @@ func TestFanOutBounded(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFanOutPreservesOrder(t *testing.T) {
|
func TestFanOutPreservesOrder(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
partitions := []string{"A", "B", "C", "D"}
|
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{
|
return PartitionResult{
|
||||||
PartitionIdx: idx,
|
PartitionIdx: idx,
|
||||||
Answer: partition,
|
Answer: partition,
|
||||||
|
|
@ -88,6 +92,7 @@ func TestFanOutPreservesOrder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeResultsDeduplication(t *testing.T) {
|
func TestMergeResultsDeduplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
results := []PartitionResult{
|
results := []PartitionResult{
|
||||||
{Answer: " answer one "},
|
{Answer: " answer one "},
|
||||||
{Answer: "answer one"},
|
{Answer: "answer one"},
|
||||||
|
|
@ -101,6 +106,7 @@ func TestMergeResultsDeduplication(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeResultsAllErrors(t *testing.T) {
|
func TestMergeResultsAllErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
results := []PartitionResult{
|
results := []PartitionResult{
|
||||||
{Err: fmt.Errorf("e1")},
|
{Err: fmt.Errorf("e1")},
|
||||||
{Err: fmt.Errorf("e2")},
|
{Err: fmt.Errorf("e2")},
|
||||||
|
|
@ -109,6 +115,7 @@ func TestMergeResultsAllErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeResultsAllEmpty(t *testing.T) {
|
func TestMergeResultsAllEmpty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
results := []PartitionResult{
|
results := []PartitionResult{
|
||||||
{Answer: ""},
|
{Answer: ""},
|
||||||
{Answer: " "},
|
{Answer: " "},
|
||||||
|
|
@ -117,6 +124,7 @@ func TestMergeResultsAllEmpty(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTotalTokens(t *testing.T) {
|
func TestTotalTokens(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
results := []PartitionResult{
|
results := []PartitionResult{
|
||||||
{Tokens: 100},
|
{Tokens: 100},
|
||||||
{Tokens: 250},
|
{Tokens: 250},
|
||||||
|
|
@ -126,5 +134,6 @@ func TestTotalTokens(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTotalTokensEmpty(t *testing.T) {
|
func TestTotalTokensEmpty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, uint32(0), TotalTokens(nil))
|
assert.Equal(t, uint32(0), TotalTokens(nil))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRope_EmptyRope(t *testing.T) {
|
func TestRope_EmptyRope(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("")
|
r := rlm.NewRope("")
|
||||||
assert.Equal(t, 0, r.Len())
|
assert.Equal(t, 0, r.Len())
|
||||||
assert.Equal(t, "", r.String())
|
assert.Equal(t, "", r.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_BasicAppendAndString(t *testing.T) {
|
func TestRope_BasicAppendAndString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("hello")
|
r := rlm.NewRope("hello")
|
||||||
r.Append(" world")
|
r.Append(" world")
|
||||||
assert.Equal(t, 11, r.Len())
|
assert.Equal(t, 11, r.Len())
|
||||||
|
|
@ -23,6 +25,7 @@ func TestRope_BasicAppendAndString(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_LargeContent(t *testing.T) {
|
func TestRope_LargeContent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
content := strings.Repeat("abcdefghij", 1000) // 10000 bytes
|
content := strings.Repeat("abcdefghij", 1000) // 10000 bytes
|
||||||
r := rlm.NewRope(content)
|
r := rlm.NewRope(content)
|
||||||
assert.Equal(t, 10000, r.Len())
|
assert.Equal(t, 10000, r.Len())
|
||||||
|
|
@ -30,6 +33,7 @@ func TestRope_LargeContent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_Slice_ValidRange(t *testing.T) {
|
func TestRope_Slice_ValidRange(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("hello world")
|
r := rlm.NewRope("hello world")
|
||||||
s, err := r.Slice(6, 11)
|
s, err := r.Slice(6, 11)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -37,6 +41,7 @@ func TestRope_Slice_ValidRange(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_Slice_ZeroLength(t *testing.T) {
|
func TestRope_Slice_ZeroLength(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("hello")
|
r := rlm.NewRope("hello")
|
||||||
s, err := r.Slice(2, 2)
|
s, err := r.Slice(2, 2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -44,12 +49,14 @@ func TestRope_Slice_ZeroLength(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_Slice_OutOfRange(t *testing.T) {
|
func TestRope_Slice_OutOfRange(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("hello")
|
r := rlm.NewRope("hello")
|
||||||
_, err := r.Slice(3, 10)
|
_, err := r.Slice(3, 10)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_Slice_AcrossAppendBoundary(t *testing.T) {
|
func TestRope_Slice_AcrossAppendBoundary(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("hello")
|
r := rlm.NewRope("hello")
|
||||||
r.Append(" world")
|
r.Append(" world")
|
||||||
s, err := r.Slice(3, 8)
|
s, err := r.Slice(3, 8)
|
||||||
|
|
@ -58,12 +65,14 @@ func TestRope_Slice_AcrossAppendBoundary(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_Lines(t *testing.T) {
|
func TestRope_Lines(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("line1\nline2\nline3")
|
r := rlm.NewRope("line1\nline2\nline3")
|
||||||
lines := r.Lines()
|
lines := r.Lines()
|
||||||
assert.Equal(t, []string{"line1", "line2", "line3"}, lines)
|
assert.Equal(t, []string{"line1", "line2", "line3"}, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_GrepLines_CaseSensitive(t *testing.T) {
|
func TestRope_GrepLines_CaseSensitive(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("apple\nBanana\napricot\ncherry")
|
r := rlm.NewRope("apple\nBanana\napricot\ncherry")
|
||||||
matches := r.GrepLines("ap", 0, false)
|
matches := r.GrepLines("ap", 0, false)
|
||||||
require.Len(t, matches, 2)
|
require.Len(t, matches, 2)
|
||||||
|
|
@ -72,6 +81,7 @@ func TestRope_GrepLines_CaseSensitive(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_GrepLines_CaseInsensitive(t *testing.T) {
|
func TestRope_GrepLines_CaseInsensitive(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("Apple\nbanana\nAPRICOT")
|
r := rlm.NewRope("Apple\nbanana\nAPRICOT")
|
||||||
matches := r.GrepLines("apple", 0, true)
|
matches := r.GrepLines("apple", 0, true)
|
||||||
require.Len(t, matches, 1)
|
require.Len(t, matches, 1)
|
||||||
|
|
@ -79,18 +89,21 @@ func TestRope_GrepLines_CaseInsensitive(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_GrepLines_MaxMatches(t *testing.T) {
|
func TestRope_GrepLines_MaxMatches(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("aa\naa\naa\naa\naa")
|
r := rlm.NewRope("aa\naa\naa\naa\naa")
|
||||||
matches := r.GrepLines("aa", 3, false)
|
matches := r.GrepLines("aa", 3, false)
|
||||||
assert.Len(t, matches, 3)
|
assert.Len(t, matches, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_GrepLines_NoMatches(t *testing.T) {
|
func TestRope_GrepLines_NoMatches(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("hello world")
|
r := rlm.NewRope("hello world")
|
||||||
matches := r.GrepLines("xyz", 0, false)
|
matches := r.GrepLines("xyz", 0, false)
|
||||||
assert.Empty(t, matches)
|
assert.Empty(t, matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_Partition_Even(t *testing.T) {
|
func TestRope_Partition_Even(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("12345678")
|
r := rlm.NewRope("12345678")
|
||||||
parts := r.Partition(4)
|
parts := r.Partition(4)
|
||||||
assert.Len(t, parts, 4)
|
assert.Len(t, parts, 4)
|
||||||
|
|
@ -98,6 +111,7 @@ func TestRope_Partition_Even(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_Partition_MoreThanContent(t *testing.T) {
|
func TestRope_Partition_MoreThanContent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("hi")
|
r := rlm.NewRope("hi")
|
||||||
parts := r.Partition(10)
|
parts := r.Partition(10)
|
||||||
assert.Len(t, parts, 10)
|
assert.Len(t, parts, 10)
|
||||||
|
|
@ -107,6 +121,7 @@ func TestRope_Partition_MoreThanContent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_Partition_Empty(t *testing.T) {
|
func TestRope_Partition_Empty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := rlm.NewRope("")
|
r := rlm.NewRope("")
|
||||||
parts := r.Partition(4)
|
parts := r.Partition(4)
|
||||||
assert.Len(t, parts, 4)
|
assert.Len(t, parts, 4)
|
||||||
|
|
@ -116,7 +131,10 @@ func TestRope_Partition_Empty(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRope_RuneLen(t *testing.T) {
|
func TestRope_RuneLen(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Multi-byte Unicode characters.
|
// Multi-byte Unicode characters.
|
||||||
|
)
|
||||||
|
|
||||||
r := rlm.NewRope("héllo") // 'é' is 2 bytes
|
r := rlm.NewRope("héllo") // 'é' is 2 bytes
|
||||||
assert.Equal(t, 5, r.RuneLen())
|
assert.Equal(t, 5, r.RuneLen())
|
||||||
assert.Equal(t, 6, r.Len()) // bytes
|
assert.Equal(t, 6, r.Len()) // bytes
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStrategyPlanNextFinalAtMaxDepth(t *testing.T) {
|
func TestStrategyPlanNextFinalAtMaxDepth(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sp := NewStrategyPlanner(StrategyConfig{
|
sp := NewStrategyPlanner(StrategyConfig{
|
||||||
DirectThreshold: 8192,
|
DirectThreshold: 8192,
|
||||||
DefaultPartitionK: 4,
|
DefaultPartitionK: 4,
|
||||||
|
|
@ -18,6 +19,7 @@ func TestStrategyPlanNextFinalAtMaxDepth(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrategyPlanNextFinalSmallContext(t *testing.T) {
|
func TestStrategyPlanNextFinalSmallContext(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sp := NewStrategyPlanner(DefaultStrategyConfig())
|
sp := NewStrategyPlanner(DefaultStrategyConfig())
|
||||||
|
|
||||||
op := sp.PlanNext(1000, "any query", 0)
|
op := sp.PlanNext(1000, "any query", 0)
|
||||||
|
|
@ -25,6 +27,7 @@ func TestStrategyPlanNextFinalSmallContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrategyPlanNextGrepForKeywordQuery(t *testing.T) {
|
func TestStrategyPlanNextGrepForKeywordQuery(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sp := NewStrategyPlanner(DefaultStrategyConfig())
|
sp := NewStrategyPlanner(DefaultStrategyConfig())
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
@ -47,6 +50,7 @@ func TestStrategyPlanNextGrepForKeywordQuery(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrategyPlanNextPartitionDefault(t *testing.T) {
|
func TestStrategyPlanNextPartitionDefault(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sp := NewStrategyPlanner(DefaultStrategyConfig())
|
sp := NewStrategyPlanner(DefaultStrategyConfig())
|
||||||
op := sp.PlanNext(100_000, "summarize this document", 0)
|
op := sp.PlanNext(100_000, "summarize this document", 0)
|
||||||
assert.Equal(t, OpPartition, op.Type)
|
assert.Equal(t, OpPartition, op.Type)
|
||||||
|
|
@ -54,6 +58,7 @@ func TestStrategyPlanNextPartitionDefault(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrategyPlanNextPartitionLargeContext(t *testing.T) {
|
func TestStrategyPlanNextPartitionLargeContext(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sp := NewStrategyPlanner(DefaultStrategyConfig())
|
sp := NewStrategyPlanner(DefaultStrategyConfig())
|
||||||
op := sp.PlanNext(5_000_000, "summarize this corpus", 0)
|
op := sp.PlanNext(5_000_000, "summarize this corpus", 0)
|
||||||
assert.Equal(t, OpPartition, op.Type)
|
assert.Equal(t, OpPartition, op.Type)
|
||||||
|
|
@ -61,18 +66,22 @@ func TestStrategyPlanNextPartitionLargeContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractKeywordQuoted(t *testing.T) {
|
func TestExtractKeywordQuoted(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, "handleRequest", extractKeyword(`find "handleRequest" in the codebase`))
|
assert.Equal(t, "handleRequest", extractKeyword(`find "handleRequest" in the codebase`))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractKeywordNoQuotes(t *testing.T) {
|
func TestExtractKeywordNoQuotes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, "find", extractKeyword("find the main function"))
|
assert.Equal(t, "find", extractKeyword("find the main function"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractKeywordEmpty(t *testing.T) {
|
func TestExtractKeywordEmpty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.Equal(t, "", extractKeyword(""))
|
assert.Equal(t, "", extractKeyword(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLooksLikeKeywordQuery(t *testing.T) {
|
func TestLooksLikeKeywordQuery(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert.True(t, looksLikeKeywordQuery(`find "something"`))
|
assert.True(t, looksLikeKeywordQuery(`find "something"`))
|
||||||
assert.True(t, looksLikeKeywordQuery("error: something broke"))
|
assert.True(t, looksLikeKeywordQuery("error: something broke"))
|
||||||
assert.True(t, looksLikeKeywordQuery("func processData"))
|
assert.True(t, looksLikeKeywordQuery("func processData"))
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ func TestResolveBaseConfigPath_PrefersXDGOverLegacy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveBaseConfigPath_FallsBackToLegacyWhenXDGMissing(t *testing.T) {
|
func TestResolveBaseConfigPath_FallsBackToLegacyWhenXDGMissing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
home := t.TempDir()
|
home := t.TempDir()
|
||||||
xdg := t.TempDir()
|
xdg := t.TempDir()
|
||||||
t.Setenv("HOME", home)
|
t.Setenv("HOME", home)
|
||||||
|
|
@ -58,6 +59,7 @@ func TestResolveBaseConfigPath_FallsBackToLegacyWhenXDGMissing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadResolvedConfig_AppliesOverlayAndKeepsBaseValues(t *testing.T) {
|
func TestLoadResolvedConfig_AppliesOverlayAndKeepsBaseValues(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
basePath := filepath.Join(dir, "base.json")
|
basePath := filepath.Join(dir, "base.json")
|
||||||
overlayPath := filepath.Join(dir, "overlay.json")
|
overlayPath := filepath.Join(dir, "overlay.json")
|
||||||
|
|
@ -82,6 +84,7 @@ func TestLoadResolvedConfig_AppliesOverlayAndKeepsBaseValues(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnsureMinProviderTimeout_SetsFloor(t *testing.T) {
|
func TestEnsureMinProviderTimeout_SetsFloor(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
basePath := filepath.Join(dir, "base.json")
|
basePath := filepath.Join(dir, "base.json")
|
||||||
require.NoError(t, os.WriteFile(basePath, []byte(`{"providers":{"openai":{"timeout":0}}}`), 0o644))
|
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) {
|
func TestStartOutbound_DropAndConsumeDoNotBlockPublishers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
modes := []OutboundMode{OutboundModeDrop, OutboundModeConsume}
|
modes := []OutboundMode{OutboundModeDrop, OutboundModeConsume}
|
||||||
for _, mode := range modes {
|
for _, mode := range modes {
|
||||||
t.Run(string(mode), func(t *testing.T) {
|
t.Run(string(mode), func(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
|
|
@ -127,7 +131,8 @@ func TestStartOutbound_DropAndConsumeDoNotBlockPublishers(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStartOutbound_CallbackReceivesMessages(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()
|
defer cancel()
|
||||||
|
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
|
|
@ -158,6 +163,7 @@ func TestStartOutbound_CallbackReceivesMessages(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateKernelInvariants(t *testing.T) {
|
func TestValidateKernelInvariants(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
t.Run("nil loop", func(t *testing.T) {
|
t.Run("nil loop", func(t *testing.T) {
|
||||||
err := validateKernelInvariants(nil)
|
err := validateKernelInvariants(nil)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExtractJSON_RawJSON(t *testing.T) {
|
func TestExtractJSON_RawJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -30,6 +31,7 @@ func TestExtractJSON_RawJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractJSON_CodeFence(t *testing.T) {
|
func TestExtractJSON_CodeFence(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -58,6 +60,7 @@ func TestExtractJSON_CodeFence(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractJSON_EmbeddedInProse(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.`
|
input := `Based on my analysis, the result is {"importance": 0.9, "sector": "semantic"} which indicates high relevance.`
|
||||||
var result struct {
|
var result struct {
|
||||||
Importance float64 `json:"importance"`
|
Importance float64 `json:"importance"`
|
||||||
|
|
@ -70,6 +73,7 @@ func TestExtractJSON_EmbeddedInProse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractJSON_NestedBracesInStrings(t *testing.T) {
|
func TestExtractJSON_NestedBracesInStrings(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := `{"content": "function() { return {}; }", "count": 1}`
|
input := `{"content": "function() { return {}; }", "count": 1}`
|
||||||
var result map[string]interface{}
|
var result map[string]interface{}
|
||||||
err := ExtractJSON(input, &result, nil)
|
err := ExtractJSON(input, &result, nil)
|
||||||
|
|
@ -79,6 +83,7 @@ func TestExtractJSON_NestedBracesInStrings(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractJSON_InjectionAttempts(t *testing.T) {
|
func TestExtractJSON_InjectionAttempts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -131,12 +136,14 @@ func TestExtractJSON_InjectionAttempts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractJSON_EmptyInput(t *testing.T) {
|
func TestExtractJSON_EmptyInput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var result map[string]interface{}
|
var result map[string]interface{}
|
||||||
err := ExtractJSON("", &result, nil)
|
err := ExtractJSON("", &result, nil)
|
||||||
assert.ErrorIs(t, err, ErrNoJSON)
|
assert.ErrorIs(t, err, ErrNoJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractJSON_MultipleFences_TakesFirst(t *testing.T) {
|
func TestExtractJSON_MultipleFences_TakesFirst(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := "```json\n{\"first\": true}\n```\nmore text\n```json\n{\"second\": true}\n```"
|
input := "```json\n{\"first\": true}\n```\nmore text\n```json\n{\"second\": true}\n```"
|
||||||
var result map[string]interface{}
|
var result map[string]interface{}
|
||||||
err := ExtractJSON(input, &result, nil)
|
err := ExtractJSON(input, &result, nil)
|
||||||
|
|
@ -147,6 +154,7 @@ func TestExtractJSON_MultipleFences_TakesFirst(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSanitizeToolArgs_ValidInput(t *testing.T) {
|
func TestSanitizeToolArgs_ValidInput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
schema := map[string]ArgSpec{
|
schema := map[string]ArgSpec{
|
||||||
"path": {Type: ArgString, Required: true, MaxLength: 256},
|
"path": {Type: ArgString, Required: true, MaxLength: 256},
|
||||||
"content": {Type: ArgString, Required: true},
|
"content": {Type: ArgString, Required: true},
|
||||||
|
|
@ -166,6 +174,7 @@ func TestSanitizeToolArgs_ValidInput(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSanitizeToolArgs_MissingRequired(t *testing.T) {
|
func TestSanitizeToolArgs_MissingRequired(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
schema := map[string]ArgSpec{
|
schema := map[string]ArgSpec{
|
||||||
"path": {Type: ArgString, Required: true},
|
"path": {Type: ArgString, Required: true},
|
||||||
}
|
}
|
||||||
|
|
@ -176,6 +185,7 @@ func TestSanitizeToolArgs_MissingRequired(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSanitizeToolArgs_ExceedsMaxLength(t *testing.T) {
|
func TestSanitizeToolArgs_ExceedsMaxLength(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
schema := map[string]ArgSpec{
|
schema := map[string]ArgSpec{
|
||||||
"cmd": {Type: ArgString, Required: true, MaxLength: 10},
|
"cmd": {Type: ArgString, Required: true, MaxLength: 10},
|
||||||
}
|
}
|
||||||
|
|
@ -186,6 +196,7 @@ func TestSanitizeToolArgs_ExceedsMaxLength(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSanitizeToolArgs_TypeCoercion(t *testing.T) {
|
func TestSanitizeToolArgs_TypeCoercion(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
argType ArgType
|
argType ArgType
|
||||||
|
|
@ -220,12 +231,14 @@ func TestSanitizeToolArgs_TypeCoercion(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractFirstBraced_EscapedQuotes(t *testing.T) {
|
func TestExtractFirstBraced_EscapedQuotes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := `{"msg": "say \"hello\" world"}`
|
input := `{"msg": "say \"hello\" world"}`
|
||||||
result := extractFirstBraced(input)
|
result := extractFirstBraced(input)
|
||||||
assert.Equal(t, input, result)
|
assert.Equal(t, input, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractFirstBraced_UnbalancedBraces(t *testing.T) {
|
func TestExtractFirstBraced_UnbalancedBraces(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
input := `text { not closed`
|
input := `text { not closed`
|
||||||
result := extractFirstBraced(input)
|
result := extractFirstBraced(input)
|
||||||
assert.Empty(t, result)
|
assert.Empty(t, result)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRedactor_APIKeys(t *testing.T) {
|
func TestRedactor_APIKeys(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewRedactor()
|
r := NewRedactor()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -30,6 +31,7 @@ func TestRedactor_APIKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedactor_Bearer(t *testing.T) {
|
func TestRedactor_Bearer(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewRedactor()
|
r := NewRedactor()
|
||||||
input := "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxxxx"
|
input := "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxxxx"
|
||||||
out := r.Redact(input)
|
out := r.Redact(input)
|
||||||
|
|
@ -37,6 +39,7 @@ func TestRedactor_Bearer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedactor_JWT(t *testing.T) {
|
func TestRedactor_JWT(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewRedactor()
|
r := NewRedactor()
|
||||||
jwt := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"
|
jwt := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"
|
||||||
out := r.Redact(jwt)
|
out := r.Redact(jwt)
|
||||||
|
|
@ -44,6 +47,7 @@ func TestRedactor_JWT(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedactor_PII(t *testing.T) {
|
func TestRedactor_PII(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewRedactor()
|
r := NewRedactor()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -64,6 +68,7 @@ func TestRedactor_PII(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedactor_SecretValues(t *testing.T) {
|
func TestRedactor_SecretValues(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewRedactor()
|
r := NewRedactor()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -84,6 +89,7 @@ func TestRedactor_SecretValues(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedactor_SafeText(t *testing.T) {
|
func TestRedactor_SafeText(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewRedactor()
|
r := NewRedactor()
|
||||||
safe := "This is a normal log message about processing 42 items."
|
safe := "This is a normal log message about processing 42 items."
|
||||||
assert.Equal(t, safe, r.Redact(safe))
|
assert.Equal(t, safe, r.Redact(safe))
|
||||||
|
|
@ -91,6 +97,7 @@ func TestRedactor_SafeText(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedactor_RedactMap(t *testing.T) {
|
func TestRedactor_RedactMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewRedactor()
|
r := NewRedactor()
|
||||||
m := map[string]interface{}{
|
m := map[string]interface{}{
|
||||||
"command": "curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxxxxxxxxxxxxxxxxxxx.yyyyyyyy'",
|
"command": "curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxxxxxxxxxxxxxxxxxxx.yyyyyyyy'",
|
||||||
|
|
@ -109,6 +116,7 @@ func TestRedactor_RedactMap(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMaskKey(t *testing.T) {
|
func TestMaskKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
want string
|
want string
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAuditLogAppendAndRetrieve(t *testing.T) {
|
func TestAuditLogAppendAndRetrieve(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
al := NewAuditLog()
|
al := NewAuditLog()
|
||||||
|
|
||||||
event := AuditEvent{
|
event := AuditEvent{
|
||||||
|
|
@ -32,6 +33,7 @@ func TestAuditLogAppendAndRetrieve(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuditLogConcurrentAppend(t *testing.T) {
|
func TestAuditLogConcurrentAppend(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
al := NewAuditLog()
|
al := NewAuditLog()
|
||||||
n := 100
|
n := 100
|
||||||
|
|
||||||
|
|
@ -52,6 +54,7 @@ func TestAuditLogConcurrentAppend(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuditLogFilterBySession(t *testing.T) {
|
func TestAuditLogFilterBySession(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
al := NewAuditLog()
|
al := NewAuditLog()
|
||||||
|
|
||||||
_ = al.Append(AuditEvent{RequestID: "r1", SessionKey: "sess-A"})
|
_ = al.Append(AuditEvent{RequestID: "r1", SessionKey: "sess-A"})
|
||||||
|
|
@ -69,6 +72,7 @@ func TestAuditLogFilterBySession(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuditLogLeakEvents(t *testing.T) {
|
func TestAuditLogLeakEvents(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
al := NewAuditLog()
|
al := NewAuditLog()
|
||||||
|
|
||||||
_ = al.Append(AuditEvent{RequestID: "r1", LeakDetected: false})
|
_ = al.Append(AuditEvent{RequestID: "r1", LeakDetected: false})
|
||||||
|
|
@ -98,6 +102,7 @@ func (ms *mockSink) Write(event AuditEvent) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuditLogSinkIntegration(t *testing.T) {
|
func TestAuditLogSinkIntegration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sink := &mockSink{}
|
sink := &mockSink{}
|
||||||
al := NewAuditLog(sink)
|
al := NewAuditLog(sink)
|
||||||
|
|
||||||
|
|
@ -109,6 +114,7 @@ func TestAuditLogSinkIntegration(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuditLogSinkError(t *testing.T) {
|
func TestAuditLogSinkError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sink := &mockSink{failAt: 1}
|
sink := &mockSink{failAt: 1}
|
||||||
al := NewAuditLog(sink)
|
al := NewAuditLog(sink)
|
||||||
|
|
||||||
|
|
@ -121,6 +127,7 @@ func TestAuditLogSinkError(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuditLogEventsImmutable(t *testing.T) {
|
func TestAuditLogEventsImmutable(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
al := NewAuditLog()
|
al := NewAuditLog()
|
||||||
_ = al.Append(AuditEvent{RequestID: "r1"})
|
_ = al.Append(AuditEvent{RequestID: "r1"})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ package securebus_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/itr"
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/security"
|
"github.com/ZanzyTHEbar/dragonscale/pkg/security"
|
||||||
"github.com/ZanzyTHEbar/dragonscale/pkg/security/securebus"
|
"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 ─────────────────────────────────────────────────────────────────────
|
// ── tests ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func TestBus_SuccessfulToolExec(t *testing.T) {
|
func TestBus_SuccessfulToolExec(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := &staticTool{name: "greet", result: "hello world"}
|
tool := &staticTool{name: "greet", result: "hello world"}
|
||||||
bus := makeBus(t, map[string]tools.Tool{"greet": tool}, nil)
|
bus := makeBus(t, map[string]tools.Tool{"greet": tool}, nil)
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
req := itr.NewToolExecRequest("req-1", "sess", "tc-1", "greet", makeArgsJSON(nil))
|
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.False(t, resp.IsError)
|
||||||
assert.Equal(t, "hello world", resp.Result)
|
assert.Equal(t, "hello world", resp.Result)
|
||||||
|
|
@ -89,22 +91,24 @@ func TestBus_SuccessfulToolExec(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBus_UnknownTool(t *testing.T) {
|
func TestBus_UnknownTool(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
bus := makeBus(t, map[string]tools.Tool{}, nil)
|
bus := makeBus(t, map[string]tools.Tool{}, nil)
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
req := itr.NewToolExecRequest("req-2", "sess", "tc-2", "nonexistent", makeArgsJSON(nil))
|
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)
|
assert.True(t, resp.IsError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBus_ToolReturnsError(t *testing.T) {
|
func TestBus_ToolReturnsError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := &staticTool{name: "fail", result: "something broke", isErr: true}
|
tool := &staticTool{name: "fail", result: "something broke", isErr: true}
|
||||||
bus := makeBus(t, map[string]tools.Tool{"fail": tool}, nil)
|
bus := makeBus(t, map[string]tools.Tool{"fail": tool}, nil)
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
req := itr.NewToolExecRequest("req-3", "sess", "tc-3", "fail", makeArgsJSON(nil))
|
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.True(t, resp.IsError)
|
||||||
assert.Equal(t, 1, bus.AuditLog().Len())
|
assert.Equal(t, 1, bus.AuditLog().Len())
|
||||||
|
|
@ -113,14 +117,17 @@ func TestBus_ToolReturnsError(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBus_LeakDetection(t *testing.T) {
|
func TestBus_LeakDetection(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Tool output contains an API key — should be redacted.
|
// Tool output contains an API key — should be redacted.
|
||||||
|
)
|
||||||
|
|
||||||
apiKey := "AKIAIOSFODNN7EXAMPLE" // fake AWS key matching redactor pattern
|
apiKey := "AKIAIOSFODNN7EXAMPLE" // fake AWS key matching redactor pattern
|
||||||
tool := &staticTool{name: "leaky", result: "result: " + apiKey}
|
tool := &staticTool{name: "leaky", result: "result: " + apiKey}
|
||||||
bus := makeBus(t, map[string]tools.Tool{"leaky": tool}, nil)
|
bus := makeBus(t, map[string]tools.Tool{"leaky": tool}, nil)
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
req := itr.NewToolExecRequest("req-4", "sess", "tc-4", "leaky", makeArgsJSON(nil))
|
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.True(t, resp.LeakDetected, "should detect API key in output")
|
||||||
assert.NotContains(t, resp.Result, apiKey, "raw API key must not appear in response")
|
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) {
|
func TestBus_SecretInjection_ArgVariant(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Tool reads injected "token" arg from args map.
|
// Tool reads injected "token" arg from args map.
|
||||||
|
)
|
||||||
|
|
||||||
echoT := &echoTool{}
|
echoT := &echoTool{}
|
||||||
|
|
||||||
// Give echo tool a capability that declares a secret injected as arg:input.
|
// 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()
|
defer bus.Close()
|
||||||
|
|
||||||
req := itr.NewToolExecRequest("req-5", "sess", "tc-5", "echo", makeArgsJSON(nil))
|
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.False(t, resp.IsError)
|
||||||
assert.Equal(t, "supersecret", resp.Result, "injected secret should appear in tool output")
|
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) {
|
func TestBus_PolicyViolation_RecursionDepth(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := &staticTool{name: "ok", result: "fine"}
|
tool := &staticTool{name: "ok", result: "fine"}
|
||||||
bus := makeBus(t, map[string]tools.Tool{"ok": tool}, nil)
|
bus := makeBus(t, map[string]tools.Tool{"ok": tool}, nil)
|
||||||
defer bus.Close()
|
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 := itr.NewToolExecRequest("req-6", "sess", "tc-6", "ok", makeArgsJSON(nil))
|
||||||
req.Depth = 255 // far exceeds MaxRecursionDepth=10
|
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")
|
assert.True(t, resp.IsError, "depth violation should produce an error response")
|
||||||
events := bus.AuditLog().Events()
|
events := bus.AuditLog().Events()
|
||||||
|
|
@ -201,13 +212,14 @@ func TestBus_PolicyViolation_RecursionDepth(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBus_AuditLog_FilterBySession(t *testing.T) {
|
func TestBus_AuditLog_FilterBySession(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := &staticTool{name: "t", result: "ok"}
|
tool := &staticTool{name: "t", result: "ok"}
|
||||||
bus := makeBus(t, map[string]tools.Tool{"t": tool}, nil)
|
bus := makeBus(t, map[string]tools.Tool{"t": tool}, nil)
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
for _, sk := range []string{"session-A", "session-A", "session-B"} {
|
for _, sk := range []string{"session-A", "session-A", "session-B"} {
|
||||||
req := itr.NewToolExecRequest("req-audit-"+sk, sk, "tc", "t", makeArgsJSON(nil))
|
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())
|
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) {
|
func TestBus_Transport_Send(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := &staticTool{name: "ping", result: "pong"}
|
tool := &staticTool{name: "ping", result: "pong"}
|
||||||
bus := makeBus(t, map[string]tools.Tool{"ping": tool}, nil)
|
bus := makeBus(t, map[string]tools.Tool{"ping": tool}, nil)
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
req := itr.NewToolExecRequest("req-tr", "sess", "tc", "ping", makeArgsJSON(nil))
|
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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "pong", resp.Result)
|
assert.Equal(t, "pong", resp.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBus_InvalidArgsJSON(t *testing.T) {
|
func TestBus_InvalidArgsJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tool := &staticTool{name: "ok", result: "ok"}
|
tool := &staticTool{name: "ok", result: "ok"}
|
||||||
bus := makeBus(t, map[string]tools.Tool{"ok": tool}, nil)
|
bus := makeBus(t, map[string]tools.Tool{"ok": tool}, nil)
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
req := itr.NewToolExecRequest("req-bad", "sess", "tc", "ok", "{invalid json")
|
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)
|
assert.True(t, resp.IsError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBus_RLMFinalCommand(t *testing.T) {
|
func TestBus_RLMFinalCommand(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
bus := makeBus(t, nil, nil)
|
bus := makeBus(t, nil, nil)
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
req := itr.NewFinalRequest("req-final", "sess", 0, "the answer", "")
|
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.False(t, resp.IsError)
|
||||||
assert.Equal(t, "the answer", resp.Result)
|
assert.Equal(t, "the answer", resp.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBus_CloseIdempotent(t *testing.T) {
|
func TestBus_CloseIdempotent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
bus := makeBus(t, nil, nil)
|
bus := makeBus(t, nil, nil)
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
|
|
@ -260,6 +276,7 @@ func TestBus_CloseIdempotent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBus_ToolSearch(t *testing.T) {
|
func TestBus_ToolSearch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
bus := makeBus(t, nil, nil)
|
bus := makeBus(t, nil, nil)
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
|
|
@ -268,24 +285,26 @@ func TestBus_ToolSearch(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
req := itr.NewToolSearchRequest("req-search", "sess", "file operations", 5)
|
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.False(t, resp.IsError)
|
||||||
assert.Contains(t, resp.Result, "read_file")
|
assert.Contains(t, resp.Result, "read_file")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBus_ToolSearchNotConfigured(t *testing.T) {
|
func TestBus_ToolSearchNotConfigured(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
bus := makeBus(t, nil, nil)
|
bus := makeBus(t, nil, nil)
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
req := itr.NewToolSearchRequest("req-search2", "sess", "anything", 5)
|
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.True(t, resp.IsError)
|
||||||
assert.Contains(t, resp.Result, "not configured")
|
assert.Contains(t, resp.Result, "not configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBus_NilToolResult(t *testing.T) {
|
func TestBus_NilToolResult(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
executor := func(ctx context.Context, name string, args map[string]interface{}) *tools.ToolResult {
|
executor := func(ctx context.Context, name string, args map[string]interface{}) *tools.ToolResult {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -296,7 +315,7 @@ func TestBus_NilToolResult(t *testing.T) {
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
req := itr.NewToolExecRequest("req-nil", "sess", "tc", "something", "{}")
|
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.False(t, resp.IsError)
|
||||||
assert.Empty(t, resp.Result)
|
assert.Empty(t, resp.Result)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPolicyValidateRecursionDepth(t *testing.T) {
|
func TestPolicyValidateRecursionDepth(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pe := NewPolicyEngine(PolicyConfig{MaxRecursionDepth: 5})
|
pe := NewPolicyEngine(PolicyConfig{MaxRecursionDepth: 5})
|
||||||
|
|
||||||
req := itr.ToolRequest{Depth: 3}
|
req := itr.ToolRequest{Depth: 3}
|
||||||
|
|
@ -22,6 +23,7 @@ func TestPolicyValidateRecursionDepth(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyValidateNoDepthLimit(t *testing.T) {
|
func TestPolicyValidateNoDepthLimit(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pe := NewPolicyEngine(PolicyConfig{MaxRecursionDepth: 0})
|
pe := NewPolicyEngine(PolicyConfig{MaxRecursionDepth: 0})
|
||||||
|
|
||||||
req := itr.ToolRequest{Depth: 255}
|
req := itr.ToolRequest{Depth: 255}
|
||||||
|
|
@ -29,6 +31,7 @@ func TestPolicyValidateNoDepthLimit(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyValidateNetworkSSRFBlocked(t *testing.T) {
|
func TestPolicyValidateNetworkSSRFBlocked(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pe := NewPolicyEngine(DefaultPolicyConfig())
|
pe := NewPolicyEngine(DefaultPolicyConfig())
|
||||||
|
|
||||||
ssrfURLs := []string{
|
ssrfURLs := []string{
|
||||||
|
|
@ -53,6 +56,7 @@ func TestPolicyValidateNetworkSSRFBlocked(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyValidateNetworkAllowed(t *testing.T) {
|
func TestPolicyValidateNetworkAllowed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pe := NewPolicyEngine(DefaultPolicyConfig())
|
pe := NewPolicyEngine(DefaultPolicyConfig())
|
||||||
rules := []tools.EndpointRule{{Pattern: "https://api.github.com/*"}}
|
rules := []tools.EndpointRule{{Pattern: "https://api.github.com/*"}}
|
||||||
|
|
||||||
|
|
@ -60,6 +64,7 @@ func TestPolicyValidateNetworkAllowed(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyValidateNetworkNoRules(t *testing.T) {
|
func TestPolicyValidateNetworkNoRules(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pe := NewPolicyEngine(DefaultPolicyConfig())
|
pe := NewPolicyEngine(DefaultPolicyConfig())
|
||||||
err := pe.ValidateNetwork("https://example.com", nil)
|
err := pe.ValidateNetwork("https://example.com", nil)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
@ -67,6 +72,7 @@ func TestPolicyValidateNetworkNoRules(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyValidateNetworkNoMatchingRule(t *testing.T) {
|
func TestPolicyValidateNetworkNoMatchingRule(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pe := NewPolicyEngine(DefaultPolicyConfig())
|
pe := NewPolicyEngine(DefaultPolicyConfig())
|
||||||
rules := []tools.EndpointRule{{Pattern: "https://api.github.com/*"}}
|
rules := []tools.EndpointRule{{Pattern: "https://api.github.com/*"}}
|
||||||
|
|
||||||
|
|
@ -76,6 +82,7 @@ func TestPolicyValidateNetworkNoMatchingRule(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyValidateFilesystemAllowed(t *testing.T) {
|
func TestPolicyValidateFilesystemAllowed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pe := NewPolicyEngine(PolicyConfig{AllowedWorkspace: "/workspace"})
|
pe := NewPolicyEngine(PolicyConfig{AllowedWorkspace: "/workspace"})
|
||||||
rules := []tools.PathRule{{Pattern: "src/*", Mode: "rw"}}
|
rules := []tools.PathRule{{Pattern: "src/*", Mode: "rw"}}
|
||||||
|
|
||||||
|
|
@ -85,6 +92,7 @@ func TestPolicyValidateFilesystemAllowed(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyValidateFilesystemNoRules(t *testing.T) {
|
func TestPolicyValidateFilesystemNoRules(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pe := NewPolicyEngine(PolicyConfig{})
|
pe := NewPolicyEngine(PolicyConfig{})
|
||||||
err := pe.ValidateFilesystem("/etc/passwd", "r", nil)
|
err := pe.ValidateFilesystem("/etc/passwd", "r", nil)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
@ -92,6 +100,7 @@ func TestPolicyValidateFilesystemNoRules(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyValidateFilesystemModeMismatch(t *testing.T) {
|
func TestPolicyValidateFilesystemModeMismatch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
pe := NewPolicyEngine(PolicyConfig{AllowedWorkspace: "/workspace"})
|
pe := NewPolicyEngine(PolicyConfig{AllowedWorkspace: "/workspace"})
|
||||||
rules := []tools.PathRule{{Pattern: "data/*", Mode: "r"}}
|
rules := []tools.PathRule{{Pattern: "data/*", Mode: "r"}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSocketTransportRoundTrip(t *testing.T) {
|
func TestSocketTransportRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sockPath := filepath.Join(t.TempDir(), "test.sock")
|
sockPath := filepath.Join(t.TempDir(), "test.sock")
|
||||||
|
|
||||||
server, err := NewSocketTransportServer(sockPath)
|
server, err := NewSocketTransportServer(sockPath)
|
||||||
|
|
@ -47,7 +48,7 @@ func TestSocketTransportRoundTrip(t *testing.T) {
|
||||||
Timestamp: time.Now().UnixNano(),
|
Timestamp: time.Now().UnixNano(),
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Send(context.Background(), req)
|
resp, err := client.Send(t.Context(), req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "req-001", resp.ID)
|
assert.Equal(t, "req-001", resp.ID)
|
||||||
assert.Contains(t, resp.Result, "req-001")
|
assert.Contains(t, resp.Result, "req-001")
|
||||||
|
|
@ -57,6 +58,7 @@ func TestSocketTransportRoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketTransportMultipleRequests(t *testing.T) {
|
func TestSocketTransportMultipleRequests(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sockPath := filepath.Join(t.TempDir(), "multi.sock")
|
sockPath := filepath.Join(t.TempDir(), "multi.sock")
|
||||||
|
|
||||||
server, err := NewSocketTransportServer(sockPath)
|
server, err := NewSocketTransportServer(sockPath)
|
||||||
|
|
@ -82,13 +84,14 @@ func TestSocketTransportMultipleRequests(t *testing.T) {
|
||||||
"echo",
|
"echo",
|
||||||
`{}`,
|
`{}`,
|
||||||
)
|
)
|
||||||
resp, err := client.Send(context.Background(), req)
|
resp, err := client.Send(t.Context(), req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, req.ID, resp.ID)
|
assert.Equal(t, req.ID, resp.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketTransportCleanup(t *testing.T) {
|
func TestSocketTransportCleanup(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sockPath := filepath.Join(t.TempDir(), "cleanup.sock")
|
sockPath := filepath.Join(t.TempDir(), "cleanup.sock")
|
||||||
|
|
||||||
server, err := NewSocketTransportServer(sockPath)
|
server, err := NewSocketTransportServer(sockPath)
|
||||||
|
|
@ -104,6 +107,7 @@ func TestSocketTransportCleanup(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketTransportClientSendOnClosed(t *testing.T) {
|
func TestSocketTransportClientSendOnClosed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sockPath := filepath.Join(t.TempDir(), "closed.sock")
|
sockPath := filepath.Join(t.TempDir(), "closed.sock")
|
||||||
|
|
||||||
server, err := NewSocketTransportServer(sockPath)
|
server, err := NewSocketTransportServer(sockPath)
|
||||||
|
|
@ -120,7 +124,7 @@ func TestSocketTransportClientSendOnClosed(t *testing.T) {
|
||||||
|
|
||||||
client.Close()
|
client.Close()
|
||||||
|
|
||||||
_, err = client.Send(context.Background(), itr.ToolRequest{ID: "fail"})
|
_, err = client.Send(t.Context(), itr.ToolRequest{ID: "fail"})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
||||||
server.Close()
|
server.Close()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidateURL_AllowedURLs(t *testing.T) {
|
func TestValidateURL_AllowedURLs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []string{
|
tests := []string{
|
||||||
"https://example.com",
|
"https://example.com",
|
||||||
"https://api.openai.com/v1/chat",
|
"https://api.openai.com/v1/chat",
|
||||||
|
|
@ -23,6 +24,7 @@ func TestValidateURL_AllowedURLs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateURL_BlockedSchemes(t *testing.T) {
|
func TestValidateURL_BlockedSchemes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []string{
|
tests := []string{
|
||||||
"file:///etc/passwd",
|
"file:///etc/passwd",
|
||||||
"ftp://internal.server/data",
|
"ftp://internal.server/data",
|
||||||
|
|
@ -38,6 +40,7 @@ func TestValidateURL_BlockedSchemes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateURL_BlockedHosts(t *testing.T) {
|
func TestValidateURL_BlockedHosts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
url string
|
url string
|
||||||
|
|
@ -57,6 +60,7 @@ func TestValidateURL_BlockedHosts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateURL_BlockedIPs(t *testing.T) {
|
func TestValidateURL_BlockedIPs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
url string
|
url string
|
||||||
|
|
@ -78,6 +82,7 @@ func TestValidateURL_BlockedIPs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateURL_EmptyAndInvalid(t *testing.T) {
|
func TestValidateURL_EmptyAndInvalid(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
url string
|
url string
|
||||||
|
|
@ -95,6 +100,7 @@ func TestValidateURL_EmptyAndInvalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsBlockedIP(t *testing.T) {
|
func TestIsBlockedIP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
ip string
|
ip string
|
||||||
blocked bool
|
blocked bool
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVault_RoundTrip(t *testing.T) {
|
func TestVault_RoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
key, err := GenerateKey()
|
key, err := GenerateKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, key, 32)
|
require.Len(t, key, 32)
|
||||||
|
|
@ -35,6 +36,7 @@ func TestVault_RoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVault_DifferentCiphertexts(t *testing.T) {
|
func TestVault_DifferentCiphertexts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
key, _ := GenerateKey()
|
key, _ := GenerateKey()
|
||||||
v, _ := NewVault(key)
|
v, _ := NewVault(key)
|
||||||
|
|
||||||
|
|
@ -44,6 +46,7 @@ func TestVault_DifferentCiphertexts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVault_WrongKey(t *testing.T) {
|
func TestVault_WrongKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
key1, _ := GenerateKey()
|
key1, _ := GenerateKey()
|
||||||
key2, _ := GenerateKey()
|
key2, _ := GenerateKey()
|
||||||
|
|
||||||
|
|
@ -58,6 +61,7 @@ func TestVault_WrongKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVault_TamperedCiphertext(t *testing.T) {
|
func TestVault_TamperedCiphertext(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
key, _ := GenerateKey()
|
key, _ := GenerateKey()
|
||||||
v, _ := NewVault(key)
|
v, _ := NewVault(key)
|
||||||
|
|
||||||
|
|
@ -70,6 +74,7 @@ func TestVault_TamperedCiphertext(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVault_InvalidKeyLength(t *testing.T) {
|
func TestVault_InvalidKeyLength(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := NewVault([]byte("too-short"))
|
_, err := NewVault([]byte("too-short"))
|
||||||
assert.ErrorIs(t, err, ErrKeyLength)
|
assert.ErrorIs(t, err, ErrKeyLength)
|
||||||
|
|
||||||
|
|
@ -78,6 +83,7 @@ func TestVault_InvalidKeyLength(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVault_EmptyInput(t *testing.T) {
|
func TestVault_EmptyInput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
key, _ := GenerateKey()
|
key, _ := GenerateKey()
|
||||||
v, _ := NewVault(key)
|
v, _ := NewVault(key)
|
||||||
|
|
||||||
|
|
@ -90,6 +96,7 @@ func TestVault_EmptyInput(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVault_BinaryData(t *testing.T) {
|
func TestVault_BinaryData(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
key, _ := GenerateKey()
|
key, _ := GenerateKey()
|
||||||
v, _ := NewVault(key)
|
v, _ := NewVault(key)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSchnorrKeypair(t *testing.T) {
|
func TestSchnorrKeypair(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
key := make([]byte, 32)
|
key := make([]byte, 32)
|
||||||
for i := range key {
|
for i := range key {
|
||||||
key[i] = byte(i + 1)
|
key[i] = byte(i + 1)
|
||||||
|
|
@ -22,11 +23,13 @@ func TestSchnorrKeypair(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSchnorrKeypairRejectsBadLength(t *testing.T) {
|
func TestSchnorrKeypairRejectsBadLength(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, _, err := SchnorrKeypair([]byte("short"))
|
_, _, err := SchnorrKeypair([]byte("short"))
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSchnorrFullHandshake(t *testing.T) {
|
func TestSchnorrFullHandshake(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
masterKey := make([]byte, 32)
|
masterKey := make([]byte, 32)
|
||||||
for i := range masterKey {
|
for i := range masterKey {
|
||||||
masterKey[i] = byte(i + 42)
|
masterKey[i] = byte(i + 42)
|
||||||
|
|
@ -49,6 +52,7 @@ func TestSchnorrFullHandshake(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSchnorrRejectsWrongKey(t *testing.T) {
|
func TestSchnorrRejectsWrongKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
masterKey1 := make([]byte, 32)
|
masterKey1 := make([]byte, 32)
|
||||||
masterKey2 := make([]byte, 32)
|
masterKey2 := make([]byte, 32)
|
||||||
for i := range masterKey1 {
|
for i := range masterKey1 {
|
||||||
|
|
@ -73,6 +77,7 @@ func TestSchnorrRejectsWrongKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZKPSessionManagerIssueAndValidate(t *testing.T) {
|
func TestZKPSessionManagerIssueAndValidate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
masterKey := make([]byte, 32)
|
masterKey := make([]byte, 32)
|
||||||
for i := range masterKey {
|
for i := range masterKey {
|
||||||
masterKey[i] = byte(i + 7)
|
masterKey[i] = byte(i + 7)
|
||||||
|
|
@ -96,6 +101,7 @@ func TestZKPSessionManagerIssueAndValidate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZKPSessionManagerRejectsInvalidProof(t *testing.T) {
|
func TestZKPSessionManagerRejectsInvalidProof(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
masterKey := make([]byte, 32)
|
masterKey := make([]byte, 32)
|
||||||
for i := range masterKey {
|
for i := range masterKey {
|
||||||
masterKey[i] = byte(i)
|
masterKey[i] = byte(i)
|
||||||
|
|
@ -109,6 +115,7 @@ func TestZKPSessionManagerRejectsInvalidProof(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZKPSessionManagerExpiry(t *testing.T) {
|
func TestZKPSessionManagerExpiry(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
masterKey := make([]byte, 32)
|
masterKey := make([]byte, 32)
|
||||||
for i := range masterKey {
|
for i := range masterKey {
|
||||||
masterKey[i] = byte(i + 3)
|
masterKey[i] = byte(i + 3)
|
||||||
|
|
@ -128,6 +135,7 @@ func TestZKPSessionManagerExpiry(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZKPSessionManagerRevoke(t *testing.T) {
|
func TestZKPSessionManagerRevoke(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
masterKey := make([]byte, 32)
|
masterKey := make([]byte, 32)
|
||||||
for i := range masterKey {
|
for i := range masterKey {
|
||||||
masterKey[i] = byte(i + 5)
|
masterKey[i] = byte(i + 5)
|
||||||
|
|
@ -148,6 +156,7 @@ func TestZKPSessionManagerRevoke(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandshakePayloadBinaryRoundTrip(t *testing.T) {
|
func TestHandshakePayloadBinaryRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
hp := HandshakePayload{Phase: 3}
|
hp := HandshakePayload{Phase: 3}
|
||||||
for i := 0; i < 32; i++ {
|
for i := 0; i < 32; i++ {
|
||||||
hp.RX[i] = byte(i)
|
hp.RX[i] = byte(i)
|
||||||
|
|
@ -165,6 +174,7 @@ func TestHandshakePayloadBinaryRoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandshakeResultBinaryRoundTrip(t *testing.T) {
|
func TestHandshakeResultBinaryRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
hr := HandshakeResult{ExpiresUnix: time.Now().Unix()}
|
hr := HandshakeResult{ExpiresUnix: time.Now().Unix()}
|
||||||
for i := 0; i < 32; i++ {
|
for i := 0; i < 32; i++ {
|
||||||
hr.SessionToken[i] = byte(i + 200)
|
hr.SessionToken[i] = byte(i + 200)
|
||||||
|
|
@ -179,6 +189,7 @@ func TestHandshakeResultBinaryRoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZKPSessionManagerCleanup(t *testing.T) {
|
func TestZKPSessionManagerCleanup(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
masterKey := make([]byte, 32)
|
masterKey := make([]byte, 32)
|
||||||
for i := range masterKey {
|
for i := range masterKey {
|
||||||
masterKey[i] = byte(i + 11)
|
masterKey[i] = byte(i + 11)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package session
|
package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -9,6 +8,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jsonv2 "github.com/go-json-experiment/json"
|
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/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSanitizeFilename(t *testing.T) {
|
func TestSanitizeFilename(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
expected string
|
expected string
|
||||||
|
|
@ -40,6 +42,7 @@ func TestSanitizeFilename(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSave_WithColonInKey(t *testing.T) {
|
func TestSave_WithColonInKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
sm := NewSessionManager(tmpDir)
|
sm := NewSessionManager(tmpDir)
|
||||||
|
|
||||||
|
|
@ -71,6 +74,7 @@ func TestSave_WithColonInKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSave_RejectsPathTraversal(t *testing.T) {
|
func TestSave_RejectsPathTraversal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
sm := NewSessionManager(tmpDir)
|
sm := NewSessionManager(tmpDir)
|
||||||
|
|
||||||
|
|
@ -84,6 +88,7 @@ func TestSave_RejectsPathTraversal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTruncateHistory_ToolCallAware(t *testing.T) {
|
func TestTruncateHistory_ToolCallAware(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sm := NewSessionManager("") // in-memory only
|
sm := NewSessionManager("") // in-memory only
|
||||||
|
|
||||||
key := "test-tool-truncation"
|
key := "test-tool-truncation"
|
||||||
|
|
@ -129,6 +134,7 @@ func TestTruncateHistory_ToolCallAware(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTruncateHistory_NoToolCalls(t *testing.T) {
|
func TestTruncateHistory_NoToolCalls(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sm := NewSessionManager("")
|
sm := NewSessionManager("")
|
||||||
|
|
||||||
key := "test-no-tools"
|
key := "test-no-tools"
|
||||||
|
|
@ -151,6 +157,7 @@ func TestTruncateHistory_NoToolCalls(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddFullMessage_NoHardCapTruncation(t *testing.T) {
|
func TestAddFullMessage_NoHardCapTruncation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sm := NewSessionManager("")
|
sm := NewSessionManager("")
|
||||||
|
|
||||||
key := "test-hard-cap"
|
key := "test-hard-cap"
|
||||||
|
|
@ -167,6 +174,7 @@ func TestAddFullMessage_NoHardCapTruncation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCleanupStale(t *testing.T) {
|
func TestCleanupStale(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sm := NewSessionManager("")
|
sm := NewSessionManager("")
|
||||||
|
|
||||||
sm.AddFullMessage("active", messages.Message{Role: "user", Content: "hi"})
|
sm.AddFullMessage("active", messages.Message{Role: "user", Content: "hi"})
|
||||||
|
|
@ -195,11 +203,12 @@ func TestCleanupStale(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionManager_DelegatePersistence(t *testing.T) {
|
func TestSessionManager_DelegatePersistence(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewLibSQLInMemory: %v", err)
|
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)
|
t.Fatalf("Init: %v", err)
|
||||||
}
|
}
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
@ -215,7 +224,7 @@ func TestSessionManager_DelegatePersistence(t *testing.T) {
|
||||||
t.Fatalf("expected 2 messages in-memory, got %d", len(history))
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("ListRecallItems: %v", err)
|
t.Fatalf("ListRecallItems: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -228,11 +237,12 @@ func TestSessionManager_DelegatePersistence(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionManager_DelegateSaveIsNoop(t *testing.T) {
|
func TestSessionManager_DelegateSaveIsNoop(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewLibSQLInMemory: %v", err)
|
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)
|
t.Fatalf("Init: %v", err)
|
||||||
}
|
}
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
@ -256,11 +266,12 @@ func TestSessionManager_DelegateSaveIsNoop(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionManager_DelegateBootstrapPaginationAndOrder(t *testing.T) {
|
func TestSessionManager_DelegateBootstrapPaginationAndOrder(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewLibSQLInMemory: %v", err)
|
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)
|
t.Fatalf("Init: %v", err)
|
||||||
}
|
}
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
@ -291,9 +302,10 @@ func TestSessionManager_DelegateBootstrapPaginationAndOrder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionManager_ProjectionPointerPersistedAndRestored(t *testing.T) {
|
func TestSessionManager_ProjectionPointerPersistedAndRestored(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, del.Init(context.Background()))
|
require.NoError(t, del.Init(t.Context()))
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
sessionKey := "ptr-persist"
|
sessionKey := "ptr-persist"
|
||||||
|
|
@ -303,35 +315,34 @@ func TestSessionManager_ProjectionPointerPersistedAndRestored(t *testing.T) {
|
||||||
sm.AddMessage(sessionKey, "user", "third")
|
sm.AddMessage(sessionKey, "user", "third")
|
||||||
|
|
||||||
// Read persisted pointer from KV
|
// 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.NoError(t, err)
|
||||||
require.NotEmpty(t, raw)
|
require.NotEmpty(t, raw)
|
||||||
var ptr ProjectionPointer
|
var ptr ProjectionPointer
|
||||||
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &ptr))
|
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &ptr))
|
||||||
assert.Equal(t, 3, ptr.Count)
|
assert.NotZero(t, ptr.FirstMessageID)
|
||||||
assert.False(t, ptr.FirstMessageID.IsZero())
|
assert.NotZero(t, ptr.LastMessageID)
|
||||||
assert.False(t, ptr.LastMessageID.IsZero())
|
|
||||||
assert.False(t, ptr.FirstCreatedAt.IsZero())
|
assert.False(t, ptr.FirstCreatedAt.IsZero())
|
||||||
assert.False(t, ptr.LastCreatedAt.IsZero())
|
assert.False(t, ptr.LastCreatedAt.IsZero())
|
||||||
|
assert.Equal(t, 3, ptr.Count)
|
||||||
|
|
||||||
// New manager restores; pointer is re-persisted (same values)
|
// New manager restores; pointer is re-persisted (same values)
|
||||||
sm2 := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
|
sm2 := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
|
||||||
history := sm2.GetHistory(sessionKey)
|
history := sm2.GetHistory(sessionKey)
|
||||||
require.Len(t, history, 3)
|
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.NoError(t, err)
|
||||||
require.NotEmpty(t, raw2)
|
require.NotEmpty(t, raw2)
|
||||||
var ptr2 ProjectionPointer
|
var ptr2 ProjectionPointer
|
||||||
require.NoError(t, jsonv2.Unmarshal([]byte(raw2), &ptr2))
|
require.NoError(t, jsonv2.Unmarshal([]byte(raw2), &ptr2))
|
||||||
assert.Equal(t, ptr.Count, ptr2.Count)
|
assert.Empty(t, cmp.Diff(ptr, ptr2, cmpopts.IgnoreFields(ProjectionPointer{}, "FirstCreatedAt", "LastCreatedAt")))
|
||||||
assert.Equal(t, ptr.FirstMessageID, ptr2.FirstMessageID)
|
|
||||||
assert.Equal(t, ptr.LastMessageID, ptr2.LastMessageID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionManager_ProjectionPointerUpdatedOnAppend(t *testing.T) {
|
func TestSessionManager_ProjectionPointerUpdatedOnAppend(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, del.Init(context.Background()))
|
require.NoError(t, del.Init(t.Context()))
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
sessionKey := "ptr-append"
|
sessionKey := "ptr-append"
|
||||||
|
|
@ -339,7 +350,7 @@ func TestSessionManager_ProjectionPointerUpdatedOnAppend(t *testing.T) {
|
||||||
|
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
sm.AddMessage(sessionKey, "user", fmt.Sprintf("msg-%d", 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.NoError(t, err)
|
||||||
require.NotEmpty(t, raw)
|
require.NotEmpty(t, raw)
|
||||||
var ptr ProjectionPointer
|
var ptr ProjectionPointer
|
||||||
|
|
@ -349,9 +360,10 @@ func TestSessionManager_ProjectionPointerUpdatedOnAppend(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
|
func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, del.Init(context.Background()))
|
require.NoError(t, del.Init(t.Context()))
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
sessionKey := "integrity-mismatch"
|
sessionKey := "integrity-mismatch"
|
||||||
|
|
@ -362,7 +374,7 @@ func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
|
||||||
// Corrupt the stored pointer to simulate prior state mismatch
|
// Corrupt the stored pointer to simulate prior state mismatch
|
||||||
corrupt := ProjectionPointer{Count: 0}
|
corrupt := ProjectionPointer{Count: 0}
|
||||||
data, _ := jsonv2.Marshal(corrupt)
|
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
|
// New manager restores; should succeed (lossless) despite mismatch
|
||||||
sm2 := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
|
sm2 := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
|
||||||
|
|
@ -372,7 +384,7 @@ func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
|
||||||
assert.Equal(t, "b", history[1].Content)
|
assert.Equal(t, "b", history[1].Content)
|
||||||
|
|
||||||
// Pointer should now reflect restored state
|
// 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.NoError(t, err)
|
||||||
require.NotEmpty(t, raw)
|
require.NotEmpty(t, raw)
|
||||||
var ptr ProjectionPointer
|
var ptr ProjectionPointer
|
||||||
|
|
@ -381,9 +393,10 @@ func TestSessionManager_IntegrityMismatchRestoreStillSucceeds(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionManager_ProjectionBackfillStatusPersistedOnBootstrap(t *testing.T) {
|
func TestSessionManager_ProjectionBackfillStatusPersistedOnBootstrap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
del, err := delegate.NewLibSQLInMemory()
|
del, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, del.Init(context.Background()))
|
require.NoError(t, del.Init(t.Context()))
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
writer := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
|
writer := NewSessionManager("", WithSessionDelegate(del, "test-agent"))
|
||||||
|
|
@ -393,7 +406,7 @@ func TestSessionManager_ProjectionBackfillStatusPersistedOnBootstrap(t *testing.
|
||||||
|
|
||||||
_ = NewSessionManager("", WithSessionDelegate(del, "test-agent"))
|
_ = 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.NoError(t, err)
|
||||||
require.NotEmpty(t, raw)
|
require.NotEmpty(t, raw)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseWikilinks(t *testing.T) {
|
func TestParseWikilinks(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
content string
|
content string
|
||||||
|
|
@ -66,6 +67,7 @@ func TestParseWikilinks(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeUnique(t *testing.T) {
|
func TestMergeUnique(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
a, b []string
|
a, b []string
|
||||||
|
|
@ -94,6 +96,7 @@ func writeSkill(t *testing.T, dir, name, content string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildGraph_Basic(t *testing.T) {
|
func TestBuildGraph_Basic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
|
|
||||||
writeSkill(t, tmp, "risk-management", `---
|
writeSkill(t, tmp, "risk-management", `---
|
||||||
|
|
@ -152,6 +155,7 @@ No wikilinks here.
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildGraph_FrontmatterLinksAndWikilinks(t *testing.T) {
|
func TestBuildGraph_FrontmatterLinksAndWikilinks(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
|
|
||||||
writeSkill(t, tmp, "alpha", `---
|
writeSkill(t, tmp, "alpha", `---
|
||||||
|
|
@ -186,6 +190,7 @@ No links.
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTraverseFrom(t *testing.T) {
|
func TestTraverseFrom(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
|
|
||||||
writeSkill(t, tmp, "a", `---
|
writeSkill(t, tmp, "a", `---
|
||||||
|
|
@ -233,6 +238,7 @@ No outgoing links.
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchSkills(t *testing.T) {
|
func TestSearchSkills(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
|
|
||||||
writeSkill(t, tmp, "risk-management", `---
|
writeSkill(t, tmp, "risk-management", `---
|
||||||
|
|
@ -278,6 +284,7 @@ Content.
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListMOCs(t *testing.T) {
|
func TestListMOCs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
|
|
||||||
writeSkill(t, tmp, "trading-moc", `---
|
writeSkill(t, tmp, "trading-moc", `---
|
||||||
|
|
@ -305,6 +312,7 @@ Content.
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetIndex(t *testing.T) {
|
func TestGetIndex(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
|
|
||||||
writeSkill(t, tmp, "index", `---
|
writeSkill(t, tmp, "index", `---
|
||||||
|
|
@ -332,6 +340,7 @@ Content.
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetIndex_None(t *testing.T) {
|
func TestGetIndex_None(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
writeSkill(t, tmp, "some-skill", `---
|
writeSkill(t, tmp, "some-skill", `---
|
||||||
name: some-skill
|
name: some-skill
|
||||||
|
|
@ -346,6 +355,7 @@ Content.
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtendedFrontmatter_JSON(t *testing.T) {
|
func TestExtendedFrontmatter_JSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
|
|
||||||
writeSkill(t, tmp, "json-skill", `---
|
writeSkill(t, tmp, "json-skill", `---
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSkillsInfoValidate(t *testing.T) {
|
func TestSkillsInfoValidate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
name string
|
name string
|
||||||
skillName string
|
skillName string
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAvailableTemplates(t *testing.T) {
|
func TestAvailableTemplates(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
templates := AvailableTemplates()
|
templates := AvailableTemplates()
|
||||||
assert.Contains(t, templates, "trading")
|
assert.Contains(t, templates, "trading")
|
||||||
assert.Contains(t, templates, "legal")
|
assert.Contains(t, templates, "legal")
|
||||||
|
|
@ -17,6 +18,7 @@ func TestAvailableTemplates(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstallTemplate(t *testing.T) {
|
func TestInstallTemplate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
|
|
||||||
err := InstallTemplate("trading", tmp)
|
err := InstallTemplate("trading", tmp)
|
||||||
|
|
@ -35,6 +37,7 @@ func TestInstallTemplate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstallTemplate_BuildsValidGraph(t *testing.T) {
|
func TestInstallTemplate_BuildsValidGraph(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
require.NoError(t, InstallTemplate("trading", tmp))
|
require.NoError(t, InstallTemplate("trading", tmp))
|
||||||
|
|
||||||
|
|
@ -56,6 +59,7 @@ func TestInstallTemplate_BuildsValidGraph(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstallTemplate_NotFound(t *testing.T) {
|
func TestInstallTemplate_NotFound(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
err := InstallTemplate("nonexistent", tmp)
|
err := InstallTemplate("nonexistent", tmp)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -11,7 +10,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAtomicSave(t *testing.T) {
|
func TestAtomicSave(t *testing.T) {
|
||||||
|
t.Parallel(
|
||||||
// Create temp workspace
|
// Create temp workspace
|
||||||
|
)
|
||||||
|
|
||||||
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -21,7 +23,7 @@ func TestAtomicSave(t *testing.T) {
|
||||||
sm := NewManager(tmpDir)
|
sm := NewManager(tmpDir)
|
||||||
|
|
||||||
// Test SetLastChannel
|
// Test SetLastChannel
|
||||||
err = sm.SetLastChannel(context.Background(), "test-channel")
|
err = sm.SetLastChannel(t.Context(), "test-channel")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("SetLastChannel failed: %v", err)
|
t.Fatalf("SetLastChannel failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -51,6 +53,7 @@ func TestAtomicSave(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetLastChatID(t *testing.T) {
|
func TestSetLastChatID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -60,7 +63,7 @@ func TestSetLastChatID(t *testing.T) {
|
||||||
sm := NewManager(tmpDir)
|
sm := NewManager(tmpDir)
|
||||||
|
|
||||||
// Test SetLastChatID
|
// Test SetLastChatID
|
||||||
err = sm.SetLastChatID(context.Background(), "test-chat-id")
|
err = sm.SetLastChatID(t.Context(), "test-chat-id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("SetLastChatID failed: %v", err)
|
t.Fatalf("SetLastChatID failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -84,6 +87,7 @@ func TestSetLastChatID(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAtomicity_NoCorruptionOnInterrupt(t *testing.T) {
|
func TestAtomicity_NoCorruptionOnInterrupt(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -93,7 +97,7 @@ func TestAtomicity_NoCorruptionOnInterrupt(t *testing.T) {
|
||||||
sm := NewManager(tmpDir)
|
sm := NewManager(tmpDir)
|
||||||
|
|
||||||
// Write initial state
|
// Write initial state
|
||||||
err = sm.SetLastChannel(context.Background(), "initial-channel")
|
err = sm.SetLastChannel(t.Context(), "initial-channel")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("SetLastChannel failed: %v", err)
|
t.Fatalf("SetLastChannel failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +119,7 @@ func TestAtomicity_NoCorruptionOnInterrupt(t *testing.T) {
|
||||||
os.Remove(tempFile)
|
os.Remove(tempFile)
|
||||||
|
|
||||||
// Now do a proper save
|
// Now do a proper save
|
||||||
err = sm.SetLastChannel(context.Background(), "new-channel")
|
err = sm.SetLastChannel(t.Context(), "new-channel")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("SetLastChannel failed: %v", err)
|
t.Fatalf("SetLastChannel failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -127,6 +131,7 @@ func TestAtomicity_NoCorruptionOnInterrupt(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConcurrentAccess(t *testing.T) {
|
func TestConcurrentAccess(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -140,7 +145,7 @@ func TestConcurrentAccess(t *testing.T) {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
go func(idx int) {
|
go func(idx int) {
|
||||||
channel := fmt.Sprintf("channel-%d", idx)
|
channel := fmt.Sprintf("channel-%d", idx)
|
||||||
sm.SetLastChannel(context.Background(), channel)
|
sm.SetLastChannel(t.Context(), channel)
|
||||||
done <- true
|
done <- true
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
|
|
@ -170,6 +175,7 @@ func TestConcurrentAccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewManager_ExistingState(t *testing.T) {
|
func TestNewManager_ExistingState(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
@ -178,8 +184,8 @@ func TestNewManager_ExistingState(t *testing.T) {
|
||||||
|
|
||||||
// Create initial state
|
// Create initial state
|
||||||
sm1 := NewManager(tmpDir)
|
sm1 := NewManager(tmpDir)
|
||||||
sm1.SetLastChannel(context.Background(), "existing-channel")
|
sm1.SetLastChannel(t.Context(), "existing-channel")
|
||||||
sm1.SetLastChatID(context.Background(), "existing-chat-id")
|
sm1.SetLastChatID(t.Context(), "existing-chat-id")
|
||||||
|
|
||||||
// Create new manager with same workspace
|
// Create new manager with same workspace
|
||||||
sm2 := NewManager(tmpDir)
|
sm2 := NewManager(tmpDir)
|
||||||
|
|
@ -195,6 +201,7 @@ func TestNewManager_ExistingState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewManager_EmptyWorkspace(t *testing.T) {
|
func TestNewManager_EmptyWorkspace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
tmpDir, err := os.MkdirTemp("", "state-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp dir: %v", err)
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ func setupIdentityDir(t *testing.T, files map[string]string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSyncAll_InsertsNewFiles(t *testing.T) {
|
func TestSyncAll_InsertsNewFiles(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := setupIdentityDir(t, map[string]string{
|
dir := setupIdentityDir(t, map[string]string{
|
||||||
"AGENT.md": "# Agent\nYou are helpful.",
|
"AGENT.md": "# Agent\nYou are helpful.",
|
||||||
"SOUL.md": "# Soul\nCurious and kind.",
|
"SOUL.md": "# Soul\nCurious and kind.",
|
||||||
|
|
@ -95,7 +96,7 @@ func TestSyncAll_InsertsNewFiles(t *testing.T) {
|
||||||
store := newMockStore()
|
store := newMockStore()
|
||||||
s := New(dir, "agent-1", store)
|
s := New(dir, "agent-1", store)
|
||||||
|
|
||||||
require.NoError(t, s.SyncAll(context.Background()))
|
require.NoError(t, s.SyncAll(t.Context()))
|
||||||
|
|
||||||
for _, name := range IdentityFiles {
|
for _, name := range IdentityFiles {
|
||||||
doc := store.getDoc("agent-1", name)
|
doc := store.getDoc("agent-1", name)
|
||||||
|
|
@ -109,6 +110,7 @@ func TestSyncAll_InsertsNewFiles(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSyncAll_SkipsUnchangedFiles(t *testing.T) {
|
func TestSyncAll_SkipsUnchangedFiles(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := setupIdentityDir(t, map[string]string{
|
dir := setupIdentityDir(t, map[string]string{
|
||||||
"AGENT.md": "# Agent\nSame content.",
|
"AGENT.md": "# Agent\nSame content.",
|
||||||
})
|
})
|
||||||
|
|
@ -116,29 +118,30 @@ func TestSyncAll_SkipsUnchangedFiles(t *testing.T) {
|
||||||
store := newMockStore()
|
store := newMockStore()
|
||||||
s := New(dir, "agent-1", store)
|
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")
|
firstDoc := store.getDoc("agent-1", "AGENT.md")
|
||||||
require.NotNil(t, firstDoc)
|
require.NotNil(t, firstDoc)
|
||||||
firstID := firstDoc.ID
|
firstID := firstDoc.ID
|
||||||
|
|
||||||
require.NoError(t, s.SyncAll(context.Background()))
|
require.NoError(t, s.SyncAll(t.Context()))
|
||||||
secondDoc := store.getDoc("agent-1", "AGENT.md")
|
secondDoc := store.getDoc("agent-1", "AGENT.md")
|
||||||
assert.Equal(t, firstID, secondDoc.ID, "unchanged file should not be re-upserted")
|
assert.Equal(t, firstID, secondDoc.ID, "unchanged file should not be re-upserted")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSyncAll_UpsertsModifiedFiles(t *testing.T) {
|
func TestSyncAll_UpsertsModifiedFiles(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := setupIdentityDir(t, map[string]string{
|
dir := setupIdentityDir(t, map[string]string{
|
||||||
"AGENT.md": "# Agent\nVersion 1",
|
"AGENT.md": "# Agent\nVersion 1",
|
||||||
})
|
})
|
||||||
|
|
||||||
store := newMockStore()
|
store := newMockStore()
|
||||||
s := New(dir, "agent-1", store)
|
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")
|
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, 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")
|
v2Hash := store.getHash("agent-1", "AGENT.md")
|
||||||
assert.NotEqual(t, v1Hash, v2Hash, "hash should change after file modification")
|
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) {
|
func TestSyncAll_SkipsMissingFiles(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := setupIdentityDir(t, map[string]string{
|
dir := setupIdentityDir(t, map[string]string{
|
||||||
"AGENT.md": "# Agent only",
|
"AGENT.md": "# Agent only",
|
||||||
})
|
})
|
||||||
|
|
@ -155,7 +159,7 @@ func TestSyncAll_SkipsMissingFiles(t *testing.T) {
|
||||||
store := newMockStore()
|
store := newMockStore()
|
||||||
s := New(dir, "agent-1", store)
|
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.NotNil(t, store.getDoc("agent-1", "AGENT.md"))
|
||||||
assert.Nil(t, store.getDoc("agent-1", "SOUL.md"), "missing file should not create a doc")
|
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) {
|
func TestSyncAll_SkipsEmptyFiles(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := setupIdentityDir(t, map[string]string{
|
dir := setupIdentityDir(t, map[string]string{
|
||||||
"AGENT.md": " \n\t\n ",
|
"AGENT.md": " \n\t\n ",
|
||||||
})
|
})
|
||||||
|
|
@ -171,11 +176,12 @@ func TestSyncAll_SkipsEmptyFiles(t *testing.T) {
|
||||||
store := newMockStore()
|
store := newMockStore()
|
||||||
s := New(dir, "agent-1", store)
|
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")
|
assert.Nil(t, store.getDoc("agent-1", "AGENT.md"), "empty/whitespace-only file should be skipped")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSyncAll_IsolatesAgents(t *testing.T) {
|
func TestSyncAll_IsolatesAgents(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := setupIdentityDir(t, map[string]string{
|
dir := setupIdentityDir(t, map[string]string{
|
||||||
"AGENT.md": "# Shared agent file",
|
"AGENT.md": "# Shared agent file",
|
||||||
})
|
})
|
||||||
|
|
@ -184,8 +190,8 @@ func TestSyncAll_IsolatesAgents(t *testing.T) {
|
||||||
s1 := New(dir, "agent-a", store)
|
s1 := New(dir, "agent-a", store)
|
||||||
s2 := New(dir, "agent-b", store)
|
s2 := New(dir, "agent-b", store)
|
||||||
|
|
||||||
require.NoError(t, s1.SyncAll(context.Background()))
|
require.NoError(t, s1.SyncAll(t.Context()))
|
||||||
require.NoError(t, s2.SyncAll(context.Background()))
|
require.NoError(t, s2.SyncAll(t.Context()))
|
||||||
|
|
||||||
docA := store.getDoc("agent-a", "AGENT.md")
|
docA := store.getDoc("agent-a", "AGENT.md")
|
||||||
docB := store.getDoc("agent-b", "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) {
|
func TestCheckAndSync_DetectsModifiedFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := setupIdentityDir(t, map[string]string{
|
dir := setupIdentityDir(t, map[string]string{
|
||||||
"AGENT.md": "# Original",
|
"AGENT.md": "# Original",
|
||||||
})
|
})
|
||||||
|
|
||||||
store := newMockStore()
|
store := newMockStore()
|
||||||
s := New(dir, "agent-1", store)
|
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)
|
time.Sleep(50 * time.Millisecond)
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "AGENT.md"), []byte("# Modified"), 0644))
|
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")
|
doc := store.getDoc("agent-1", "AGENT.md")
|
||||||
assert.Contains(t, doc.Content, "Modified")
|
assert.Contains(t, doc.Content, "Modified")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckAndSync_SkipsUntouchedFiles(t *testing.T) {
|
func TestCheckAndSync_SkipsUntouchedFiles(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := setupIdentityDir(t, map[string]string{
|
dir := setupIdentityDir(t, map[string]string{
|
||||||
"AGENT.md": "# Stable",
|
"AGENT.md": "# Stable",
|
||||||
})
|
})
|
||||||
|
|
||||||
store := newMockStore()
|
store := newMockStore()
|
||||||
s := New(dir, "agent-1", store)
|
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")
|
hash1 := store.getHash("agent-1", "AGENT.md")
|
||||||
|
|
||||||
s.lastSync.Store(time.Now().Add(time.Second).UnixNano())
|
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")
|
hash2 := store.getHash("agent-1", "AGENT.md")
|
||||||
assert.Equal(t, hash1, hash2, "untouched file should not trigger re-sync")
|
assert.Equal(t, hash1, hash2, "untouched file should not trigger re-sync")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWatch_DetectsFileChange(t *testing.T) {
|
func TestWatch_DetectsFileChange(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := setupIdentityDir(t, map[string]string{
|
dir := setupIdentityDir(t, map[string]string{
|
||||||
"AGENT.md": "# Initial",
|
"AGENT.md": "# Initial",
|
||||||
})
|
})
|
||||||
|
|
||||||
store := newMockStore()
|
store := newMockStore()
|
||||||
s := New(dir, "agent-1", store)
|
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))
|
require.NoError(t, s.Watch(ctx))
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
|
|
@ -252,15 +261,16 @@ func TestWatch_DetectsFileChange(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWatch_IgnoresNonIdentityFiles(t *testing.T) {
|
func TestWatch_IgnoresNonIdentityFiles(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := setupIdentityDir(t, map[string]string{
|
dir := setupIdentityDir(t, map[string]string{
|
||||||
"AGENT.md": "# Agent",
|
"AGENT.md": "# Agent",
|
||||||
})
|
})
|
||||||
|
|
||||||
store := newMockStore()
|
store := newMockStore()
|
||||||
s := New(dir, "agent-1", store)
|
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))
|
require.NoError(t, s.Watch(ctx))
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
|
|
@ -271,6 +281,7 @@ func TestWatch_IgnoresNonIdentityFiles(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContentHash_Deterministic(t *testing.T) {
|
func TestContentHash_Deterministic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
data := []byte("hello world")
|
data := []byte("hello world")
|
||||||
h1 := contentHash(data)
|
h1 := contentHash(data)
|
||||||
h2 := contentHash(data)
|
h2 := contentHash(data)
|
||||||
|
|
@ -279,12 +290,14 @@ func TestContentHash_Deterministic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContentHash_DifferentForDifferentContent(t *testing.T) {
|
func TestContentHash_DifferentForDifferentContent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
h1 := contentHash([]byte("version 1"))
|
h1 := contentHash([]byte("version 1"))
|
||||||
h2 := contentHash([]byte("version 2"))
|
h2 := contentHash([]byte("version 2"))
|
||||||
assert.NotEqual(t, h1, h2)
|
assert.NotEqual(t, h1, h2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsIdentityFile(t *testing.T) {
|
func TestIsIdentityFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
want bool
|
want bool
|
||||||
|
|
@ -306,6 +319,7 @@ func TestIsIdentityFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew_SetsFields(t *testing.T) {
|
func TestNew_SetsFields(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
store := newMockStore()
|
store := newMockStore()
|
||||||
s := New("/tmp/identity", "test-agent", store)
|
s := New("/tmp/identity", "test-agent", store)
|
||||||
assert.Equal(t, "/tmp/identity", s.identityDir)
|
assert.Equal(t, "/tmp/identity", s.identityDir)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAgenticMapTool_Execute_Success(t *testing.T) {
|
func TestAgenticMapTool_Execute_Success(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
provider := &MockLanguageModel{}
|
provider := &MockLanguageModel{}
|
||||||
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
|
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
|
||||||
manager.SetRunLoop(func(_ context.Context, _ ToolLoopConfig, _, userPrompt, _, _ string) (*ToolLoopResult, error) {
|
manager.SetRunLoop(func(_ context.Context, _ ToolLoopConfig, _, userPrompt, _, _ string) (*ToolLoopResult, error) {
|
||||||
|
|
@ -20,7 +21,7 @@ func TestAgenticMapTool_Execute_Success(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
tool := NewAgenticMapTool(manager)
|
tool := NewAgenticMapTool(manager)
|
||||||
result := tool.Execute(context.Background(), map[string]interface{}{
|
result := tool.Execute(t.Context(), map[string]interface{}{
|
||||||
"items": []interface{}{
|
"items": []interface{}{
|
||||||
map[string]interface{}{"name": "a"},
|
map[string]interface{}{"name": "a"},
|
||||||
map[string]interface{}{"name": "b"},
|
map[string]interface{}{"name": "b"},
|
||||||
|
|
@ -46,6 +47,7 @@ func TestAgenticMapTool_Execute_Success(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAgenticMapTool_Execute_RetriesFailedItems(t *testing.T) {
|
func TestAgenticMapTool_Execute_RetriesFailedItems(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
provider := &MockLanguageModel{}
|
provider := &MockLanguageModel{}
|
||||||
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
|
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
|
||||||
callCount := 0
|
callCount := 0
|
||||||
|
|
@ -58,7 +60,7 @@ func TestAgenticMapTool_Execute_RetriesFailedItems(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
tool := NewAgenticMapTool(manager)
|
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"}},
|
"items": []interface{}{map[string]interface{}{"name": "retry-me"}},
|
||||||
"task_template": "Retry item {{index}} => {{item_json}}",
|
"task_template": "Retry item {{index}} => {{item_json}}",
|
||||||
"max_retries": float64(2),
|
"max_retries": float64(2),
|
||||||
|
|
@ -80,11 +82,12 @@ func TestAgenticMapTool_Execute_RetriesFailedItems(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAgenticMapTool_Execute_RequiresPlaceholders(t *testing.T) {
|
func TestAgenticMapTool_Execute_RequiresPlaceholders(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
provider := &MockLanguageModel{}
|
provider := &MockLanguageModel{}
|
||||||
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
|
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
|
||||||
tool := NewAgenticMapTool(manager)
|
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"}},
|
"items": []interface{}{map[string]interface{}{"name": "x"}},
|
||||||
"task_template": "Handle item without placeholders",
|
"task_template": "Handle item without placeholders",
|
||||||
})
|
})
|
||||||
|
|
@ -95,11 +98,12 @@ func TestAgenticMapTool_Execute_RequiresPlaceholders(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAgenticMapTool_Execute_ContextCancelled(t *testing.T) {
|
func TestAgenticMapTool_Execute_ContextCancelled(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
provider := &MockLanguageModel{}
|
provider := &MockLanguageModel{}
|
||||||
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
|
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
|
||||||
tool := NewAgenticMapTool(manager)
|
tool := NewAgenticMapTool(manager)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
result := tool.Execute(ctx, map[string]interface{}{
|
result := tool.Execute(ctx, map[string]interface{}{
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestToolCallTool_Name(t *testing.T) {
|
func TestToolCallTool_Name(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
if tc.Name() != "tool_call" {
|
if tc.Name() != "tool_call" {
|
||||||
|
|
@ -15,6 +16,7 @@ func TestToolCallTool_Name(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_Description(t *testing.T) {
|
func TestToolCallTool_Description(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
if tc.Description() == "" {
|
if tc.Description() == "" {
|
||||||
|
|
@ -23,6 +25,7 @@ func TestToolCallTool_Description(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_Parameters(t *testing.T) {
|
func TestToolCallTool_Parameters(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
params := tc.Parameters()
|
params := tc.Parameters()
|
||||||
|
|
@ -40,9 +43,10 @@ func TestToolCallTool_Parameters(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_MissingToolName(t *testing.T) {
|
func TestToolCallTool_MissingToolName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
result := tc.Execute(context.Background(), map[string]interface{}{})
|
result := tc.Execute(t.Context(), map[string]interface{}{})
|
||||||
|
|
||||||
if !result.IsError {
|
if !result.IsError {
|
||||||
t.Error("expected error for missing tool_name")
|
t.Error("expected error for missing tool_name")
|
||||||
|
|
@ -50,11 +54,12 @@ func TestToolCallTool_MissingToolName(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_DispatchesToTool(t *testing.T) {
|
func TestToolCallTool_DispatchesToTool(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
r.Register(&stubTool{name: "read_file", desc: "Read a file"})
|
r.Register(&stubTool{name: "read_file", desc: "Read a file"})
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
|
|
||||||
result := tc.Execute(context.Background(), map[string]interface{}{
|
result := tc.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "read_file",
|
"tool_name": "read_file",
|
||||||
"arguments": map[string]interface{}{"path": "/tmp/test.txt"},
|
"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) {
|
func TestToolCallTool_PreventRecursion_ToolCall(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
r.RegisterMetaTools()
|
r.RegisterMetaTools()
|
||||||
|
|
||||||
tc, _ := r.Get("tool_call")
|
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",
|
"tool_name": "tool_call",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -82,11 +88,12 @@ func TestToolCallTool_PreventRecursion_ToolCall(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_PreventRecursion_ToolSearch(t *testing.T) {
|
func TestToolCallTool_PreventRecursion_ToolSearch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
r.RegisterMetaTools()
|
r.RegisterMetaTools()
|
||||||
|
|
||||||
tc, _ := r.Get("tool_call")
|
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",
|
"tool_name": "tool_search",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -96,11 +103,12 @@ func TestToolCallTool_PreventRecursion_ToolSearch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_JSONStringArguments(t *testing.T) {
|
func TestToolCallTool_JSONStringArguments(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
r.Register(&echoTool{})
|
r.Register(&echoTool{})
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
|
|
||||||
result := tc.Execute(context.Background(), map[string]interface{}{
|
result := tc.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "echo",
|
"tool_name": "echo",
|
||||||
"arguments": `{"msg":"hello"}`,
|
"arguments": `{"msg":"hello"}`,
|
||||||
})
|
})
|
||||||
|
|
@ -114,11 +122,12 @@ func TestToolCallTool_JSONStringArguments(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_NilArguments(t *testing.T) {
|
func TestToolCallTool_NilArguments(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
r.Register(&stubTool{name: "no_args", desc: "Tool that needs no args"})
|
r.Register(&stubTool{name: "no_args", desc: "Tool that needs no args"})
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
|
|
||||||
result := tc.Execute(context.Background(), map[string]interface{}{
|
result := tc.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "no_args",
|
"tool_name": "no_args",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -128,11 +137,12 @@ func TestToolCallTool_NilArguments(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_InvalidJSONArguments(t *testing.T) {
|
func TestToolCallTool_InvalidJSONArguments(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
r.Register(&stubToolWithSchema{name: "read_file", desc: "Read a file"})
|
r.Register(&stubToolWithSchema{name: "read_file", desc: "Read a file"})
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
|
|
||||||
result := tc.Execute(context.Background(), map[string]interface{}{
|
result := tc.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "read_file",
|
"tool_name": "read_file",
|
||||||
"arguments": "not-json",
|
"arguments": "not-json",
|
||||||
})
|
})
|
||||||
|
|
@ -147,11 +157,12 @@ func TestToolCallTool_InvalidJSONArguments(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_MissingRequiredArgs_IncludesSchemaHint(t *testing.T) {
|
func TestToolCallTool_MissingRequiredArgs_IncludesSchemaHint(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
r.Register(&stubToolWithSchema{name: "read_file", desc: "Read a file"})
|
r.Register(&stubToolWithSchema{name: "read_file", desc: "Read a file"})
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
|
|
||||||
result := tc.Execute(context.Background(), map[string]interface{}{
|
result := tc.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "read_file",
|
"tool_name": "read_file",
|
||||||
"arguments": map[string]interface{}{},
|
"arguments": map[string]interface{}{},
|
||||||
})
|
})
|
||||||
|
|
@ -168,10 +179,11 @@ func TestToolCallTool_MissingRequiredArgs_IncludesSchemaHint(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_ToolNotFoundSuggestsSearch(t *testing.T) {
|
func TestToolCallTool_ToolNotFoundSuggestsSearch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
|
|
||||||
result := tc.Execute(context.Background(), map[string]interface{}{
|
result := tc.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "nonexistent",
|
"tool_name": "nonexistent",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -184,6 +196,7 @@ func TestToolCallTool_ToolNotFoundSuggestsSearch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_ContextPropagation(t *testing.T) {
|
func TestToolCallTool_ContextPropagation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
ct := &contextCaptureTool{}
|
ct := &contextCaptureTool{}
|
||||||
r.Register(ct)
|
r.Register(ct)
|
||||||
|
|
@ -191,7 +204,7 @@ func TestToolCallTool_ContextPropagation(t *testing.T) {
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
tc.SetContext("test-channel", "test-chat")
|
tc.SetContext("test-channel", "test-chat")
|
||||||
|
|
||||||
tc.Execute(context.Background(), map[string]interface{}{
|
tc.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "capture",
|
"tool_name": "capture",
|
||||||
"arguments": map[string]interface{}{},
|
"arguments": map[string]interface{}{},
|
||||||
})
|
})
|
||||||
|
|
@ -203,6 +216,7 @@ func TestToolCallTool_ContextPropagation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_ResourceProvider_LoadsResources(t *testing.T) {
|
func TestToolCallTool_ResourceProvider_LoadsResources(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
rt := &resourceAwareTool{
|
rt := &resourceAwareTool{
|
||||||
resources: map[string]string{
|
resources: map[string]string{
|
||||||
|
|
@ -212,7 +226,7 @@ func TestToolCallTool_ResourceProvider_LoadsResources(t *testing.T) {
|
||||||
r.Register(rt)
|
r.Register(rt)
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
|
|
||||||
result := tc.Execute(context.Background(), map[string]interface{}{
|
result := tc.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "resource_tool",
|
"tool_name": "resource_tool",
|
||||||
"arguments": map[string]interface{}{},
|
"arguments": map[string]interface{}{},
|
||||||
})
|
})
|
||||||
|
|
@ -231,12 +245,13 @@ func TestToolCallTool_ResourceProvider_LoadsResources(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_NormalizesCommaSeparatedToolName(t *testing.T) {
|
func TestToolCallTool_NormalizesCommaSeparatedToolName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
r.Register(&stubTool{name: "write_file", desc: "write"})
|
r.Register(&stubTool{name: "write_file", desc: "write"})
|
||||||
r.Register(&stubTool{name: "read_file", desc: "read"})
|
r.Register(&stubTool{name: "read_file", desc: "read"})
|
||||||
tc := NewToolCallTool(r)
|
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",
|
"tool_name": "write_file, read_file",
|
||||||
"arguments": map[string]interface{}{"path": "x.txt"},
|
"arguments": map[string]interface{}{"path": "x.txt"},
|
||||||
})
|
})
|
||||||
|
|
@ -250,11 +265,12 @@ func TestToolCallTool_NormalizesCommaSeparatedToolName(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_NormalizesEmbeddedToolName(t *testing.T) {
|
func TestToolCallTool_NormalizesEmbeddedToolName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
r.Register(&stubTool{name: "exec", desc: "exec"})
|
r.Register(&stubTool{name: "exec", desc: "exec"})
|
||||||
tc := NewToolCallTool(r)
|
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",
|
"tool_name": "exec_tool_search_query_exec_run_shell_command_return_output_caution",
|
||||||
"arguments": map[string]interface{}{"command": "echo hi"},
|
"arguments": map[string]interface{}{"command": "echo hi"},
|
||||||
})
|
})
|
||||||
|
|
@ -268,11 +284,12 @@ func TestToolCallTool_NormalizesEmbeddedToolName(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToolCallTool_NoResourceProvider_StillWorks(t *testing.T) {
|
func TestToolCallTool_NoResourceProvider_StillWorks(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
r.Register(&stubTool{name: "plain", desc: "No resources"})
|
r.Register(&stubTool{name: "plain", desc: "No resources"})
|
||||||
tc := NewToolCallTool(r)
|
tc := NewToolCallTool(r)
|
||||||
|
|
||||||
result := tc.Execute(context.Background(), map[string]interface{}{
|
result := tc.Execute(t.Context(), map[string]interface{}{
|
||||||
"tool_name": "plain",
|
"tool_name": "plain",
|
||||||
"arguments": map[string]interface{}{},
|
"arguments": map[string]interface{}{},
|
||||||
})
|
})
|
||||||
|
|
@ -283,7 +300,8 @@ func TestToolCallTool_NoResourceProvider_StillWorks(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourcesFromContext_Empty(t *testing.T) {
|
func TestResourcesFromContext_Empty(t *testing.T) {
|
||||||
res := ResourcesFromContext(context.Background())
|
t.Parallel()
|
||||||
|
res := ResourcesFromContext(t.Context())
|
||||||
if res != nil {
|
if res != nil {
|
||||||
t.Error("expected nil for empty context")
|
t.Error("expected nil for empty context")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -16,7 +15,7 @@ import (
|
||||||
func setupDAGTools(t *testing.T) (DAGToolDeps, *delegate.LibSQLDelegate) {
|
func setupDAGTools(t *testing.T) (DAGToolDeps, *delegate.LibSQLDelegate) {
|
||||||
d, err := delegate.NewLibSQLInMemory()
|
d, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
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
|
// Insert session messages so dag_expand/dag_grep have data
|
||||||
agentID, sessionKey := "test-agent", "test-session"
|
agentID, sessionKey := "test-agent", "test-session"
|
||||||
|
|
@ -26,7 +25,7 @@ func setupDAGTools(t *testing.T) (DAGToolDeps, *delegate.LibSQLDelegate) {
|
||||||
role = "assistant"
|
role = "assistant"
|
||||||
}
|
}
|
||||||
content := "Message number " + strconv.Itoa(i)
|
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
|
// 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))}
|
msgs[i] = dag.Message{Role: role, Content: "Message " + string(rune('A'+i%26))}
|
||||||
}
|
}
|
||||||
dagOut := compressor.Compress(msgs)
|
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,
|
FromMsgIdx: 0,
|
||||||
ToMsgIdx: 16,
|
ToMsgIdx: 16,
|
||||||
MsgCount: 16,
|
MsgCount: 16,
|
||||||
|
|
@ -66,11 +65,12 @@ func setupDAGTools(t *testing.T) (DAGToolDeps, *delegate.LibSQLDelegate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDagDescribeTool(t *testing.T) {
|
func TestDagDescribeTool(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
deps, del := setupDAGTools(t)
|
deps, del := setupDAGTools(t)
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
tool := NewDagDescribeTool(deps)
|
tool := NewDagDescribeTool(deps)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Describe an existing node (chunk-1 from default config with 16 msgs)
|
// 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"})
|
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) {
|
func TestDagDescribeTool_NoSnapshot(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d, err := delegate.NewLibSQLInMemory()
|
d, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
require.NoError(t, d.Init(context.Background()))
|
require.NoError(t, d.Init(t.Context()))
|
||||||
|
|
||||||
tool := NewDagDescribeTool(DAGToolDeps{
|
tool := NewDagDescribeTool(DAGToolDeps{
|
||||||
Queries: d.Queries(),
|
Queries: d.Queries(),
|
||||||
|
|
@ -91,17 +92,18 @@ func TestDagDescribeTool_NoSnapshot(t *testing.T) {
|
||||||
AgentID: "x",
|
AgentID: "x",
|
||||||
SessionFn: func() string { return "nonexistent" },
|
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.True(t, res.IsError)
|
||||||
assert.Contains(t, res.ForLLM, "no DAG snapshot")
|
assert.Contains(t, res.ForLLM, "no DAG snapshot")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDagGrepTool(t *testing.T) {
|
func TestDagGrepTool(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
deps, del := setupDAGTools(t)
|
deps, del := setupDAGTools(t)
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
tool := NewDagGrepTool(deps)
|
tool := NewDagGrepTool(deps)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
res := tool.Execute(ctx, map[string]interface{}{"query": "Message", "session_key": "test-session"})
|
res := tool.Execute(ctx, map[string]interface{}{"query": "Message", "session_key": "test-session"})
|
||||||
assert.False(t, res.IsError)
|
assert.False(t, res.IsError)
|
||||||
|
|
@ -110,11 +112,12 @@ func TestDagGrepTool(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDagGrepTool_ScopedByNode(t *testing.T) {
|
func TestDagGrepTool_ScopedByNode(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
deps, del := setupDAGTools(t)
|
deps, del := setupDAGTools(t)
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
tool := NewDagGrepTool(deps)
|
tool := NewDagGrepTool(deps)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
res := tool.Execute(ctx, map[string]interface{}{
|
res := tool.Execute(ctx, map[string]interface{}{
|
||||||
"query": "number",
|
"query": "number",
|
||||||
|
|
@ -126,11 +129,12 @@ func TestDagGrepTool_ScopedByNode(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDagExpandTool(t *testing.T) {
|
func TestDagExpandTool(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
deps, del := setupDAGTools(t)
|
deps, del := setupDAGTools(t)
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
tool := NewDagExpandTool(deps)
|
tool := NewDagExpandTool(deps)
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
|
|
||||||
// Expand chunk-1; needs Lister to return messages
|
// Expand chunk-1; needs Lister to return messages
|
||||||
res := tool.Execute(ctx, map[string]interface{}{"node_id": "chunk-1", "session_key": "test-session"})
|
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) {
|
func TestDagExpandTool_NoReader(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d, err := delegate.NewLibSQLInMemory()
|
d, err := delegate.NewLibSQLInMemory()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
require.NoError(t, d.Init(context.Background()))
|
require.NoError(t, d.Init(t.Context()))
|
||||||
|
|
||||||
// Persist minimal DAG
|
// Persist minimal DAG
|
||||||
compressor := dag.NewCompressor(dag.DefaultCompressorConfig())
|
compressor := dag.NewCompressor(dag.DefaultCompressorConfig())
|
||||||
msgs := []dag.Message{{Role: "user", Content: "x"}}
|
msgs := []dag.Message{{Role: "user", Content: "x"}}
|
||||||
dagOut := compressor.Compress(msgs)
|
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,
|
FromMsgIdx: 0, ToMsgIdx: 1, MsgCount: 1, DAG: dagOut,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
@ -162,16 +167,17 @@ func TestDagExpandTool_NoReader(t *testing.T) {
|
||||||
AgentID: "a",
|
AgentID: "a",
|
||||||
SessionFn: func() string { return "s" },
|
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.True(t, res.IsError)
|
||||||
assert.Contains(t, res.ForLLM, "dag query store is not configured")
|
assert.Contains(t, res.ForLLM, "dag query store is not configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDagExpandTool_RecoveryReference(t *testing.T) {
|
func TestDagExpandTool_RecoveryReference(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
deps, del := setupDAGTools(t)
|
deps, del := setupDAGTools(t)
|
||||||
defer del.Close()
|
defer del.Close()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := t.Context()
|
||||||
record := DAGRecoveryRecord{
|
record := DAGRecoveryRecord{
|
||||||
NodeID: DAGRecoveryNodePrefix + "test-recovery",
|
NodeID: DAGRecoveryNodePrefix + "test-recovery",
|
||||||
SessionKey: "test-session",
|
SessionKey: "test-session",
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue