@@ -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)
+}