package server import ( "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "github.com/dal/kaos/internal/config" ) const testTask1 = `# T01: Prvi task **Agent:** coder **Model:** Sonnet **Zavisi od:** — --- ## Opis Opis prvog taska. --- ` const testTask2 = `# T08: HTTP server **Agent:** coder **Model:** Sonnet **Zavisi od:** T07 --- ## Opis Implementacija HTTP servera. --- ` func setupTestServer(t *testing.T) *Server { t.Helper() dir := t.TempDir() tasksDir := filepath.Join(dir, "TASKS") for _, folder := range []string{"backlog", "ready", "active", "review", "done", "reports"} { os.MkdirAll(filepath.Join(tasksDir, folder), 0755) } os.WriteFile(filepath.Join(tasksDir, "done", "T01.md"), []byte(testTask1), 0644) os.WriteFile(filepath.Join(tasksDir, "backlog", "T08.md"), []byte(testTask2), 0644) cfg := &config.Config{ TasksDir: tasksDir, ProjectPath: dir, Port: "0", } return New(cfg) } 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 TestDashboardHTML(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodGet, "/", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d", w.Code) } body := w.Body.String() if !containsStr(body, "KAOS Dashboard") { t.Error("expected 'KAOS Dashboard' in HTML") } if !containsStr(body, "T01") { t.Error("expected T01 in HTML") } if !containsStr(body, "T08") { t.Error("expected T08 in HTML") } if !containsStr(body, "BACKLOG") { t.Error("expected BACKLOG column in HTML") } if !containsStr(body, "DONE") { t.Error("expected DONE column in HTML") } } func TestTaskDetailHTML(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodGet, "/task/T01", nil) w := httptest.NewRecorder() srv.Router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d", w.Code) } body := w.Body.String() if !containsStr(body, "T01") { t.Error("expected T01 in detail HTML") } if !containsStr(body, "Prvi task") { t.Error("expected task title in detail HTML") } } func TestTaskDetailHTML_NotFound(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodGet, "/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 TestHTMLMoveTask(t *testing.T) { srv := setupTestServer(t) req := httptest.NewRequest(http.MethodPost, "/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()) } // Should return updated dashboard HTML body := w.Body.String() if !containsStr(body, "KAOS Dashboard") { t.Error("expected dashboard HTML after move") } } func containsStr(s, substr string) bool { return len(s) >= len(substr) && findStr(s, substr) } func findStr(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }