diff options
| author | Claudomator Agent <agent@claudomator> | 2026-03-14 07:03:55 +0000 |
|---|---|---|
| committer | Claudomator Agent <agent@claudomator> | 2026-03-14 07:03:55 +0000 |
| commit | e5864d941e31a0e4ce59e31bee13e9c7c43909f4 (patch) | |
| tree | 96f73ec7a6345177b82f233ba5b6afb4e7e4e0c7 | |
| parent | 2ee988ccc04c09ceb6de7cdb75c94114e85d01b9 (diff) | |
test
| -rw-r--r-- | internal/api/server_test.go | 20 | ||||
| -rw-r--r-- | internal/executor/executor_test.go | 32 | ||||
| -rw-r--r-- | web/test/cancel-blocked.test.mjs | 68 |
3 files changed, 120 insertions, 0 deletions
diff --git a/internal/api/server_test.go b/internal/api/server_test.go index 2209a69..ec927c0 100644 --- a/internal/api/server_test.go +++ b/internal/api/server_test.go @@ -896,6 +896,26 @@ func TestServer_CancelTask_Queued_TransitionsToCancelled(t *testing.T) { } } +func TestServer_CancelTask_Blocked_TransitionsToCancelled(t *testing.T) { + srv, store := testServer(t) + createTaskWithState(t, store, "cancel-blocked-1", task.StateBlocked) + + req := httptest.NewRequest("POST", "/api/tasks/cancel-blocked-1/cancel", nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("status: want 200, got %d; body: %s", w.Code, w.Body.String()) + } + updated, err := store.GetTask("cancel-blocked-1") + if err != nil { + t.Fatal(err) + } + if updated.State != task.StateCancelled { + t.Errorf("state: want CANCELLED, got %s", updated.State) + } +} + func TestServer_CancelTask_Completed_Returns409(t *testing.T) { srv, store := testServer(t) createTaskWithState(t, store, "cancel-completed-1", task.StateCompleted) diff --git a/internal/executor/executor_test.go b/internal/executor/executor_test.go index 17982f8..f6d0179 100644 --- a/internal/executor/executor_test.go +++ b/internal/executor/executor_test.go @@ -964,6 +964,7 @@ func (m *minimalMockStore) UpdateTaskSummary(taskID, summary string) error func (m *minimalMockStore) AppendTaskInteraction(taskID string, _ task.Interaction) error { return nil } +func (m *minimalMockStore) UpdateTaskAgent(id string, agent task.AgentConfig) error { return nil } func (m *minimalMockStore) lastStateUpdate() (string, task.State, bool) { m.mu.Lock() @@ -1159,3 +1160,34 @@ func TestPool_SpecificAgent_SkipsLoadBalancing(t *testing.T) { t.Errorf("expected claude runner to NOT be called, got %d", claudeRunner.callCount()) } } + +// TestPool_SpecificAgent_PersistsToDB verifies that if a specific agent +// is requested, it is persisted to the database before the task runs. +func TestPool_SpecificAgent_PersistsToDB(t *testing.T) { + store := testStore(t) + geminiRunner := &mockRunner{} + runners := map[string]Runner{ + "gemini": geminiRunner, + } + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) + pool := NewPool(4, runners, store, logger) + + tk := makeTask("persist-gemini") + tk.Agent.Type = "gemini" + store.CreateTask(tk) + + if err := pool.Submit(context.Background(), tk); err != nil { + t.Fatalf("submit: %v", err) + } + + <-pool.Results() + + // Check the task in the database. + reloaded, err := store.GetTask(tk.ID) + if err != nil { + t.Fatalf("get task: %v", err) + } + if reloaded.Agent.Type != "gemini" { + t.Errorf("expected agent type gemini in DB, got %q", reloaded.Agent.Type) + } +} diff --git a/web/test/cancel-blocked.test.mjs b/web/test/cancel-blocked.test.mjs new file mode 100644 index 0000000..0488345 --- /dev/null +++ b/web/test/cancel-blocked.test.mjs @@ -0,0 +1,68 @@ +// cancel-blocked.test.mjs — cancel button visibility for BLOCKED tasks +// +// Run with: node --test web/test/cancel-blocked.test.mjs + +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +// ── Logic under test ────────────────────────────────────────────────────────── +// BLOCKED tasks must show a Cancel button (in addition to question/subtask UI). +// The cancel button should be visible for BLOCKED, RUNNING, and other active states. + +const CANCEL_STATES = new Set(['RUNNING', 'BLOCKED']); + +function showCancelButton(state) { + return CANCEL_STATES.has(state); +} + +function getCancelEndpoint(taskId) { + return `/api/tasks/${taskId}/cancel`; +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +describe('cancel button visibility for BLOCKED tasks', () => { + it('shows Cancel button for BLOCKED', () => { + assert.equal(showCancelButton('BLOCKED'), true); + }); + + it('shows Cancel button for RUNNING', () => { + assert.equal(showCancelButton('RUNNING'), true); + }); + + it('does not show Cancel button for PENDING', () => { + assert.equal(showCancelButton('PENDING'), false); + }); + + it('does not show Cancel button for COMPLETED', () => { + assert.equal(showCancelButton('COMPLETED'), false); + }); + + it('does not show Cancel button for QUEUED', () => { + assert.equal(showCancelButton('QUEUED'), false); + }); + + it('does not show Cancel button for FAILED', () => { + assert.equal(showCancelButton('FAILED'), false); + }); + + it('does not show Cancel button for CANCELLED', () => { + assert.equal(showCancelButton('CANCELLED'), false); + }); + + it('does not show Cancel button for READY', () => { + assert.equal(showCancelButton('READY'), false); + }); +}); + +describe('cancel API endpoint for BLOCKED tasks', () => { + it('uses correct cancel endpoint for a BLOCKED task', () => { + assert.equal(getCancelEndpoint('task-123'), '/api/tasks/task-123/cancel'); + }); + + it('cancel endpoint uses POST method (no-op check — method is always POST)', () => { + // The cancel action always calls POST /api/tasks/{id}/cancel + const method = 'POST'; + assert.equal(method, 'POST'); + }); +}); |
