package server import ( "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "testing" ) func TestAPIGetTasks(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodGet, "/api/tasks", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var tasks []taskResponse if err := json.Unmarshal(w.Body.Bytes(), &tasks); err != nil { t.Fatalf("invalid JSON: %v", err) } if len(tasks) != 2 { t.Fatalf("expected 2 tasks, got %d", len(tasks)) } // Check that tasks have correct statuses statuses := map[string]string{} for _, task := range tasks { statuses[task.ID] = task.Status } if statuses["T01"] != "done" { t.Errorf("expected T01 status done, got %s", statuses["T01"]) } if statuses["T08"] != "backlog" { t.Errorf("expected T08 status backlog, got %s", statuses["T08"]) } } func TestAPIGetTask(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodGet, "/api/task/T01", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d", w.Code) } var detail taskDetailResponse if err := json.Unmarshal(w.Body.Bytes(), &detail); err != nil { t.Fatalf("invalid JSON: %v", err) } if detail.ID != "T01" { t.Errorf("expected T01, got %s", detail.ID) } if detail.Content == "" { t.Error("expected non-empty content") } } func TestAPIGetTask_NotFound(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodGet, "/api/task/T99", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusNotFound { t.Fatalf("expected 404, got %d", w.Code) } } func TestAPIMoveTask(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodPost, "/api/task/T08/move?to=ready", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } // Verify file was moved if _, err := os.Stat(filepath.Join(srv.Config.TasksDir, "ready", "T08.md")); err != nil { t.Error("expected T08.md in ready/") } if _, err := os.Stat(filepath.Join(srv.Config.TasksDir, "backlog", "T08.md")); !os.IsNotExist(err) { t.Error("expected T08.md removed from backlog/") } } func TestAPIMoveTask_NotFound(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodPost, "/api/task/T99/move?to=ready", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusNotFound { t.Fatalf("expected 404, got %d", w.Code) } } func TestAPIMoveTask_InvalidFolder(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodPost, "/api/task/T08/move?to=invalid", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d", w.Code) } } func TestAPIMoveTask_ForbiddenToActive(t *testing.T) { srv := setupTestServer(t) // Put T08 in ready first os.Rename( filepath.Join(srv.Config.TasksDir, "backlog", "T08.md"), filepath.Join(srv.Config.TasksDir, "ready", "T08.md"), ) // Try to move ready → active (agent-only) req := httptest.NewRequest(http.MethodPost, "/api/task/T08/move?to=active", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusForbidden { t.Fatalf("expected 403 for ready→active, got %d: %s", w.Code, w.Body.String()) } } func TestAPIMoveTask_ForbiddenActiveToReview(t *testing.T) { srv := setupTestServer(t) // Put T08 in active os.Rename( filepath.Join(srv.Config.TasksDir, "backlog", "T08.md"), filepath.Join(srv.Config.TasksDir, "active", "T08.md"), ) // Try to move active → review (agent-only) req := httptest.NewRequest(http.MethodPost, "/api/task/T08/move?to=review", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusForbidden { t.Fatalf("expected 403 for active→review, got %d: %s", w.Code, w.Body.String()) } } func TestAPIMoveTask_AllowedBacklogToReady(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodPost, "/api/task/T08/move?to=ready", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200 for backlog→ready, got %d: %s", w.Code, w.Body.String()) } } func TestAPIMoveTask_AllowedReviewToDone(t *testing.T) { srv := setupTestServer(t) // Put T08 in review os.Rename( filepath.Join(srv.Config.TasksDir, "backlog", "T08.md"), filepath.Join(srv.Config.TasksDir, "review", "T08.md"), ) req := httptest.NewRequest(http.MethodPost, "/api/task/T08/move?to=done", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200 for review→done, got %d: %s", w.Code, w.Body.String()) } } func TestAPIMoveTask_AddsTimestamp(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodPost, "/api/task/T08/move?to=ready", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) content, _ := os.ReadFile(filepath.Join(srv.Config.TasksDir, "ready", "T08.md")) text := string(content) if !containsStr(text, "Odobren (→ready)") { t.Error("expected timestamp in API-moved task file") } }