From 704d007a26cac804148a51d35e129beaea382fb0 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Tue, 3 Mar 2026 21:15:01 +0000 Subject: Add subtask support: parent_task_id, ListSubtasks, UpdateTask - Task struct gains ParentTaskID field - DB schema adds parent_task_id column (additive migration) - DB.ListSubtasks fetches children of a parent task - DB.UpdateTask allows partial field updates (name, description, state, etc.) - Templates table added to initial schema Co-Authored-By: Claude Sonnet 4.6 --- internal/storage/db_test.go | 89 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) (limited to 'internal/storage/db_test.go') diff --git a/internal/storage/db_test.go b/internal/storage/db_test.go index db6c8ad..7eb81d2 100644 --- a/internal/storage/db_test.go +++ b/internal/storage/db_test.go @@ -251,6 +251,95 @@ func TestListExecutions(t *testing.T) { } } +func TestDB_UpdateTask(t *testing.T) { + t.Run("happy path", func(t *testing.T) { + db := testDB(t) + now := time.Now().UTC().Truncate(time.Second) + + tk := &task.Task{ + ID: "upd-1", + Name: "Original Name", + Description: "original desc", + Claude: task.ClaudeConfig{Model: "sonnet", Instructions: "original"}, + Priority: task.PriorityNormal, + Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "linear"}, + Tags: []string{"old"}, + DependsOn: []string{}, + State: task.StateCompleted, + CreatedAt: now, + UpdatedAt: now, + } + tk.Timeout.Duration = 5 * time.Minute + if err := db.CreateTask(tk); err != nil { + t.Fatalf("creating task: %v", err) + } + + u := TaskUpdate{ + Name: "Updated Name", + Description: "updated desc", + Config: task.ClaudeConfig{Model: "opus", Instructions: "updated"}, + Priority: task.PriorityHigh, + TimeoutNS: int64(15 * time.Minute), + Retry: task.RetryConfig{MaxAttempts: 3, Backoff: "exponential"}, + Tags: []string{"new", "tag"}, + DependsOn: []string{"dep-1"}, + } + if err := db.UpdateTask("upd-1", u); err != nil { + t.Fatalf("UpdateTask: %v", err) + } + + got, err := db.GetTask("upd-1") + if err != nil { + t.Fatalf("GetTask: %v", err) + } + if got.Name != "Updated Name" { + t.Errorf("name: want 'Updated Name', got %q", got.Name) + } + if got.Description != "updated desc" { + t.Errorf("description: want 'updated desc', got %q", got.Description) + } + if got.Claude.Model != "opus" { + t.Errorf("model: want 'opus', got %q", got.Claude.Model) + } + if got.Priority != task.PriorityHigh { + t.Errorf("priority: want 'high', got %q", got.Priority) + } + if got.Timeout.Duration != 15*time.Minute { + t.Errorf("timeout: want 15m, got %v", got.Timeout.Duration) + } + if got.Retry.MaxAttempts != 3 { + t.Errorf("retry.max_attempts: want 3, got %d", got.Retry.MaxAttempts) + } + if got.Retry.Backoff != "exponential" { + t.Errorf("retry.backoff: want 'exponential', got %q", got.Retry.Backoff) + } + if len(got.Tags) != 2 || got.Tags[0] != "new" || got.Tags[1] != "tag" { + t.Errorf("tags: want [new tag], got %v", got.Tags) + } + if len(got.DependsOn) != 1 || got.DependsOn[0] != "dep-1" { + t.Errorf("depends_on: want [dep-1], got %v", got.DependsOn) + } + if got.State != task.StatePending { + t.Errorf("state: want PENDING after update, got %v", got.State) + } + // id and created_at must be unchanged + if got.ID != "upd-1" { + t.Errorf("id changed: got %q", got.ID) + } + if !got.CreatedAt.Equal(now) { + t.Errorf("created_at changed: want %v, got %v", now, got.CreatedAt) + } + }) + + t.Run("not found", func(t *testing.T) { + db := testDB(t) + err := db.UpdateTask("nonexistent", TaskUpdate{Name: "x"}) + if err == nil { + t.Fatal("expected error for nonexistent task, got nil") + } + }) +} + func TestUpdateExecution(t *testing.T) { db := testDB(t) now := time.Now().UTC() -- cgit v1.2.3