agent-orchestrator/backend/internal/review/launcher_test.go

145 lines
4.3 KiB
Go

package review
import (
"context"
"strings"
"testing"
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
)
type fakeReviewer struct {
gotInv ports.ReviewInvocation
}
func (f *fakeReviewer) ReviewCommand(_ context.Context, inv ports.ReviewInvocation) (ports.ReviewCommandSpec, error) {
f.gotInv = inv
return ports.ReviewCommandSpec{Argv: []string{"greptile", "review"}}, nil
}
func (f *fakeReviewer) ReviewMessage(_ context.Context, inv ports.ReviewInvocation) (string, error) {
f.gotInv = inv
return "review run " + inv.RunID, nil
}
type fakePreLaunchReviewer struct {
fakeReviewer
prelaunched bool
gotPre ports.ReviewInvocation
}
func (f *fakePreLaunchReviewer) PreLaunch(_ context.Context, inv ports.ReviewInvocation) error {
f.prelaunched = true
f.gotPre = inv
return nil
}
type fakeReviewerResolver struct {
reviewer ports.Reviewer
ok bool
}
func (f fakeReviewerResolver) Reviewer(domain.ReviewerHarness) (ports.Reviewer, bool) {
return f.reviewer, f.ok
}
type fakeRuntime struct {
createCfg ports.RuntimeConfig
sentMsg string
sentTo string
alive bool
}
func (f *fakeRuntime) Create(_ context.Context, cfg ports.RuntimeConfig) (ports.RuntimeHandle, error) {
f.createCfg = cfg
return ports.RuntimeHandle{ID: string(cfg.SessionID)}, nil
}
func (f *fakeRuntime) IsAlive(_ context.Context, _ ports.RuntimeHandle) (bool, error) {
return f.alive, nil
}
func (f *fakeRuntime) SendMessage(_ context.Context, handle ports.RuntimeHandle, msg string) error {
f.sentTo = handle.ID
f.sentMsg = msg
return nil
}
func launchSpec() LaunchSpec {
return LaunchSpec{
RunID: "run-1", WorkerID: "mer-1", Harness: domain.ReviewerClaudeCode,
WorkspacePath: "/ws/mer-1", PRURL: "https://github.com/o/r/pull/1", TargetSHA: "sha1",
}
}
func TestLauncherSpawnReturnsStableHandle(t *testing.T) {
reviewer := &fakeReviewer{}
rt := &fakeRuntime{}
l := NewLauncher(fakeReviewerResolver{reviewer: reviewer, ok: true}, rt)
handle, err := l.Spawn(context.Background(), launchSpec())
if err != nil {
t.Fatalf("Spawn: %v", err)
}
if handle != "review-mer-1" {
t.Fatalf("handle = %q, want review-mer-1", handle)
}
if rt.createCfg.WorkspacePath != "/ws/mer-1" || len(rt.createCfg.Argv) == 0 || rt.createCfg.Argv[0] != "greptile" {
t.Fatalf("create cfg = %+v", rt.createCfg)
}
// No environment is used to carry review identity.
if len(rt.createCfg.Env) != 0 {
t.Fatalf("expected no env, got %v", rt.createCfg.Env)
}
if reviewer.gotInv.RunID != "run-1" || reviewer.gotInv.TargetSHA != "sha1" || reviewer.gotInv.ReviewerID != "review-mer-1" {
t.Fatalf("invocation = %+v", reviewer.gotInv)
}
}
func TestLauncherSpawnRunsReviewerPreLaunch(t *testing.T) {
reviewer := &fakePreLaunchReviewer{}
rt := &fakeRuntime{}
l := NewLauncher(fakeReviewerResolver{reviewer: reviewer, ok: true}, rt)
if _, err := l.Spawn(context.Background(), launchSpec()); err != nil {
t.Fatalf("Spawn: %v", err)
}
if !reviewer.prelaunched {
t.Fatal("expected reviewer pre-launch to run")
}
if reviewer.gotPre.ReviewerID != "review-mer-1" || reviewer.gotPre.WorkspacePath != "/ws/mer-1" {
t.Fatalf("prelaunch invocation = %+v", reviewer.gotPre)
}
if rt.createCfg.WorkspacePath == "" {
t.Fatal("runtime should still be created after pre-launch")
}
}
func TestLauncherNotifySendsMessageToHandle(t *testing.T) {
reviewer := &fakeReviewer{}
rt := &fakeRuntime{}
l := NewLauncher(fakeReviewerResolver{reviewer: reviewer, ok: true}, rt)
if err := l.Notify(context.Background(), "review-mer-1", launchSpec()); err != nil {
t.Fatalf("Notify: %v", err)
}
if rt.sentTo != "review-mer-1" || !strings.Contains(rt.sentMsg, "run-1") {
t.Fatalf("sent to %q msg %q", rt.sentTo, rt.sentMsg)
}
}
func TestLauncherAlive(t *testing.T) {
l := NewLauncher(fakeReviewerResolver{ok: true}, &fakeRuntime{alive: true})
if ok, _ := l.Alive(context.Background(), "review-mer-1"); !ok {
t.Fatal("want alive true")
}
if ok, _ := l.Alive(context.Background(), ""); ok {
t.Fatal("empty handle should not be alive")
}
}
func TestLauncherSpawnNoAdapter(t *testing.T) {
l := NewLauncher(fakeReviewerResolver{ok: false}, &fakeRuntime{})
if _, err := l.Spawn(context.Background(), launchSpec()); err == nil || !strings.Contains(err.Error(), "no reviewer adapter") {
t.Fatalf("err = %v, want no-adapter", err)
}
}