agent-orchestrator/backend/internal/observe/reaper/reaper_test.go

103 lines
2.9 KiB
Go

package reaper
import (
"context"
"errors"
"io"
"log/slog"
"testing"
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
)
var ctx = context.Background()
type fakeLCM struct {
observed map[domain.SessionID]ports.RuntimeFacts
}
func (l *fakeLCM) ApplyRuntimeObservation(_ context.Context, id domain.SessionID, f ports.RuntimeFacts) error {
if l.observed == nil {
l.observed = map[domain.SessionID]ports.RuntimeFacts{}
}
l.observed[id] = f
return nil
}
type fakeSessions struct{ rows []domain.SessionRecord }
func (s fakeSessions) ListAllSessions(context.Context) ([]domain.SessionRecord, error) {
return s.rows, nil
}
type fakeRuntime struct {
alive bool
err error
}
func (r fakeRuntime) IsAlive(context.Context, ports.RuntimeHandle) (bool, error) {
return r.alive, r.err
}
func probableSession(id domain.SessionID) domain.SessionRecord {
return domain.SessionRecord{
ID: id,
Activity: domain.Activity{State: domain.ActivityActive},
Metadata: domain.SessionMetadata{RuntimeHandleID: "h1"},
}
}
func quietLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) }
func newReaper(lcm *fakeLCM, sessions fakeSessions, rt fakeRuntime) *Reaper {
return New(lcm, sessions, rt, Config{Logger: quietLogger()})
}
func TestTick_ReportsAliveProbe(t *testing.T) {
lcm := &fakeLCM{}
sessions := fakeSessions{rows: []domain.SessionRecord{probableSession("mer-1")}}
if err := newReaper(lcm, sessions, fakeRuntime{alive: true}).Tick(ctx); err != nil {
t.Fatal(err)
}
if lcm.observed["mer-1"].Probe != ports.ProbeAlive {
t.Fatalf("want alive probe, got %q", lcm.observed["mer-1"].Probe)
}
}
func TestTick_ReportsProbeErrorAsFailed(t *testing.T) {
lcm := &fakeLCM{}
sessions := fakeSessions{rows: []domain.SessionRecord{probableSession("mer-1")}}
if err := newReaper(lcm, sessions, fakeRuntime{err: errors.New("tmux gone")}).Tick(ctx); err != nil {
t.Fatal(err)
}
if lcm.observed["mer-1"].Probe != ports.ProbeFailed {
t.Fatalf("probe error must be reported as failed, got %q", lcm.observed["mer-1"].Probe)
}
}
func TestTick_SkipsTerminatedSession(t *testing.T) {
lcm := &fakeLCM{}
dead := probableSession("mer-1")
dead.IsTerminated = true
sessions := fakeSessions{rows: []domain.SessionRecord{dead}}
if err := newReaper(lcm, sessions, fakeRuntime{alive: true}).Tick(ctx); err != nil {
t.Fatal(err)
}
if _, probed := lcm.observed["mer-1"]; probed {
t.Fatal("terminated sessions must not be probed")
}
}
func TestTick_SkipsSessionWithoutHandle(t *testing.T) {
lcm := &fakeLCM{}
noHandle := domain.SessionRecord{ID: "mer-1"} // no runtime metadata
sessions := fakeSessions{rows: []domain.SessionRecord{noHandle}}
if err := newReaper(lcm, sessions, fakeRuntime{alive: true}).Tick(ctx); err != nil {
t.Fatal(err)
}
if _, probed := lcm.observed["mer-1"]; probed {
t.Fatal("a session without a runtime handle must be skipped")
}
}