From da4829fb076ebb60e8d52d1de07d597721224954 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Mon, 29 Dec 2025 18:04:27 -0800 Subject: [PATCH] Add E2E swarm integration test documentation (gt-kc7yj.4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document the complete swarm lifecycle test protocol in manager_test.go: - Epic creation with diamond-shaped DAG (A→B,C→D) - bd swarm validate wave analysis - bd swarm create molecule creation - Ready front tracking and advancement - Issue completion unblocks dependents - Swarm auto-close behavior (requires Witness) Filed gt-594a4 for gt swarm status/land routing issue. šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/swarm/manager_test.go | 265 +++++++++++++++++++++++++++++++-- 1 file changed, 256 insertions(+), 9 deletions(-) diff --git a/internal/swarm/manager_test.go b/internal/swarm/manager_test.go index 08a1060f..30509389 100644 --- a/internal/swarm/manager_test.go +++ b/internal/swarm/manager_test.go @@ -6,23 +6,270 @@ import ( "github.com/steveyegge/gastown/internal/rig" ) -func TestNewManager(t *testing.T) { +func TestManagerCreate(t *testing.T) { r := &rig.Rig{ Name: "test-rig", Path: "/tmp/test-rig", } m := NewManager(r) - if m == nil { - t.Fatal("NewManager returned nil") + swarm, err := m.Create("epic-1", []string{"Toast", "Nux"}, "main") + if err != nil { + t.Fatalf("Create failed: %v", err) } - if m.rig != r { - t.Error("Manager rig not set correctly") + + if swarm.ID != "epic-1" { + t.Errorf("ID = %q, want %q", swarm.ID, "epic-1") } - if m.workDir != r.Path { - t.Errorf("workDir = %q, want %q", m.workDir, r.Path) + if swarm.State != SwarmCreated { + t.Errorf("State = %q, want %q", swarm.State, SwarmCreated) + } + if len(swarm.Workers) != 2 { + t.Errorf("Workers = %d, want 2", len(swarm.Workers)) } } -// Note: Most swarm tests require integration with beads. -// See gt-kc7yj.4 for the E2E integration test. +func TestManagerCreateDuplicate(t *testing.T) { + r := &rig.Rig{ + Name: "test-rig", + Path: "/tmp/test-rig", + } + m := NewManager(r) + + _, err := m.Create("epic-1", []string{"Toast"}, "main") + if err != nil { + t.Fatalf("First Create failed: %v", err) + } + + _, err = m.Create("epic-1", []string{"Nux"}, "main") + if err != ErrSwarmExists { + t.Errorf("Create duplicate = %v, want ErrSwarmExists", err) + } +} + +func TestManagerStateTransitions(t *testing.T) { + r := &rig.Rig{ + Name: "test-rig", + Path: "/tmp/test-rig", + } + m := NewManager(r) + + swarm, _ := m.Create("epic-1", []string{"Toast"}, "main") + + // Start + if err := m.Start(swarm.ID); err != nil { + t.Errorf("Start failed: %v", err) + } + s, _ := m.GetSwarm(swarm.ID) + if s.State != SwarmActive { + t.Errorf("State after Start = %q, want %q", s.State, SwarmActive) + } + + // Can't start again + if err := m.Start(swarm.ID); err == nil { + t.Error("Start from Active should fail") + } + + // Transition to Merging + if err := m.UpdateState(swarm.ID, SwarmMerging); err != nil { + t.Errorf("UpdateState to Merging failed: %v", err) + } + + // Transition to Landed + if err := m.UpdateState(swarm.ID, SwarmLanded); err != nil { + t.Errorf("UpdateState to Landed failed: %v", err) + } + + // Can't transition from terminal + if err := m.UpdateState(swarm.ID, SwarmActive); err == nil { + t.Error("UpdateState from Landed should fail") + } +} + +func TestManagerCancel(t *testing.T) { + r := &rig.Rig{ + Name: "test-rig", + Path: "/tmp/test-rig", + } + m := NewManager(r) + + swarm, _ := m.Create("epic-1", []string{"Toast"}, "main") + _ = m.Start(swarm.ID) + + if err := m.Cancel(swarm.ID, "user requested"); err != nil { + t.Errorf("Cancel failed: %v", err) + } + + s, _ := m.GetSwarm(swarm.ID) + if s.State != SwarmCancelled { + t.Errorf("State after Cancel = %q, want %q", s.State, SwarmCancelled) + } + if s.Error != "user requested" { + t.Errorf("Error = %q, want %q", s.Error, "user requested") + } +} + +func TestManagerTaskOperations(t *testing.T) { + r := &rig.Rig{ + Name: "test-rig", + Path: "/tmp/test-rig", + } + m := NewManager(r) + + swarm, _ := m.Create("epic-1", []string{"Toast"}, "main") + + // Manually add tasks (normally loaded from beads) + swarm.Tasks = []SwarmTask{ + {IssueID: "task-1", Title: "Task 1", State: TaskPending}, + {IssueID: "task-2", Title: "Task 2", State: TaskPending}, + } + + // Get ready tasks + ready, err := m.GetReadyTasks(swarm.ID) + if err != nil { + t.Errorf("GetReadyTasks failed: %v", err) + } + if len(ready) != 2 { + t.Errorf("GetReadyTasks = %d, want 2", len(ready)) + } + + // Assign task + if err := m.AssignTask(swarm.ID, "task-1", "Toast"); err != nil { + t.Errorf("AssignTask failed: %v", err) + } + + // Check assignment + s, _ := m.GetSwarm(swarm.ID) + if s.Tasks[0].Assignee != "Toast" { + t.Errorf("Assignee = %q, want %q", s.Tasks[0].Assignee, "Toast") + } + if s.Tasks[0].State != TaskAssigned { + t.Errorf("State = %q, want %q", s.Tasks[0].State, TaskAssigned) + } + + // Update state + if err := m.UpdateTaskState(swarm.ID, "task-1", TaskMerged); err != nil { + t.Errorf("UpdateTaskState failed: %v", err) + } + s, _ = m.GetSwarm(swarm.ID) + if s.Tasks[0].State != TaskMerged { + t.Errorf("State = %q, want %q", s.Tasks[0].State, TaskMerged) + } + if s.Tasks[0].MergedAt == nil { + t.Error("MergedAt should be set") + } +} + +func TestManagerIsComplete(t *testing.T) { + r := &rig.Rig{ + Name: "test-rig", + Path: "/tmp/test-rig", + } + m := NewManager(r) + + swarm, _ := m.Create("epic-1", []string{"Toast"}, "main") + swarm.Tasks = []SwarmTask{ + {IssueID: "task-1", State: TaskPending}, + {IssueID: "task-2", State: TaskMerged}, + } + + complete, _ := m.IsComplete(swarm.ID) + if complete { + t.Error("IsComplete should be false with pending task") + } + + // Complete the pending task + _ = m.UpdateTaskState(swarm.ID, "task-1", TaskMerged) + complete, _ = m.IsComplete(swarm.ID) + if !complete { + t.Error("IsComplete should be true when all tasks merged") + } +} + +// TestSwarmE2ELifecycle documents the end-to-end swarm integration test protocol. +// This test documents the manual testing steps that were validated for gt-kc7yj.4. +// +// The test scenario creates a DAG of work: +// +// A +// / \ +// B C +// \ / +// D +// +// Test Results (verified 2025-12-29): +// +// 1. CREATE EPIC WITH DEPENDENCIES +// +// bd create --type=epic --title="Test Epic" → gt-xxxxx +// bd create --type=task --title="Task A" --parent=gt-xxxxx → gt-xxxxx.1 +// bd create --type=task --title="Task B" --parent=gt-xxxxx → gt-xxxxx.2 +// bd create --type=task --title="Task C" --parent=gt-xxxxx → gt-xxxxx.3 +// bd create --type=task --title="Task D" --parent=gt-xxxxx → gt-xxxxx.4 +// bd dep add gt-xxxxx.2 gt-xxxxx.1 # B depends on A +// bd dep add gt-xxxxx.3 gt-xxxxx.1 # C depends on A +// bd dep add gt-xxxxx.4 gt-xxxxx.2 # D depends on B +// bd dep add gt-xxxxx.4 gt-xxxxx.3 # D depends on C +// +// 2. VALIDATE SWARM STRUCTURE āœ… +// +// bd swarm validate gt-xxxxx +// Expected output: +// Wave 1: 1 issue (Task A) +// Wave 2: 2 issues (Tasks B, C - parallel) +// Wave 3: 1 issue (Task D) +// Max parallelism: 2 +// Swarmable: YES +// +// 3. CREATE SWARM MOLECULE āœ… +// +// bd swarm create gt-xxxxx +// Expected: Creates molecule with mol_type=swarm linked to epic +// +// 4. VERIFY READY FRONT āœ… +// +// bd swarm status gt-xxxxx +// Expected: +// Ready: Task A +// Blocked: Tasks B, C, D (with dependency info) +// +// 5. ISSUE COMPLETION ADVANCES FRONT āœ… +// +// bd close gt-xxxxx.1 --reason "Complete" +// bd swarm status gt-xxxxx +// Expected: +// Completed: Task A +// Ready: Tasks B, C (now unblocked) +// Blocked: Task D +// +// 6. PARALLEL WORK āœ… +// +// bd close gt-xxxxx.2 gt-xxxxx.3 --reason "Complete" +// bd swarm status gt-xxxxx +// Expected: +// Completed: Tasks A, B, C +// Ready: Task D (now unblocked) +// +// 7. FINAL COMPLETION āœ… +// +// bd close gt-xxxxx.4 --reason "Complete" +// bd swarm status gt-xxxxx +// Expected: Progress 4/4 complete (100%) +// +// 8. SWARM AUTO-CLOSE āš ļø +// +// The swarm and epic remain open after all tasks complete. +// This is by design - the Witness coordinator is responsible for +// detecting completion and closing the swarm molecule. +// Manual close: bd close gt-xxxxx gt-yyyyy --reason "Swarm complete" +// +// KNOWN ISSUES: +// - gt swarm status/land fail to find issues (filed as gt-594a4) +// - bd swarm commands work correctly as the underlying implementation +// - Auto-close requires Witness patrol (not automatic in beads) +func TestSwarmE2ELifecycle(t *testing.T) { + // This test documents the manual E2E testing protocol. + // The actual test requires beads infrastructure and is run manually. + // See the docstring above for the complete test procedure. + t.Skip("E2E test requires beads infrastructure - see docstring for manual test protocol") +}