diff options
Diffstat (limited to 'internal/api/server_test.go')
| -rw-r--r-- | internal/api/server_test.go | 123 |
1 files changed, 119 insertions, 4 deletions
diff --git a/internal/api/server_test.go b/internal/api/server_test.go index afdc9d2..c90e3b3 100644 --- a/internal/api/server_test.go +++ b/internal/api/server_test.go @@ -894,10 +894,11 @@ func TestServer_CancelTask_Completed_Returns409(t *testing.T) { // mockQuestionStore implements questionStore for testing handleAnswerQuestion. type mockQuestionStore struct { - getTaskFn func(id string) (*task.Task, error) - getLatestExecutionFn func(taskID string) (*storage.Execution, error) - updateTaskQuestionFn func(taskID, questionJSON string) error - updateTaskStateFn func(id string, newState task.State) error + getTaskFn func(id string) (*task.Task, error) + getLatestExecutionFn func(taskID string) (*storage.Execution, error) + updateTaskQuestionFn func(taskID, questionJSON string) error + updateTaskStateFn func(id string, newState task.State) error + appendInteractionFn func(taskID string, interaction task.Interaction) error } func (m *mockQuestionStore) GetTask(id string) (*task.Task, error) { @@ -912,6 +913,12 @@ func (m *mockQuestionStore) UpdateTaskQuestion(taskID, questionJSON string) erro func (m *mockQuestionStore) UpdateTaskState(id string, newState task.State) error { return m.updateTaskStateFn(id, newState) } +func (m *mockQuestionStore) AppendTaskInteraction(taskID string, interaction task.Interaction) error { + if m.appendInteractionFn != nil { + return m.appendInteractionFn(taskID, interaction) + } + return nil +} func TestServer_AnswerQuestion_UpdateQuestionFails_Returns500(t *testing.T) { srv, _ := testServer(t) @@ -1178,6 +1185,114 @@ func TestResumeTimedOut_ResponseShape(t *testing.T) { } } +func TestResumeInterrupted_Cancelled_Success_Returns202(t *testing.T) { + srv, store := testServer(t) + createTaskWithState(t, store, "resume-cancelled-1", task.StateCancelled) + + exec := &storage.Execution{ + ID: "exec-cancelled-1", + TaskID: "resume-cancelled-1", + SessionID: "550e8400-e29b-41d4-a716-446655440030", + Status: "CANCELLED", + } + if err := store.CreateExecution(exec); err != nil { + t.Fatalf("create execution: %v", err) + } + + req := httptest.NewRequest("POST", "/api/tasks/resume-cancelled-1/resume", nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusAccepted { + t.Errorf("status: want 202, got %d; body: %s", w.Code, w.Body.String()) + } + got, _ := store.GetTask("resume-cancelled-1") + if got.State != task.StateQueued && got.State != task.StateRunning && got.State != task.StateReady { + t.Errorf("task state: want QUEUED/RUNNING/READY after resume, got %v", got.State) + } +} + +func TestResumeInterrupted_Failed_Success_Returns202(t *testing.T) { + srv, store := testServer(t) + createTaskWithState(t, store, "resume-failed-1", task.StateFailed) + + exec := &storage.Execution{ + ID: "exec-failed-1", + TaskID: "resume-failed-1", + SessionID: "550e8400-e29b-41d4-a716-446655440031", + Status: "FAILED", + } + if err := store.CreateExecution(exec); err != nil { + t.Fatalf("create execution: %v", err) + } + + req := httptest.NewRequest("POST", "/api/tasks/resume-failed-1/resume", nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusAccepted { + t.Errorf("status: want 202, got %d; body: %s", w.Code, w.Body.String()) + } + got, _ := store.GetTask("resume-failed-1") + if got.State != task.StateQueued && got.State != task.StateRunning && got.State != task.StateReady { + t.Errorf("task state: want QUEUED/RUNNING/READY after resume, got %v", got.State) + } +} + +func TestResumeInterrupted_BudgetExceeded_Success_Returns202(t *testing.T) { + srv, store := testServer(t) + createTaskWithState(t, store, "resume-budget-1", task.StateBudgetExceeded) + + exec := &storage.Execution{ + ID: "exec-budget-1", + TaskID: "resume-budget-1", + SessionID: "550e8400-e29b-41d4-a716-446655440032", + Status: "BUDGET_EXCEEDED", + } + if err := store.CreateExecution(exec); err != nil { + t.Fatalf("create execution: %v", err) + } + + req := httptest.NewRequest("POST", "/api/tasks/resume-budget-1/resume", nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusAccepted { + t.Errorf("status: want 202, got %d; body: %s", w.Code, w.Body.String()) + } + got, _ := store.GetTask("resume-budget-1") + if got.State != task.StateQueued && got.State != task.StateRunning && got.State != task.StateReady { + t.Errorf("task state: want QUEUED/RUNNING/READY after resume, got %v", got.State) + } +} + +func TestResumeInterrupted_NoSession_Returns500(t *testing.T) { + srv, store := testServer(t) + createTaskWithState(t, store, "resume-nosess-1", task.StateCancelled) + + // No execution — no session ID available. + req := httptest.NewRequest("POST", "/api/tasks/resume-nosess-1/resume", nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusInternalServerError { + t.Errorf("status: want 500, got %d; body: %s", w.Code, w.Body.String()) + } +} + +func TestResumeInterrupted_WrongState_Returns409(t *testing.T) { + srv, store := testServer(t) + createTaskWithState(t, store, "resume-wrong-1", task.StatePending) + + req := httptest.NewRequest("POST", "/api/tasks/resume-wrong-1/resume", 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 TestRateLimit_ValidateRejectsExcess(t *testing.T) { srv, _ := testServer(t) srv.elaborateLimiter = newIPRateLimiter(0, 1) |
