agent-orchestrator/backend/internal/service/session/stack_test.go

128 lines
4.7 KiB
Go

package session
import (
"testing"
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
)
// live builds an idle, non-terminated session that has already signaled, so the
// derived status is governed purely by its PRs.
func live() domain.SessionRecord {
return statusRec(domain.ActivityIdle, false)
}
func TestBuildStacksMarksBlockedChildren(t *testing.T) {
// #142 (root → main), #143 stacked on #142, #144 stacked on #143.
prs := []domain.PRFacts{
{URL: "p142", SourceBranch: "ao/abc", TargetBranch: "main"},
{URL: "p143", SourceBranch: "ao/abc/auth", TargetBranch: "ao/abc"},
{URL: "p144", SourceBranch: "ao/abc/tests", TargetBranch: "ao/abc/auth"},
}
st := buildStacks(prs)
if st["p142"].Blocked || !st["p142"].BottomOfStack {
t.Fatalf("root should be unblocked bottom-of-stack, got %+v", st["p142"])
}
if !st["p143"].Blocked || st["p143"].BottomOfStack {
t.Fatalf("middle should be blocked, got %+v", st["p143"])
}
if !st["p144"].Blocked {
t.Fatalf("top should be blocked, got %+v", st["p144"])
}
}
func TestBuildStacksMergedParentUnblocksChild(t *testing.T) {
prs := []domain.PRFacts{
{URL: "p142", SourceBranch: "ao/abc", TargetBranch: "main", Merged: true},
{URL: "p143", SourceBranch: "ao/abc/auth", TargetBranch: "ao/abc"},
}
st := buildStacks(prs)
if st["p143"].Blocked {
t.Fatal("child should be unblocked once parent is merged")
}
}
func TestDeriveStatusWorstWinsAcrossIndependentPRs(t *testing.T) {
// Two independent open PRs (both target main): mergeable vs ci_failed.
// CI failure is more urgent, so the session reports ci_failed.
prs := []domain.PRFacts{
{URL: "a", SourceBranch: "ao/a", TargetBranch: "main", Mergeability: domain.MergeMergeable},
{URL: "b", SourceBranch: "ao/b", TargetBranch: "main", CI: domain.CIFailing},
}
if got := deriveStatus(live(), prs, statusNow, true); got != domain.StatusCIFailed {
t.Fatalf("got %q want ci_failed", got)
}
}
func TestDeriveStatusAllMergeableReportsMergeable(t *testing.T) {
prs := []domain.PRFacts{
{URL: "a", SourceBranch: "ao/a", TargetBranch: "main", Mergeability: domain.MergeMergeable},
{URL: "b", SourceBranch: "ao/b", TargetBranch: "main", Mergeability: domain.MergeMergeable},
}
if got := deriveStatus(live(), prs, statusNow, true); got != domain.StatusMergeable {
t.Fatalf("got %q want mergeable", got)
}
}
func TestDeriveStatusStackedChildExemptFromAggregation(t *testing.T) {
// Root mergeable; blocked child is pr_open. Child is exempt, so the session
// reports mergeable rather than being dragged down to pr_open.
prs := []domain.PRFacts{
{URL: "root", SourceBranch: "ao/abc", TargetBranch: "main", Mergeability: domain.MergeMergeable},
{URL: "child", SourceBranch: "ao/abc/x", TargetBranch: "ao/abc"},
}
if got := deriveStatus(live(), prs, statusNow, true); got != domain.StatusMergeable {
t.Fatalf("got %q want mergeable (child exempt)", got)
}
}
func TestDeriveStatusMergedParentOpenChildStaysOnChild(t *testing.T) {
// Parent merged, child now unblocked and review_pending: still alive, status
// follows the open child.
prs := []domain.PRFacts{
{URL: "root", SourceBranch: "ao/abc", TargetBranch: "main", Merged: true},
{URL: "child", SourceBranch: "ao/abc/x", TargetBranch: "main", Review: domain.ReviewRequired},
}
if got := deriveStatus(live(), prs, statusNow, true); got != domain.StatusReviewPending {
t.Fatalf("got %q want review_pending", got)
}
}
func TestDeriveStatusAllMergedReportsMerged(t *testing.T) {
prs := []domain.PRFacts{
{URL: "a", Merged: true},
{URL: "b", Merged: true},
}
if got := deriveStatus(live(), prs, statusNow, true); got != domain.StatusMerged {
t.Fatalf("got %q want merged", got)
}
}
func TestDeriveStatusAllClosedNoneMergedFallsToActivity(t *testing.T) {
prs := []domain.PRFacts{
{URL: "a", Closed: true},
{URL: "b", Closed: true},
}
if got := deriveStatus(statusRec(domain.ActivityActive, false), prs, statusNow, true); got != domain.StatusWorking {
t.Fatalf("got %q want working", got)
}
}
func TestDeriveStatusEmptyPRsUsesActivity(t *testing.T) {
if got := deriveStatus(statusRec(domain.ActivityActive, false), nil, statusNow, true); got != domain.StatusWorking {
t.Fatalf("got %q want working", got)
}
}
func TestDeriveStatusDegenerateAllBlockedStillAggregates(t *testing.T) {
// Two PRs each targeting the other's source branch (no visible root). The
// fallback aggregates across all so the session never goes dark.
prs := []domain.PRFacts{
{URL: "a", SourceBranch: "x", TargetBranch: "y", CI: domain.CIFailing},
{URL: "b", SourceBranch: "y", TargetBranch: "x", Mergeability: domain.MergeMergeable},
}
if got := deriveStatus(live(), prs, statusNow, true); got != domain.StatusCIFailed {
t.Fatalf("got %q want ci_failed (degenerate fallback)", got)
}
}