summaryrefslogtreecommitdiff
path: root/internal/api/server_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api/server_test.go')
-rw-r--r--internal/api/server_test.go123
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)