mirror of
https://github.com/hanzoai/agent.git
synced 2026-09-11 21:08:51 +00:00
Consolidate all Hanzo agent infrastructure into one repo: - control-plane/: Go control plane server (was agents-new) - sdk/: Go, Python, TypeScript SDKs - deployments/: Docker, Kubernetes, Helm configs - Dockerfile.sdk: Python SDK image (ghcr.io/hanzoai/agent:latest) - CI builds both SDK + control-plane images No more agentfield — everything is hanzoai/agent.
695 lines
18 KiB
Go
695 lines
18 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzoai/agents/control-plane/internal/storage"
|
|
"github.com/hanzoai/agents/control-plane/pkg/types"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestBuildExecutionDAG_Simple(t *testing.T) {
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: "exec-1",
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ReasonerID: "reasoner-1",
|
|
},
|
|
}
|
|
|
|
dag, timeline, status, workflowName, sessionID, actorID, maxDepth := buildExecutionDAG(executions)
|
|
|
|
require.NotNil(t, dag)
|
|
require.Equal(t, "exec-1", dag.ExecutionID)
|
|
require.Equal(t, "run-1", dag.WorkflowID)
|
|
require.Equal(t, 0, dag.WorkflowDepth)
|
|
require.Empty(t, dag.Children)
|
|
require.Len(t, timeline, 1)
|
|
require.Equal(t, "succeeded", status)
|
|
require.Equal(t, "reasoner-1", workflowName)
|
|
require.Nil(t, sessionID)
|
|
require.Nil(t, actorID)
|
|
require.Equal(t, 0, maxDepth)
|
|
}
|
|
|
|
func TestBuildExecutionDAG_WithParentChild(t *testing.T) {
|
|
parentID := "exec-parent"
|
|
childID := "exec-child"
|
|
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: parentID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ParentExecutionID: nil,
|
|
ReasonerID: "reasoner-1",
|
|
},
|
|
{
|
|
ExecutionID: childID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(1 * time.Second),
|
|
ParentExecutionID: &parentID,
|
|
ReasonerID: "reasoner-2",
|
|
},
|
|
}
|
|
|
|
dag, timeline, status, _, _, _, maxDepth := buildExecutionDAG(executions)
|
|
|
|
require.NotNil(t, dag)
|
|
require.Equal(t, parentID, dag.ExecutionID)
|
|
require.Len(t, dag.Children, 1)
|
|
require.Equal(t, childID, dag.Children[0].ExecutionID)
|
|
require.Equal(t, 0, dag.WorkflowDepth)
|
|
require.Equal(t, 1, dag.Children[0].WorkflowDepth)
|
|
require.Len(t, timeline, 2)
|
|
require.Equal(t, "succeeded", status)
|
|
require.Equal(t, 1, maxDepth)
|
|
}
|
|
|
|
func TestBuildExecutionDAG_MultipleChildren(t *testing.T) {
|
|
parentID := "exec-parent"
|
|
child1ID := "exec-child-1"
|
|
child2ID := "exec-child-2"
|
|
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: parentID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ParentExecutionID: nil,
|
|
},
|
|
{
|
|
ExecutionID: child1ID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(1 * time.Second),
|
|
ParentExecutionID: &parentID,
|
|
},
|
|
{
|
|
ExecutionID: child2ID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(2 * time.Second),
|
|
ParentExecutionID: &parentID,
|
|
},
|
|
}
|
|
|
|
dag, timeline, _, _, _, _, maxDepth := buildExecutionDAG(executions)
|
|
|
|
require.NotNil(t, dag)
|
|
require.Equal(t, parentID, dag.ExecutionID)
|
|
require.Len(t, dag.Children, 2)
|
|
|
|
childIDs := make(map[string]bool)
|
|
for _, child := range dag.Children {
|
|
childIDs[child.ExecutionID] = true
|
|
}
|
|
require.True(t, childIDs[child1ID])
|
|
require.True(t, childIDs[child2ID])
|
|
require.Len(t, timeline, 3)
|
|
require.Equal(t, 1, maxDepth)
|
|
}
|
|
|
|
func TestBuildExecutionDAG_DeepHierarchy(t *testing.T) {
|
|
rootID := "exec-root"
|
|
level1ID := "exec-level1"
|
|
level2ID := "exec-level2"
|
|
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: rootID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ParentExecutionID: nil,
|
|
},
|
|
{
|
|
ExecutionID: level1ID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(1 * time.Second),
|
|
ParentExecutionID: &rootID,
|
|
},
|
|
{
|
|
ExecutionID: level2ID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(2 * time.Second),
|
|
ParentExecutionID: &level1ID,
|
|
},
|
|
}
|
|
|
|
dag, timeline, _, _, _, _, maxDepth := buildExecutionDAG(executions)
|
|
|
|
require.NotNil(t, dag)
|
|
require.Equal(t, rootID, dag.ExecutionID)
|
|
require.Len(t, dag.Children, 1)
|
|
require.Equal(t, level1ID, dag.Children[0].ExecutionID)
|
|
require.Len(t, dag.Children[0].Children, 1)
|
|
require.Equal(t, level2ID, dag.Children[0].Children[0].ExecutionID)
|
|
require.Len(t, timeline, 3)
|
|
require.Equal(t, 2, maxDepth)
|
|
}
|
|
|
|
func TestBuildExecutionDAG_EmptyExecutions(t *testing.T) {
|
|
executions := []*types.Execution{}
|
|
|
|
dag, timeline, status, workflowName, sessionID, actorID, maxDepth := buildExecutionDAG(executions)
|
|
|
|
require.Equal(t, WorkflowDAGNode{}, dag)
|
|
require.Empty(t, timeline)
|
|
require.Equal(t, "succeeded", status) // deriveOverallStatus returns "succeeded" for empty executions
|
|
require.Empty(t, workflowName)
|
|
require.Nil(t, sessionID)
|
|
require.Nil(t, actorID)
|
|
require.Equal(t, 0, maxDepth)
|
|
}
|
|
|
|
func TestBuildExecutionDAG_NilExecutions(t *testing.T) {
|
|
// Note: buildExecutionDAG doesn't handle nil executions well in sorting,
|
|
// so we test with empty slice instead
|
|
executions := []*types.Execution{}
|
|
|
|
dag, timeline, status, workflowName, sessionID, actorID, maxDepth := buildExecutionDAG(executions)
|
|
|
|
require.Equal(t, WorkflowDAGNode{}, dag)
|
|
require.Empty(t, timeline)
|
|
require.Equal(t, "succeeded", status) // deriveOverallStatus returns "succeeded" for empty executions
|
|
require.Empty(t, workflowName)
|
|
require.Nil(t, sessionID)
|
|
require.Nil(t, actorID)
|
|
require.Equal(t, 0, maxDepth)
|
|
}
|
|
|
|
func TestDeriveOverallStatus_AllSucceeded(t *testing.T) {
|
|
executions := []*types.Execution{
|
|
{Status: "succeeded"},
|
|
{Status: "succeeded"},
|
|
{Status: "succeeded"},
|
|
}
|
|
|
|
status := deriveOverallStatus(executions)
|
|
require.Equal(t, "succeeded", status)
|
|
}
|
|
|
|
func TestDeriveOverallStatus_OneFailed(t *testing.T) {
|
|
executions := []*types.Execution{
|
|
{Status: "succeeded"},
|
|
{Status: "failed"},
|
|
{Status: "succeeded"},
|
|
}
|
|
|
|
status := deriveOverallStatus(executions)
|
|
require.Equal(t, "failed", status)
|
|
}
|
|
|
|
func TestDeriveOverallStatus_OneRunning(t *testing.T) {
|
|
executions := []*types.Execution{
|
|
{Status: "succeeded"},
|
|
{Status: "running"},
|
|
{Status: "succeeded"},
|
|
}
|
|
|
|
status := deriveOverallStatus(executions)
|
|
require.Equal(t, "running", status)
|
|
}
|
|
|
|
func TestDeriveOverallStatus_Pending(t *testing.T) {
|
|
executions := []*types.Execution{
|
|
{Status: "succeeded"},
|
|
{Status: "pending"},
|
|
{Status: "succeeded"},
|
|
}
|
|
|
|
status := deriveOverallStatus(executions)
|
|
require.Equal(t, "running", status)
|
|
}
|
|
|
|
func TestDeriveOverallStatus_Queued(t *testing.T) {
|
|
executions := []*types.Execution{
|
|
{Status: "succeeded"},
|
|
{Status: "queued"},
|
|
{Status: "succeeded"},
|
|
}
|
|
|
|
status := deriveOverallStatus(executions)
|
|
require.Equal(t, "running", status)
|
|
}
|
|
|
|
func TestBuildLightweightExecutionDAG_Simple(t *testing.T) {
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: "exec-1",
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ReasonerID: "reasoner-1",
|
|
},
|
|
}
|
|
|
|
timeline, status, workflowName, sessionID, actorID, maxDepth := buildLightweightExecutionDAG(executions)
|
|
|
|
require.Len(t, timeline, 1)
|
|
require.Equal(t, "exec-1", timeline[0].ExecutionID)
|
|
require.Equal(t, 0, timeline[0].WorkflowDepth)
|
|
require.Equal(t, "succeeded", status)
|
|
require.Equal(t, "reasoner-1", workflowName)
|
|
require.Nil(t, sessionID)
|
|
require.Nil(t, actorID)
|
|
require.Equal(t, 0, maxDepth)
|
|
}
|
|
|
|
func TestBuildLightweightExecutionDAG_WithParentChild(t *testing.T) {
|
|
parentID := "exec-parent"
|
|
childID := "exec-child"
|
|
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: parentID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ParentExecutionID: nil,
|
|
},
|
|
{
|
|
ExecutionID: childID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(1 * time.Second),
|
|
ParentExecutionID: &parentID,
|
|
},
|
|
}
|
|
|
|
timeline, status, _, _, _, maxDepth := buildLightweightExecutionDAG(executions)
|
|
|
|
require.Len(t, timeline, 2)
|
|
require.Equal(t, parentID, timeline[0].ExecutionID)
|
|
require.Equal(t, 0, timeline[0].WorkflowDepth)
|
|
require.Equal(t, childID, timeline[1].ExecutionID)
|
|
require.Equal(t, 1, timeline[1].WorkflowDepth)
|
|
require.Equal(t, "succeeded", status)
|
|
require.Equal(t, 1, maxDepth)
|
|
}
|
|
|
|
func TestBuildLightweightExecutionDAG_EmptyExecutions(t *testing.T) {
|
|
executions := []*types.Execution{}
|
|
|
|
timeline, status, workflowName, sessionID, actorID, maxDepth := buildLightweightExecutionDAG(executions)
|
|
|
|
require.Empty(t, timeline)
|
|
require.Empty(t, status)
|
|
require.Empty(t, workflowName)
|
|
require.Nil(t, sessionID)
|
|
require.Nil(t, actorID)
|
|
require.Equal(t, 0, maxDepth)
|
|
}
|
|
|
|
func TestBuildExecutionDAG_ComplexHierarchy(t *testing.T) {
|
|
rootID := "exec-root"
|
|
level1aID := "exec-level1a"
|
|
level1bID := "exec-level1b"
|
|
level2aID := "exec-level2a"
|
|
level2bID := "exec-level2b"
|
|
level3ID := "exec-level3"
|
|
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: rootID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ParentExecutionID: nil,
|
|
},
|
|
{
|
|
ExecutionID: level1aID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(1 * time.Second),
|
|
ParentExecutionID: &rootID,
|
|
},
|
|
{
|
|
ExecutionID: level1bID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(2 * time.Second),
|
|
ParentExecutionID: &rootID,
|
|
},
|
|
{
|
|
ExecutionID: level2aID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(3 * time.Second),
|
|
ParentExecutionID: &level1aID,
|
|
},
|
|
{
|
|
ExecutionID: level2bID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(4 * time.Second),
|
|
ParentExecutionID: &level1aID,
|
|
},
|
|
{
|
|
ExecutionID: level3ID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(5 * time.Second),
|
|
ParentExecutionID: &level2aID,
|
|
},
|
|
}
|
|
|
|
dag, timeline, status, _, _, _, maxDepth := buildExecutionDAG(executions)
|
|
|
|
require.NotNil(t, dag)
|
|
require.Equal(t, rootID, dag.ExecutionID)
|
|
require.Len(t, dag.Children, 2) // level1a and level1b
|
|
require.Equal(t, 3, maxDepth)
|
|
|
|
// Verify level1a has 2 children (level2a and level2b)
|
|
level1aNode := dag.Children[0]
|
|
if level1aNode.ExecutionID == level1aID {
|
|
require.Len(t, level1aNode.Children, 2)
|
|
// Verify level2a has 1 child (level3)
|
|
for _, child := range level1aNode.Children {
|
|
if child.ExecutionID == level2aID {
|
|
require.Len(t, child.Children, 1)
|
|
require.Equal(t, level3ID, child.Children[0].ExecutionID)
|
|
}
|
|
}
|
|
}
|
|
|
|
require.Len(t, timeline, 6)
|
|
require.Equal(t, "succeeded", status)
|
|
}
|
|
|
|
func TestBuildExecutionDAG_MixedStatuses(t *testing.T) {
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: "exec-1",
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ParentExecutionID: nil,
|
|
},
|
|
{
|
|
ExecutionID: "exec-2",
|
|
RunID: "run-1",
|
|
Status: "failed",
|
|
StartedAt: time.Now().Add(1 * time.Second),
|
|
ParentExecutionID: nil,
|
|
},
|
|
{
|
|
ExecutionID: "exec-3",
|
|
RunID: "run-1",
|
|
Status: "running",
|
|
StartedAt: time.Now().Add(2 * time.Second),
|
|
ParentExecutionID: nil,
|
|
},
|
|
}
|
|
|
|
_, _, status, _, _, _, _ := buildExecutionDAG(executions)
|
|
|
|
// deriveOverallStatus priority: running > failed > succeeded
|
|
// Running has highest priority as it indicates active workflow
|
|
require.Equal(t, "running", status)
|
|
}
|
|
|
|
func TestBuildExecutionDAG_OrphanedChild(t *testing.T) {
|
|
// Child with parent that doesn't exist in the list
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: "exec-child",
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ParentExecutionID: stringPtr("exec-missing-parent"),
|
|
},
|
|
}
|
|
|
|
dag, timeline, _, _, _, _, _ := buildExecutionDAG(executions)
|
|
|
|
// Should still build DAG with orphaned child as root
|
|
require.NotNil(t, dag)
|
|
require.Equal(t, "exec-child", dag.ExecutionID)
|
|
require.Len(t, timeline, 1)
|
|
}
|
|
|
|
func TestBuildExecutionDAG_CycleDetection(t *testing.T) {
|
|
// Note: The current implementation doesn't explicitly detect cycles,
|
|
// but we test that it doesn't crash with circular references
|
|
parentID := "exec-parent"
|
|
childID := "exec-child"
|
|
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: parentID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ParentExecutionID: &childID, // Circular: parent points to child
|
|
},
|
|
{
|
|
ExecutionID: childID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(1 * time.Second),
|
|
ParentExecutionID: &parentID, // Circular: child points to parent
|
|
},
|
|
}
|
|
|
|
// Should not crash, but behavior may be undefined
|
|
dag, timeline, _, _, _, _, _ := buildExecutionDAG(executions)
|
|
require.NotNil(t, dag)
|
|
require.Len(t, timeline, 2)
|
|
}
|
|
|
|
func TestBuildExecutionDAG_WithSessionAndActor(t *testing.T) {
|
|
sessionID := "session-123"
|
|
actorID := "actor-456"
|
|
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: "exec-1",
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ParentExecutionID: nil,
|
|
SessionID: &sessionID,
|
|
ActorID: &actorID,
|
|
ReasonerID: "reasoner-1",
|
|
},
|
|
}
|
|
|
|
_, _, _, _, sessionIDOut, actorIDOut, _ := buildExecutionDAG(executions)
|
|
|
|
require.NotNil(t, sessionIDOut)
|
|
require.Equal(t, sessionID, *sessionIDOut)
|
|
require.NotNil(t, actorIDOut)
|
|
require.Equal(t, actorID, *actorIDOut)
|
|
}
|
|
|
|
func TestDeriveOverallStatus_PriorityOrder(t *testing.T) {
|
|
// Test status priority: running > failed > succeeded
|
|
tests := []struct {
|
|
name string
|
|
statuses []string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "running has highest priority",
|
|
statuses: []string{"succeeded", "running", "failed"},
|
|
expected: "running",
|
|
},
|
|
{
|
|
name: "failed has priority over succeeded",
|
|
statuses: []string{"succeeded", "failed", "succeeded"},
|
|
expected: "failed",
|
|
},
|
|
{
|
|
name: "all succeeded",
|
|
statuses: []string{"succeeded", "succeeded"},
|
|
expected: "succeeded",
|
|
},
|
|
{
|
|
name: "mixed with queued",
|
|
statuses: []string{"succeeded", "queued", "succeeded"},
|
|
expected: "running",
|
|
},
|
|
{
|
|
name: "mixed with pending",
|
|
statuses: []string{"succeeded", "pending", "succeeded"},
|
|
expected: "running",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
executions := make([]*types.Execution, len(tt.statuses))
|
|
for i, status := range tt.statuses {
|
|
executions[i] = &types.Execution{
|
|
Status: status,
|
|
}
|
|
}
|
|
result := deriveOverallStatus(executions)
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildLightweightExecutionDAG_ComplexHierarchy(t *testing.T) {
|
|
rootID := "exec-root"
|
|
level1ID := "exec-level1"
|
|
level2ID := "exec-level2"
|
|
|
|
executions := []*types.Execution{
|
|
{
|
|
ExecutionID: rootID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now(),
|
|
ParentExecutionID: nil,
|
|
ReasonerID: "reasoner-1",
|
|
},
|
|
{
|
|
ExecutionID: level1ID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(1 * time.Second),
|
|
ParentExecutionID: &rootID,
|
|
},
|
|
{
|
|
ExecutionID: level2ID,
|
|
RunID: "run-1",
|
|
Status: "succeeded",
|
|
StartedAt: time.Now().Add(2 * time.Second),
|
|
ParentExecutionID: &level1ID,
|
|
},
|
|
}
|
|
|
|
timeline, status, workflowName, _, _, maxDepth := buildLightweightExecutionDAG(executions)
|
|
|
|
require.Len(t, timeline, 3)
|
|
require.Equal(t, "succeeded", status)
|
|
require.Equal(t, "reasoner-1", workflowName)
|
|
require.Equal(t, 2, maxDepth)
|
|
|
|
// Verify depth calculation
|
|
require.Equal(t, 0, timeline[0].WorkflowDepth)
|
|
require.Equal(t, 1, timeline[1].WorkflowDepth)
|
|
require.Equal(t, 2, timeline[2].WorkflowDepth)
|
|
}
|
|
|
|
func stringPtr(s string) *string {
|
|
return &s
|
|
}
|
|
|
|
func TestExecutionToDAGNode(t *testing.T) {
|
|
now := time.Now()
|
|
completed := now.Add(1 * time.Hour)
|
|
duration := int64(3600000)
|
|
|
|
exec := &types.Execution{
|
|
ExecutionID: "exec-1",
|
|
RunID: "run-1",
|
|
AgentNodeID: "agent-1",
|
|
ReasonerID: "reasoner-1",
|
|
Status: "succeeded",
|
|
StartedAt: now,
|
|
CompletedAt: &completed,
|
|
DurationMS: &duration,
|
|
ParentExecutionID: nil,
|
|
}
|
|
|
|
node := executionToDAGNode(exec, 2)
|
|
|
|
require.Equal(t, "exec-1", node.ExecutionID)
|
|
require.Equal(t, "run-1", node.WorkflowID)
|
|
require.Equal(t, "agent-1", node.AgentNodeID)
|
|
require.Equal(t, "reasoner-1", node.ReasonerID)
|
|
require.Equal(t, "succeeded", node.Status)
|
|
require.Equal(t, 2, node.WorkflowDepth)
|
|
require.NotNil(t, node.CompletedAt)
|
|
require.Equal(t, duration, *node.DurationMS)
|
|
}
|
|
|
|
func TestExecutionToLightweightNode(t *testing.T) {
|
|
now := time.Now()
|
|
completed := now.Add(1 * time.Hour)
|
|
duration := int64(3600000)
|
|
|
|
exec := &types.Execution{
|
|
ExecutionID: "exec-1",
|
|
RunID: "run-1",
|
|
AgentNodeID: "agent-1",
|
|
ReasonerID: "reasoner-1",
|
|
Status: "succeeded",
|
|
StartedAt: now,
|
|
CompletedAt: &completed,
|
|
DurationMS: &duration,
|
|
ParentExecutionID: nil,
|
|
}
|
|
|
|
node := executionToLightweightNode(exec, 2)
|
|
|
|
require.Equal(t, "exec-1", node.ExecutionID)
|
|
require.Equal(t, "agent-1", node.AgentNodeID)
|
|
require.Equal(t, "reasoner-1", node.ReasonerID)
|
|
require.Equal(t, "succeeded", node.Status)
|
|
require.Equal(t, 2, node.WorkflowDepth)
|
|
require.NotNil(t, node.CompletedAt)
|
|
require.Equal(t, duration, *node.DurationMS)
|
|
}
|
|
|
|
func TestIsLightweightRequest(t *testing.T) {
|
|
// This would require gin.Context, so we'll test the logic conceptually
|
|
// The function checks for query params "mode=lightweight" or "lightweight=true/1"
|
|
}
|
|
|
|
func TestNewExecutionGraphService(t *testing.T) {
|
|
provider, ctx := setupTestStorage(t)
|
|
|
|
svc := newExecutionGraphService(provider)
|
|
require.NotNil(t, svc)
|
|
require.NotNil(t, svc.store)
|
|
|
|
_ = ctx
|
|
}
|
|
|
|
// Helper function from other test files
|
|
func setupTestStorage(t *testing.T) (storage.StorageProvider, context.Context) {
|
|
t.Helper()
|
|
|
|
ctx := context.Background()
|
|
tempDir := t.TempDir()
|
|
cfg := storage.StorageConfig{
|
|
Mode: "local",
|
|
Local: storage.LocalStorageConfig{
|
|
DatabasePath: tempDir + "/test.db",
|
|
KVStorePath: tempDir + "/test.bolt",
|
|
},
|
|
}
|
|
|
|
provider := storage.NewLocalStorage(storage.LocalStorageConfig{})
|
|
err := provider.Initialize(ctx, cfg)
|
|
if err != nil {
|
|
if strings.Contains(strings.ToLower(err.Error()), "fts5") {
|
|
t.Skip("sqlite3 compiled without FTS5; skipping test")
|
|
}
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
_ = provider.Close(ctx)
|
|
})
|
|
|
|
return provider, ctx
|
|
}
|