Add E2E swarm integration test documentation (gt-kc7yj.4)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user