test(integration): add workflow metadata tests

Amolith created

Test combinations of status, eisenhower, priority, and motivation fields
to determine whether lune needs to warn users about workflow
restrictions.

Covers:
- All metadata fields together
- Individual field isolation
- All eisenhower quadrants
- All kanban status values
- Field clearing and preservation
- Cross-workflow updates

Assisted-by: Claude Opus 4 via Crush

Change summary

integration_test.go | 370 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 370 insertions(+)

Detailed changes

integration_test.go 🔗

@@ -168,3 +168,373 @@ func TestIntegration_NoteRoundTrip(t *testing.T) {
 
 	t.Logf("Updated note: %s", updated.ID)
 }
+
+// TestIntegration_TaskWorkflowMetadata tests how the API handles various combinations
+// of workflow metadata fields. This helps determine whether lune needs to warn users
+// about workflow restrictions.
+func TestIntegration_TaskWorkflowMetadata(t *testing.T) {
+	t.Run("all_metadata_fields", func(t *testing.T) {
+		name := testName("metadata-all")
+		task, err := integrationClient.NewTask(name).
+			InArea(testAreaID).
+			WithStatus(lunatask.StatusNext).
+			WithEisenhower(lunatask.EisenhowerDoNow).
+			Priority(lunatask.PriorityHigh).
+			WithMotivation(lunatask.MotivationMust).
+			WithEstimate(60).
+			Create(ctx())
+		if err != nil {
+			t.Fatalf("Create() error = %v", err)
+		}
+		if task == nil {
+			t.Fatal("Create() returned nil (duplicate?)")
+		}
+
+		t.Cleanup(func() {
+			if _, err := integrationClient.DeleteTask(ctx(), task.ID); err != nil {
+				t.Errorf("DeleteTask() cleanup error = %v", err)
+			}
+		})
+
+		fetched, err := integrationClient.GetTask(ctx(), task.ID)
+		if err != nil {
+			t.Fatalf("GetTask() error = %v", err)
+		}
+
+		logMetadata(t, "all_fields", fetched)
+		if fetched.Status == nil || *fetched.Status != lunatask.StatusNext {
+			t.Errorf("Status = %v, want %v", fetched.Status, lunatask.StatusNext)
+		}
+		if fetched.Eisenhower == nil || *fetched.Eisenhower != lunatask.EisenhowerDoNow {
+			t.Errorf("Eisenhower = %v, want %v", fetched.Eisenhower, lunatask.EisenhowerDoNow)
+		}
+		if fetched.Priority == nil || *fetched.Priority != lunatask.PriorityHigh {
+			t.Errorf("Priority = %v, want %v", fetched.Priority, lunatask.PriorityHigh)
+		}
+		if fetched.Motivation == nil || *fetched.Motivation != lunatask.MotivationMust {
+			t.Errorf("Motivation = %v, want %v", fetched.Motivation, lunatask.MotivationMust)
+		}
+	})
+
+	t.Run("eisenhower_only", func(t *testing.T) {
+		name := testName("metadata-eisenhower")
+		task, err := integrationClient.NewTask(name).
+			InArea(testAreaID).
+			WithEisenhower(lunatask.EisenhowerDoLater).
+			Create(ctx())
+		if err != nil {
+			t.Fatalf("Create() error = %v", err)
+		}
+		if task == nil {
+			t.Fatal("Create() returned nil (duplicate?)")
+		}
+
+		t.Cleanup(func() {
+			if _, err := integrationClient.DeleteTask(ctx(), task.ID); err != nil {
+				t.Errorf("DeleteTask() cleanup error = %v", err)
+			}
+		})
+
+		fetched, err := integrationClient.GetTask(ctx(), task.ID)
+		if err != nil {
+			t.Fatalf("GetTask() error = %v", err)
+		}
+
+		logMetadata(t, "eisenhower_only", fetched)
+		if fetched.Eisenhower == nil || *fetched.Eisenhower != lunatask.EisenhowerDoLater {
+			t.Errorf("Eisenhower = %v, want %v", fetched.Eisenhower, lunatask.EisenhowerDoLater)
+		}
+	})
+
+	t.Run("kanban_status_only", func(t *testing.T) {
+		name := testName("metadata-kanban")
+		task, err := integrationClient.NewTask(name).
+			InArea(testAreaID).
+			WithStatus(lunatask.StatusWaiting).
+			Create(ctx())
+		if err != nil {
+			t.Fatalf("Create() error = %v", err)
+		}
+		if task == nil {
+			t.Fatal("Create() returned nil (duplicate?)")
+		}
+
+		t.Cleanup(func() {
+			if _, err := integrationClient.DeleteTask(ctx(), task.ID); err != nil {
+				t.Errorf("DeleteTask() cleanup error = %v", err)
+			}
+		})
+
+		fetched, err := integrationClient.GetTask(ctx(), task.ID)
+		if err != nil {
+			t.Fatalf("GetTask() error = %v", err)
+		}
+
+		logMetadata(t, "kanban_only", fetched)
+		if fetched.Status == nil || *fetched.Status != lunatask.StatusWaiting {
+			t.Errorf("Status = %v, want %v", fetched.Status, lunatask.StatusWaiting)
+		}
+	})
+
+	t.Run("priority_and_motivation", func(t *testing.T) {
+		name := testName("metadata-pri-mot")
+		task, err := integrationClient.NewTask(name).
+			InArea(testAreaID).
+			Priority(lunatask.PriorityHighest).
+			WithMotivation(lunatask.MotivationWant).
+			Create(ctx())
+		if err != nil {
+			t.Fatalf("Create() error = %v", err)
+		}
+		if task == nil {
+			t.Fatal("Create() returned nil (duplicate?)")
+		}
+
+		t.Cleanup(func() {
+			if _, err := integrationClient.DeleteTask(ctx(), task.ID); err != nil {
+				t.Errorf("DeleteTask() cleanup error = %v", err)
+			}
+		})
+
+		fetched, err := integrationClient.GetTask(ctx(), task.ID)
+		if err != nil {
+			t.Fatalf("GetTask() error = %v", err)
+		}
+
+		logMetadata(t, "priority_motivation", fetched)
+		if fetched.Priority == nil || *fetched.Priority != lunatask.PriorityHighest {
+			t.Errorf("Priority = %v, want %v", fetched.Priority, lunatask.PriorityHighest)
+		}
+		if fetched.Motivation == nil || *fetched.Motivation != lunatask.MotivationWant {
+			t.Errorf("Motivation = %v, want %v", fetched.Motivation, lunatask.MotivationWant)
+		}
+	})
+
+	t.Run("all_eisenhower_quadrants", func(t *testing.T) {
+		quadrants := []struct {
+			name      string
+			quadrant  lunatask.Eisenhower
+			important bool
+			urgent    bool
+		}{
+			{"do_now", lunatask.EisenhowerDoNow, true, true},
+			{"delegate", lunatask.EisenhowerDelegate, false, true},
+			{"do_later", lunatask.EisenhowerDoLater, true, false},
+			{"eliminate", lunatask.EisenhowerEliminate, false, false},
+		}
+
+		for _, q := range quadrants {
+			t.Run(q.name, func(t *testing.T) {
+				name := testName("eisenhower-" + q.name)
+				task, err := integrationClient.NewTask(name).
+					InArea(testAreaID).
+					WithEisenhower(q.quadrant).
+					Create(ctx())
+				if err != nil {
+					t.Fatalf("Create() error = %v", err)
+				}
+				if task == nil {
+					t.Fatal("Create() returned nil (duplicate?)")
+				}
+
+				t.Cleanup(func() {
+					if _, err := integrationClient.DeleteTask(ctx(), task.ID); err != nil {
+						t.Errorf("DeleteTask() cleanup error = %v", err)
+					}
+				})
+
+				fetched, err := integrationClient.GetTask(ctx(), task.ID)
+				if err != nil {
+					t.Fatalf("GetTask() error = %v", err)
+				}
+
+				t.Logf("Quadrant %s: eisenhower=%v", q.name, fetched.Eisenhower)
+				if fetched.Eisenhower == nil || *fetched.Eisenhower != q.quadrant {
+					t.Errorf("Eisenhower = %v, want %v", fetched.Eisenhower, q.quadrant)
+				}
+			})
+		}
+	})
+
+	t.Run("all_status_values", func(t *testing.T) {
+		statuses := []lunatask.TaskStatus{
+			lunatask.StatusLater,
+			lunatask.StatusNext,
+			lunatask.StatusInProgress,
+			lunatask.StatusWaiting,
+		}
+
+		for _, status := range statuses {
+			t.Run(string(status), func(t *testing.T) {
+				name := testName("status-" + string(status))
+				task, err := integrationClient.NewTask(name).
+					InArea(testAreaID).
+					WithStatus(status).
+					Create(ctx())
+				if err != nil {
+					t.Fatalf("Create() error = %v", err)
+				}
+				if task == nil {
+					t.Fatal("Create() returned nil (duplicate?)")
+				}
+
+				t.Cleanup(func() {
+					if _, err := integrationClient.DeleteTask(ctx(), task.ID); err != nil {
+						t.Errorf("DeleteTask() cleanup error = %v", err)
+					}
+				})
+
+				fetched, err := integrationClient.GetTask(ctx(), task.ID)
+				if err != nil {
+					t.Fatalf("GetTask() error = %v", err)
+				}
+
+				t.Logf("Status %s: status=%v", status, fetched.Status)
+				if fetched.Status == nil || *fetched.Status != status {
+					t.Errorf("Status = %v, want %v", fetched.Status, status)
+				}
+			})
+		}
+	})
+
+	t.Run("update_clears_eisenhower", func(t *testing.T) {
+		name := testName("clear-eisenhower")
+		task, err := integrationClient.NewTask(name).
+			InArea(testAreaID).
+			WithEisenhower(lunatask.EisenhowerDoNow).
+			Create(ctx())
+		if err != nil {
+			t.Fatalf("Create() error = %v", err)
+		}
+		if task == nil {
+			t.Fatal("Create() returned nil (duplicate?)")
+		}
+
+		t.Cleanup(func() {
+			if _, err := integrationClient.DeleteTask(ctx(), task.ID); err != nil {
+				t.Errorf("DeleteTask() cleanup error = %v", err)
+			}
+		})
+
+		updated, err := integrationClient.NewTaskUpdate(task.ID).
+			WithEisenhower(lunatask.EisenhowerUncategorized).
+			Update(ctx())
+		if err != nil {
+			t.Fatalf("Update() error = %v", err)
+		}
+
+		t.Logf("After clear: eisenhower=%v", updated.Eisenhower)
+		if updated.Eisenhower == nil || *updated.Eisenhower != lunatask.EisenhowerUncategorized {
+			t.Errorf("Eisenhower = %v, want %v (uncategorized)", updated.Eisenhower, lunatask.EisenhowerUncategorized)
+		}
+	})
+
+	t.Run("update_preserves_unmodified_fields", func(t *testing.T) {
+		name := testName("preserve-fields")
+		task, err := integrationClient.NewTask(name).
+			InArea(testAreaID).
+			WithStatus(lunatask.StatusNext).
+			WithEisenhower(lunatask.EisenhowerDoLater).
+			Priority(lunatask.PriorityHigh).
+			Create(ctx())
+		if err != nil {
+			t.Fatalf("Create() error = %v", err)
+		}
+		if task == nil {
+			t.Fatal("Create() returned nil (duplicate?)")
+		}
+
+		t.Cleanup(func() {
+			if _, err := integrationClient.DeleteTask(ctx(), task.ID); err != nil {
+				t.Errorf("DeleteTask() cleanup error = %v", err)
+			}
+		})
+
+		updated, err := integrationClient.NewTaskUpdate(task.ID).
+			WithMotivation(lunatask.MotivationShould).
+			Update(ctx())
+		if err != nil {
+			t.Fatalf("Update() error = %v", err)
+		}
+
+		logMetadata(t, "after_update", updated)
+
+		if updated.Status == nil || *updated.Status != lunatask.StatusNext {
+			t.Errorf("Status changed: %v, want %v", updated.Status, lunatask.StatusNext)
+		}
+		if updated.Eisenhower == nil || *updated.Eisenhower != lunatask.EisenhowerDoLater {
+			t.Errorf("Eisenhower changed: %v, want %v", updated.Eisenhower, lunatask.EisenhowerDoLater)
+		}
+		if updated.Priority == nil || *updated.Priority != lunatask.PriorityHigh {
+			t.Errorf("Priority changed: %v, want %v", updated.Priority, lunatask.PriorityHigh)
+		}
+		if updated.Motivation == nil || *updated.Motivation != lunatask.MotivationShould {
+			t.Errorf("Motivation = %v, want %v", updated.Motivation, lunatask.MotivationShould)
+		}
+	})
+
+	t.Run("cross_workflow_update", func(t *testing.T) {
+		name := testName("cross-workflow")
+		task, err := integrationClient.NewTask(name).
+			InArea(testAreaID).
+			WithEisenhower(lunatask.EisenhowerDoNow).
+			Create(ctx())
+		if err != nil {
+			t.Fatalf("Create() error = %v", err)
+		}
+		if task == nil {
+			t.Fatal("Create() returned nil (duplicate?)")
+		}
+
+		t.Cleanup(func() {
+			if _, err := integrationClient.DeleteTask(ctx(), task.ID); err != nil {
+				t.Errorf("DeleteTask() cleanup error = %v", err)
+			}
+		})
+
+		logMetadata(t, "after_create", task)
+
+		updated, err := integrationClient.NewTaskUpdate(task.ID).
+			WithStatus(lunatask.StatusWaiting).
+			WithMotivation(lunatask.MotivationMust).
+			Update(ctx())
+		if err != nil {
+			t.Fatalf("Update() error = %v", err)
+		}
+
+		logMetadata(t, "after_update", updated)
+
+		if updated.Eisenhower == nil || *updated.Eisenhower != lunatask.EisenhowerDoNow {
+			t.Errorf("Eisenhower lost after update: %v, want %v", updated.Eisenhower, lunatask.EisenhowerDoNow)
+		}
+		if updated.Status == nil || *updated.Status != lunatask.StatusWaiting {
+			t.Errorf("Status = %v, want %v", updated.Status, lunatask.StatusWaiting)
+		}
+		if updated.Motivation == nil || *updated.Motivation != lunatask.MotivationMust {
+			t.Errorf("Motivation = %v, want %v", updated.Motivation, lunatask.MotivationMust)
+		}
+
+		fetched, err := integrationClient.GetTask(ctx(), task.ID)
+		if err != nil {
+			t.Fatalf("GetTask() error = %v", err)
+		}
+
+		logMetadata(t, "after_fetch", fetched)
+
+		if fetched.Eisenhower == nil || *fetched.Eisenhower != lunatask.EisenhowerDoNow {
+			t.Errorf("Eisenhower lost after fetch: %v, want %v", fetched.Eisenhower, lunatask.EisenhowerDoNow)
+		}
+		if fetched.Status == nil || *fetched.Status != lunatask.StatusWaiting {
+			t.Errorf("Status after fetch = %v, want %v", fetched.Status, lunatask.StatusWaiting)
+		}
+		if fetched.Motivation == nil || *fetched.Motivation != lunatask.MotivationMust {
+			t.Errorf("Motivation after fetch = %v, want %v", fetched.Motivation, lunatask.MotivationMust)
+		}
+	})
+}
+
+func logMetadata(t *testing.T, label string, task *lunatask.Task) {
+	t.Helper()
+	t.Logf("%s: status=%v eisenhower=%v priority=%v motivation=%v",
+		label, task.Status, task.Eisenhower, task.Priority, task.Motivation)
+}