diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-04 21:25:34 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-04 21:25:34 +0000 |
| commit | 6511d6e0ff139495413c7848a9b4aabb9d9ee4e2 (patch) | |
| tree | 95bd6a0efc0ace206a5716da62a5956491cb46e7 /internal/storage/db_test.go | |
| parent | 3962597950421e422b6e1ce57764550f5600ded6 (diff) | |
Add READY state for human-in-the-loop verification
Top-level tasks now land in READY after successful execution instead of
going directly to COMPLETED. Subtasks (with parent_task_id) skip the gate
and remain COMPLETED. Users accept or reject via new API endpoints:
POST /api/tasks/{id}/accept → READY → COMPLETED
POST /api/tasks/{id}/reject → READY → PENDING (with rejection_comment)
- task: add StateReady, RejectionComment field, update ValidTransition
- storage: migrate rejection_comment column, add RejectTask method
- executor: route top-level vs subtask to READY vs COMPLETED
- api: /accept and /reject handlers with 409 on invalid state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/storage/db_test.go')
| -rw-r--r-- | internal/storage/db_test.go | 49 |
1 files changed, 49 insertions, 0 deletions
diff --git a/internal/storage/db_test.go b/internal/storage/db_test.go index 7eb81d2..4f9069a 100644 --- a/internal/storage/db_test.go +++ b/internal/storage/db_test.go @@ -340,6 +340,43 @@ func TestDB_UpdateTask(t *testing.T) { }) } +func TestRejectTask(t *testing.T) { + db := testDB(t) + now := time.Now().UTC() + tk := &task.Task{ + ID: "reject-1", Name: "R", Claude: task.ClaudeConfig{Instructions: "x"}, + Priority: task.PriorityNormal, Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "linear"}, + Tags: []string{}, DependsOn: []string{}, + State: task.StateReady, CreatedAt: now, UpdatedAt: now, + } + if err := db.CreateTask(tk); err != nil { + t.Fatal(err) + } + + if err := db.RejectTask("reject-1", "needs more detail"); err != nil { + t.Fatalf("RejectTask: %v", err) + } + + got, err := db.GetTask("reject-1") + if err != nil { + t.Fatalf("GetTask: %v", err) + } + if got.State != task.StatePending { + t.Errorf("state: want PENDING, got %v", got.State) + } + if got.RejectionComment != "needs more detail" { + t.Errorf("rejection_comment: want 'needs more detail', got %q", got.RejectionComment) + } +} + +func TestRejectTask_NotFound(t *testing.T) { + db := testDB(t) + err := db.RejectTask("nonexistent", "comment") + if err == nil { + t.Fatal("expected error for nonexistent task") + } +} + func TestUpdateExecution(t *testing.T) { db := testDB(t) now := time.Now().UTC() @@ -360,6 +397,9 @@ func TestUpdateExecution(t *testing.T) { exec.ExitCode = 1 exec.ErrorMsg = "something broke" exec.EndTime = now.Add(2 * time.Minute) + exec.StdoutPath = "/tmp/exec/stdout.log" + exec.StderrPath = "/tmp/exec/stderr.log" + exec.ArtifactDir = "/tmp/exec" if err := db.UpdateExecution(exec); err != nil { t.Fatal(err) } @@ -371,4 +411,13 @@ func TestUpdateExecution(t *testing.T) { if got.ErrorMsg != "something broke" { t.Errorf("error: want 'something broke', got %q", got.ErrorMsg) } + if got.StdoutPath != "/tmp/exec/stdout.log" { + t.Errorf("stdout_path: want /tmp/exec/stdout.log, got %q", got.StdoutPath) + } + if got.StderrPath != "/tmp/exec/stderr.log" { + t.Errorf("stderr_path: want /tmp/exec/stderr.log, got %q", got.StderrPath) + } + if got.ArtifactDir != "/tmp/exec" { + t.Errorf("artifact_dir: want /tmp/exec, got %q", got.ArtifactDir) + } } |
