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/api/server_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/api/server_test.go')
| -rw-r--r-- | internal/api/server_test.go | 69 |
1 files changed, 69 insertions, 0 deletions
diff --git a/internal/api/server_test.go b/internal/api/server_test.go index 68f3657..5094961 100644 --- a/internal/api/server_test.go +++ b/internal/api/server_test.go @@ -252,6 +252,75 @@ func TestRunTask_CompletedTask_Returns409(t *testing.T) { } } +func TestAcceptTask_ReadyTask_Returns200(t *testing.T) { + srv, store := testServer(t) + createTaskWithState(t, store, "accept-ready", task.StateReady) + + req := httptest.NewRequest("POST", "/api/tasks/accept-ready/accept", nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Errorf("status: want 200, got %d; body: %s", w.Code, w.Body.String()) + } + + got, _ := store.GetTask("accept-ready") + if got.State != task.StateCompleted { + t.Errorf("task state: want COMPLETED, got %v", got.State) + } +} + +func TestAcceptTask_NonReadyTask_Returns409(t *testing.T) { + srv, store := testServer(t) + createTaskWithState(t, store, "accept-pending", task.StatePending) + + req := httptest.NewRequest("POST", "/api/tasks/accept-pending/accept", nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusConflict { + t.Errorf("status: want 409, got %d; body: %s", w.Code, w.Body.String()) + } +} + +func TestRejectTask_ReadyTask_Returns200(t *testing.T) { + srv, store := testServer(t) + createTaskWithState(t, store, "reject-ready", task.StateReady) + + body := bytes.NewBufferString(`{"comment": "needs more detail"}`) + req := httptest.NewRequest("POST", "/api/tasks/reject-ready/reject", body) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Errorf("status: want 200, got %d; body: %s", w.Code, w.Body.String()) + } + + got, _ := store.GetTask("reject-ready") + if got.State != task.StatePending { + t.Errorf("task 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_NonReadyTask_Returns409(t *testing.T) { + srv, store := testServer(t) + createTaskWithState(t, store, "reject-pending", task.StatePending) + + body := bytes.NewBufferString(`{"comment": "comment"}`) + req := httptest.NewRequest("POST", "/api/tasks/reject-pending/reject", body) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusConflict { + t.Errorf("status: want 409, got %d; body: %s", w.Code, w.Body.String()) + } +} + func TestCORS_Headers(t *testing.T) { srv, _ := testServer(t) |
