288 lines
9.0 KiB
Go
288 lines
9.0 KiB
Go
package engine
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
// fakeRunQueue records every QueueRun call.
|
|
type fakeRunQueue struct {
|
|
calls []QueueRunRequest
|
|
err error
|
|
}
|
|
|
|
func (f *fakeRunQueue) QueueRun(_ context.Context, req QueueRunRequest) error {
|
|
f.calls = append(f.calls, req)
|
|
return f.err
|
|
}
|
|
|
|
// fakePrimary returns a fixed agent profile id for any step.
|
|
type fakePrimary struct {
|
|
id string
|
|
err error
|
|
}
|
|
|
|
func (f fakePrimary) PrimaryAgentProfileID(_ context.Context, _ string) (string, error) {
|
|
return f.id, f.err
|
|
}
|
|
|
|
// fakeParticipants returns a static slice for any step.
|
|
type fakeParticipants struct {
|
|
list []ParticipantInfo
|
|
err error
|
|
}
|
|
|
|
func (f fakeParticipants) ListStepParticipants(_ context.Context, _, _ string) ([]ParticipantInfo, error) {
|
|
return f.list, f.err
|
|
}
|
|
|
|
// fakeCEO returns a fixed agent profile id (or empty / err).
|
|
type fakeCEO struct {
|
|
id string
|
|
err error
|
|
}
|
|
|
|
func (f fakeCEO) ResolveCEOAgentProfileID(_ context.Context, _ string) (string, error) {
|
|
return f.id, f.err
|
|
}
|
|
|
|
func newQueueRunInput(target, taskID string) ActionInput {
|
|
return ActionInput{
|
|
Trigger: TriggerOnComment,
|
|
State: MachineState{TaskID: "task-1", SessionID: "sess-1"},
|
|
Step: StepSpec{ID: "step-1"},
|
|
Action: Action{
|
|
Kind: ActionQueueRun,
|
|
QueueRun: &QueueRunAction{
|
|
Target: target,
|
|
TaskID: taskID,
|
|
Reason: "task_comment",
|
|
Payload: map[string]any{"comment_id": "c-1"},
|
|
},
|
|
},
|
|
OperationID: "op-1",
|
|
}
|
|
}
|
|
|
|
func TestQueueRunCallback_TargetPrimary(t *testing.T) {
|
|
q := &fakeRunQueue{}
|
|
cb := QueueRunCallback{Adapter: q, Primary: fakePrimary{id: "agent-primary"}}
|
|
if _, err := cb.Execute(context.Background(), newQueueRunInput("primary", "this")); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(q.calls) != 1 {
|
|
t.Fatalf("expected 1 queue run call, got %d", len(q.calls))
|
|
}
|
|
got := q.calls[0]
|
|
if got.AgentProfileID != "agent-primary" {
|
|
t.Fatalf("agent_profile_id = %q, want agent-primary", got.AgentProfileID)
|
|
}
|
|
if got.TaskID != "task-1" {
|
|
t.Fatalf("task_id = %q, want task-1 (resolved from 'this')", got.TaskID)
|
|
}
|
|
if got.WorkflowStepID != "step-1" {
|
|
t.Fatalf("workflow_step_id = %q, want step-1", got.WorkflowStepID)
|
|
}
|
|
if got.Reason != "task_comment" {
|
|
t.Fatalf("reason = %q, want task_comment", got.Reason)
|
|
}
|
|
if got.IdempotencyKey == "" {
|
|
t.Fatalf("expected non-empty idempotency key when OperationID is set")
|
|
}
|
|
if got.Payload["comment_id"] != "c-1" {
|
|
t.Fatalf("payload not propagated: %#v", got.Payload)
|
|
}
|
|
}
|
|
|
|
func TestQueueRunCallback_TargetParticipantRole(t *testing.T) {
|
|
q := &fakeRunQueue{}
|
|
parts := fakeParticipants{list: []ParticipantInfo{
|
|
{ID: "p1", Role: "reviewer", AgentProfileID: "rev-A"},
|
|
{ID: "p2", Role: "reviewer", AgentProfileID: "rev-B"},
|
|
{ID: "p3", Role: "approver", AgentProfileID: "app-A"},
|
|
}}
|
|
cb := QueueRunCallback{Adapter: q, Participants: parts}
|
|
if _, err := cb.Execute(context.Background(), newQueueRunInput("participant_role:reviewer", "")); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(q.calls) != 2 {
|
|
t.Fatalf("expected 2 queue run calls, got %d", len(q.calls))
|
|
}
|
|
for i, want := range []string{"rev-A", "rev-B"} {
|
|
if q.calls[i].AgentProfileID != want {
|
|
t.Fatalf("call %d agent_profile_id = %q, want %q", i, q.calls[i].AgentProfileID, want)
|
|
}
|
|
if q.calls[i].TaskID != "task-1" {
|
|
t.Fatalf("call %d task_id = %q, want task-1 (resolved from blank)", i, q.calls[i].TaskID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQueueRunCallback_TargetSpecificAgent(t *testing.T) {
|
|
q := &fakeRunQueue{}
|
|
cb := QueueRunCallback{Adapter: q}
|
|
if _, err := cb.Execute(context.Background(), newQueueRunInput("agent_profile_id:some-agent", "task-2")); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(q.calls) != 1 {
|
|
t.Fatalf("expected 1 call, got %d", len(q.calls))
|
|
}
|
|
if q.calls[0].AgentProfileID != "some-agent" {
|
|
t.Fatalf("agent_profile_id = %q", q.calls[0].AgentProfileID)
|
|
}
|
|
if q.calls[0].TaskID != "task-2" {
|
|
t.Fatalf("task_id = %q, want task-2 (literal)", q.calls[0].TaskID)
|
|
}
|
|
}
|
|
|
|
func TestQueueRunCallback_TargetWorkspaceCEO(t *testing.T) {
|
|
q := &fakeRunQueue{}
|
|
cb := QueueRunCallback{Adapter: q, CEOResolver: fakeCEO{id: "ceo-agent"}}
|
|
if _, err := cb.Execute(context.Background(), newQueueRunInput("workspace.ceo_agent", "this")); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(q.calls) != 1 {
|
|
t.Fatalf("expected 1 call, got %d", len(q.calls))
|
|
}
|
|
if q.calls[0].AgentProfileID != "ceo-agent" {
|
|
t.Fatalf("agent_profile_id = %q, want ceo-agent", q.calls[0].AgentProfileID)
|
|
}
|
|
}
|
|
|
|
func TestQueueRunCallback_MissingAdapter_Errors(t *testing.T) {
|
|
cb := QueueRunCallback{} // no Adapter
|
|
_, err := cb.Execute(context.Background(), newQueueRunInput("primary", "this"))
|
|
if err == nil || !errors.Is(err, ErrActionNotYetWired) {
|
|
t.Fatalf("expected ErrActionNotYetWired, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestQueueRunCallback_UnknownTarget_Errors(t *testing.T) {
|
|
cb := QueueRunCallback{Adapter: &fakeRunQueue{}}
|
|
_, err := cb.Execute(context.Background(), newQueueRunInput("nonsense_target", "this"))
|
|
if err == nil {
|
|
t.Fatalf("expected error for unknown target")
|
|
}
|
|
}
|
|
|
|
func TestQueueRunCallback_TargetPrimaryNoResolver_Errors(t *testing.T) {
|
|
cb := QueueRunCallback{Adapter: &fakeRunQueue{}} // missing Primary
|
|
_, err := cb.Execute(context.Background(), newQueueRunInput("primary", "this"))
|
|
if err == nil || !errors.Is(err, ErrActionNotYetWired) {
|
|
t.Fatalf("expected ErrActionNotYetWired, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestQueueRunCallback_ParticipantRoleNoStore_Errors(t *testing.T) {
|
|
cb := QueueRunCallback{Adapter: &fakeRunQueue{}}
|
|
_, err := cb.Execute(context.Background(), newQueueRunInput("participant_role:reviewer", "this"))
|
|
if err == nil || !errors.Is(err, ErrActionNotYetWired) {
|
|
t.Fatalf("expected ErrActionNotYetWired, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestQueueRunCallback_CEONoResolver_Errors(t *testing.T) {
|
|
cb := QueueRunCallback{Adapter: &fakeRunQueue{}}
|
|
_, err := cb.Execute(context.Background(), newQueueRunInput("workspace.ceo_agent", "this"))
|
|
if err == nil || !errors.Is(err, ErrActionNotYetWired) {
|
|
t.Fatalf("expected ErrActionNotYetWired, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestQueueRunCallback_TaskIDResolution(t *testing.T) {
|
|
cases := []struct {
|
|
input string
|
|
want string
|
|
}{
|
|
{"", "task-1"},
|
|
{"this", "task-1"},
|
|
{"task-99", "task-99"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.input, func(t *testing.T) {
|
|
q := &fakeRunQueue{}
|
|
cb := QueueRunCallback{Adapter: q, Primary: fakePrimary{id: "p"}}
|
|
if _, err := cb.Execute(context.Background(), newQueueRunInput("primary", tc.input)); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got := q.calls[0].TaskID; got != tc.want {
|
|
t.Fatalf("TaskID = %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQueueRunForEachParticipantCallback_FansOut(t *testing.T) {
|
|
q := &fakeRunQueue{}
|
|
parts := fakeParticipants{list: []ParticipantInfo{
|
|
{ID: "p1", Role: "reviewer", AgentProfileID: "rev-A"},
|
|
{ID: "p2", Role: "reviewer", AgentProfileID: "rev-B"},
|
|
{ID: "p3", Role: "watcher", AgentProfileID: "watch-A"},
|
|
}}
|
|
cb := QueueRunForEachParticipantCallback{Adapter: q, Participants: parts}
|
|
in := ActionInput{
|
|
Trigger: TriggerOnEnter,
|
|
State: MachineState{TaskID: "task-1"},
|
|
Step: StepSpec{ID: "step-1"},
|
|
OperationID: "op-1",
|
|
Action: Action{
|
|
Kind: ActionQueueRunForEachParticipant,
|
|
QueueRunForEachParticipant: &QueueRunForEachParticipantAction{
|
|
Role: "reviewer",
|
|
Reason: "review_started",
|
|
},
|
|
},
|
|
}
|
|
if _, err := cb.Execute(context.Background(), in); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(q.calls) != 2 {
|
|
t.Fatalf("expected 2 fan-out calls, got %d", len(q.calls))
|
|
}
|
|
for i, want := range []string{"rev-A", "rev-B"} {
|
|
if q.calls[i].AgentProfileID != want {
|
|
t.Fatalf("call %d agent_profile_id = %q, want %q", i, q.calls[i].AgentProfileID, want)
|
|
}
|
|
if q.calls[i].Reason != "review_started" {
|
|
t.Fatalf("call %d reason = %q", i, q.calls[i].Reason)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQueueRunForEachParticipantCallback_NoMatchingRole_NoCalls(t *testing.T) {
|
|
q := &fakeRunQueue{}
|
|
parts := fakeParticipants{list: []ParticipantInfo{
|
|
{ID: "p3", Role: "watcher", AgentProfileID: "watch-A"},
|
|
}}
|
|
cb := QueueRunForEachParticipantCallback{Adapter: q, Participants: parts}
|
|
in := ActionInput{
|
|
Step: StepSpec{ID: "step-1"},
|
|
Action: Action{
|
|
Kind: ActionQueueRunForEachParticipant,
|
|
QueueRunForEachParticipant: &QueueRunForEachParticipantAction{
|
|
Role: "approver",
|
|
},
|
|
},
|
|
}
|
|
if _, err := cb.Execute(context.Background(), in); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(q.calls) != 0 {
|
|
t.Fatalf("expected no calls when no participant matches, got %d", len(q.calls))
|
|
}
|
|
}
|
|
|
|
func TestQueueRunForEachParticipantCallback_MissingDeps_Errors(t *testing.T) {
|
|
cb := QueueRunForEachParticipantCallback{} // no deps
|
|
_, err := cb.Execute(context.Background(), ActionInput{
|
|
Action: Action{
|
|
Kind: ActionQueueRunForEachParticipant,
|
|
QueueRunForEachParticipant: &QueueRunForEachParticipantAction{Role: "reviewer"},
|
|
},
|
|
})
|
|
if err == nil || !errors.Is(err, ErrActionNotYetWired) {
|
|
t.Fatalf("expected ErrActionNotYetWired, got %v", err)
|
|
}
|
|
}
|