97 lines
4.8 KiB
Go
97 lines
4.8 KiB
Go
package agent_execution_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.cin.su/m/Assistant/contracts/work"
|
|
"git.cin.su/m/Assistant/modules/work/application"
|
|
)
|
|
|
|
type graphStore struct {
|
|
graphs map[string]work.WorkGraph
|
|
queued []string
|
|
intents []string
|
|
}
|
|
|
|
func (s *graphStore) SaveWorkGraph(_ context.Context, graph work.WorkGraph) error {
|
|
if s.graphs == nil {
|
|
s.graphs = map[string]work.WorkGraph{}
|
|
}
|
|
s.graphs[graph.ID+":"+itoa(graph.Revision)] = graph
|
|
return nil
|
|
}
|
|
func (s *graphStore) LoadWorkGraph(_ context.Context, id string, revision int64) (work.WorkGraph, error) {
|
|
return s.graphs[id+":"+itoa(revision)], nil
|
|
}
|
|
func (s *graphStore) SaveDispatchIntent(_ context.Context, intent work.AgentDispatchIntent) error {
|
|
s.intents = append(s.intents, intent.DispatchID)
|
|
return nil
|
|
}
|
|
func (s *graphStore) EnqueueDispatch(_ context.Context, item work.AgentDispatchQueueItem) error {
|
|
s.queued = append(s.queued, item.DispatchID)
|
|
return nil
|
|
}
|
|
|
|
func itoa(value int64) string {
|
|
return strconv.FormatInt(value, 10)
|
|
}
|
|
|
|
func TestGraphRuntimeSchedulesFanOutAndCompletesFanIn(t *testing.T) {
|
|
store := &graphStore{}
|
|
graph, ready, err := application.AdmitAndSchedule(context.Background(), store, application.GraphScheduleRequest{Graph: work.WorkGraph{ID: "g", Revision: 1, Budget: work.GraphBudget{MaxParallel: 2, MaxAttempts: 1}, Nodes: []work.GraphNode{{ID: "a", DispatchID: "d-a", IdempotencyKey: "a", State: work.GraphNodeReady}, {ID: "b", DispatchID: "d-b", IdempotencyKey: "b", State: work.GraphNodeReady}, {ID: "join", DispatchID: "d-join", IdempotencyKey: "join", State: work.GraphNodeBlocked}}, Edges: []work.GraphEdge{{From: "a", To: "join"}, {From: "b", To: "join"}}}, Intents: []work.AgentDispatchIntent{{DispatchID: "d-a"}, {DispatchID: "d-b"}, {DispatchID: "d-join"}}, Now: 1})
|
|
if err != nil || len(ready) != 2 || len(store.queued) != 2 {
|
|
t.Fatalf("graph=%+v ready=%v queued=%v err=%v", graph, ready, store.queued, err)
|
|
}
|
|
if _, err := application.CompleteGraphNode(context.Background(), store, "g", 2, "a", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := application.CompleteGraphNode(context.Background(), store, "g", 3, "b", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(store.queued) != 3 || store.queued[2] != "d-join" {
|
|
t.Fatalf("fan-in dispatch was not enqueued: %v", store.queued)
|
|
}
|
|
completed, err := application.CompleteGraphNode(context.Background(), store, "g", 4, "join", true)
|
|
if err != nil || completed.State != work.GraphCompleted {
|
|
t.Fatalf("completed=%+v err=%v", completed, err)
|
|
}
|
|
}
|
|
|
|
type graphResultStoreStub struct {
|
|
conflict work.ConflictSet
|
|
count int
|
|
}
|
|
|
|
func (s *graphResultStoreStub) SaveGraphSynthesis(_ context.Context, conflict work.ConflictSet, candidates []work.ResultCandidate, _ work.GraphSynthesisCandidate) error {
|
|
s.conflict = conflict
|
|
s.count = len(candidates)
|
|
return nil
|
|
}
|
|
|
|
func TestSynthesizeGraphPersistsVerifiedDissent(t *testing.T) {
|
|
store := &graphResultStoreStub{}
|
|
conflict, synthesis, err := application.SynthesizeGraph(context.Background(), store, application.GraphSynthesisRequest{
|
|
Conflict: work.ConflictSet{ID: "conflict-1", GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", CandidateIDs: []string{"candidate-a", "candidate-b"}},
|
|
Candidates: []work.ResultCandidate{{ID: "candidate-a", GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", AuthorRunID: "producer-a", EvidenceSummary: "a", Summary: "a"}, {ID: "candidate-b", GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", AuthorRunID: "producer-b", EvidenceSummary: "b", Summary: "b"}},
|
|
SelectedCandidate: "candidate-a", VerifierRunID: "verifier-1", VerifierIdentity: "independent", EvidenceRefs: []work.EvidenceRef{"tests:1"}, Summary: "verified", Verified: true,
|
|
})
|
|
if err != nil || !conflict.Resolved || synthesis.State != work.GraphSynthesisVerified || store.count != 2 || !store.conflict.Resolved {
|
|
t.Fatalf("unexpected runtime synthesis: conflict=%+v synthesis=%+v stored=%+v err=%v", conflict, synthesis, store.conflict, err)
|
|
}
|
|
}
|
|
|
|
func TestGraphSynthesisJSONUsesStableSnakeCaseContract(t *testing.T) {
|
|
payload, err := json.Marshal(application.GraphSynthesisRequest{Conflict: work.ConflictSet{ID: "c", GraphID: "g", GraphRevision: 1, NodeID: "n", CandidateIDs: []string{"a", "b"}}, SelectedCandidate: "a", VerifierRunID: "v", VerifierIdentity: "independent", EvidenceRefs: []work.EvidenceRef{"e"}, Summary: "s", Verified: true})
|
|
if err != nil || !strings.Contains(string(payload), `"graph_id":"g"`) || !strings.Contains(string(payload), `"selected_candidate_id":"a"`) {
|
|
t.Fatalf("unexpected synthesis JSON: %s err=%v", payload, err)
|
|
}
|
|
var decoded application.GraphSynthesisRequest
|
|
if err := json.Unmarshal(payload, &decoded); err != nil || decoded.Conflict.GraphID != "g" || decoded.SelectedCandidate != "a" || !decoded.Verified {
|
|
t.Fatalf("snake_case request did not round-trip: %+v err=%v", decoded, err)
|
|
}
|
|
}
|