KAOS/code/internal/server/api_test.go
djuka 098ed13705 T22: Reorganizacija testova + logs handler + konzola fix
- Razbijen monolitni server_test.go na fokusirane test fajlove:
  api_test.go, dashboard_test.go, docs_test.go, search_test.go,
  submit_test.go, task_detail_test.go, console_test.go, sse_test.go,
  timestamp_test.go, ui_test.go, test_helpers_test.go
- Dodat logs.go handler (handleLogsTail) koji je nedostajao
- Dodat LogFile u config
- Fix konzola: prompt se šalje preko fajla umesto direktno u PTY
- 192 testova prolazi

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 04:45:50 +00:00

206 lines
5.3 KiB
Go

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")
}
}