Files
Assistant/tests/agent_execution/graph_test.go
2026-08-22 13:31:39 +03:00

59 lines
2.9 KiB
Go

package agent_execution_test
import (
"testing"
"git.cin.su/m/Assistant/contracts/work"
workdomain "git.cin.su/m/Assistant/modules/work/domain"
)
func TestWorkGraphRejectsCycleAndOrdersReadyNodes(t *testing.T) {
graph := work.WorkGraph{ID: "graph-1", Revision: 1, State: work.GraphAdmitted, Budget: work.GraphBudget{MaxParallel: 2, MaxAttempts: 2}, Nodes: []work.GraphNode{
{ID: "node-b", IdempotencyKey: "key-b", State: work.GraphNodeReady},
{ID: "node-a", IdempotencyKey: "key-a", State: work.GraphNodeReady},
{ID: "node-c", IdempotencyKey: "key-c", State: work.GraphNodeBlocked},
}, Edges: []work.GraphEdge{{From: "node-a", To: "node-c"}}}
ready, err := workdomain.ReadyGraphNodes(graph)
if err != nil || len(ready) != 2 || ready[0] != "node-a" || ready[1] != "node-b" {
t.Fatalf("unexpected ready nodes: %#v %v", ready, err)
}
graph.Edges = append(graph.Edges, work.GraphEdge{From: "node-c", To: "node-a"})
if err := workdomain.ValidateWorkGraph(graph); err != workdomain.ErrGraphCycle {
t.Fatalf("expected cycle rejection, got %v", err)
}
}
func TestWorkGraphKeepsDependentNodeBlocked(t *testing.T) {
graph := work.WorkGraph{ID: "graph-1", Revision: 1, State: work.GraphRunning, Budget: work.GraphBudget{MaxParallel: 1, MaxAttempts: 1}, Nodes: []work.GraphNode{
{ID: "source", IdempotencyKey: "source-key", State: work.GraphNodeRunning},
{ID: "dependent", IdempotencyKey: "dependent-key", State: work.GraphNodeBlocked},
}, Edges: []work.GraphEdge{{From: "source", To: "dependent"}}}
ready, err := workdomain.ReadyGraphNodes(graph)
if err != nil || len(ready) != 0 {
t.Fatalf("dependent node became ready early: %#v %v", ready, err)
}
}
func TestGraphSchedulerEnforcesParallelBudgetAndExplicitConflict(t *testing.T) {
graph := work.WorkGraph{ID: "graph-1", Revision: 1, State: work.GraphDraft, Budget: work.GraphBudget{MaxParallel: 2, MaxAttempts: 2}, Nodes: []work.GraphNode{
{ID: "b", IdempotencyKey: "b-key", State: work.GraphNodeReady},
{ID: "a", IdempotencyKey: "a-key", State: work.GraphNodeReady},
}}
admitted, err := workdomain.AdmitGraph(graph)
if err != nil || admitted.State != work.GraphAdmitted {
t.Fatalf("graph was not admitted: %#v %v", admitted, err)
}
ready, err := workdomain.ScheduleReadyNodes(admitted, 1)
if err != nil || len(ready) != 1 || ready[0] != "a" {
t.Fatalf("scheduler ignored parallel budget/order: %#v %v", ready, err)
}
conflict := work.ConflictSet{ID: "conflict-1", GraphID: "graph-1", GraphRevision: 1, NodeID: "a", CandidateIDs: []string{"candidate-b", "candidate-a"}}
resolved, err := workdomain.ResolveGraphConflict(conflict, "candidate-a")
if err != nil || !resolved.Resolved || resolved.CandidateIDs[0] != "candidate-a" {
t.Fatalf("conflict was not explicitly resolved: %#v %v", resolved, err)
}
if _, err := workdomain.ResolveGraphConflict(conflict, "unknown"); err != workdomain.ErrConflictUnresolved {
t.Fatalf("unknown candidate was accepted: %v", err)
}
}