KAOS/code/internal/server/dashboard_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

318 lines
7.8 KiB
Go

package server
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
)
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 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 TestDashboardHTML_HasAllColumns(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
for _, col := range []string{"BACKLOG", "READY", "ACTIVE", "REVIEW", "DONE"} {
if !containsStr(body, col) {
t.Errorf("expected %s column in dashboard", col)
}
}
}
func TestDashboardHTML_HasHTMXAttributes(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "hx-get") {
t.Error("expected hx-get attributes in HTML")
}
if !containsStr(body, "hx-target") {
t.Error("expected hx-target attributes in HTML")
}
}
func TestDashboardHTML_TasksInCorrectColumns(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
// T01 should be in done column, T08 in backlog
if !containsStr(body, `id="col-done"`) {
t.Error("expected col-done in HTML")
}
if !containsStr(body, `id="col-backlog"`) {
t.Error("expected col-backlog in HTML")
}
}
func TestIsMoveAllowed(t *testing.T) {
tests := []struct {
from, to string
allowed bool
}{
{"backlog", "ready", true},
{"ready", "backlog", true},
{"review", "done", true},
{"review", "ready", true},
{"done", "review", true},
{"ready", "active", false},
{"active", "review", false},
{"backlog", "done", false},
{"backlog", "active", false},
{"done", "backlog", false},
{"ready", "ready", false},
}
for _, tt := range tests {
got := isMoveAllowed(tt.from, tt.to)
if got != tt.allowed {
t.Errorf("isMoveAllowed(%s, %s) = %v, want %v", tt.from, tt.to, got, tt.allowed)
}
}
}
func TestNoCacheHeaders(t *testing.T) {
srv := setupTestServer(t)
// Dynamic route should have no-cache headers
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
cc := w.Header().Get("Cache-Control")
if !containsStr(cc, "no-store") {
t.Errorf("expected Cache-Control no-store on dashboard, got %q", cc)
}
// API route should also have no-cache headers
req2 := httptest.NewRequest(http.MethodGet, "/api/tasks", nil)
w2 := httptest.NewRecorder()
srv.Router.ServeHTTP(w2, req2)
cc2 := w2.Header().Get("Cache-Control")
if !containsStr(cc2, "no-store") {
t.Errorf("expected Cache-Control no-store on API, got %q", cc2)
}
}
func TestDashboardReflectsDiskChanges(t *testing.T) {
srv := setupTestServer(t)
// Initial state: T08 in backlog
req := httptest.NewRequest(http.MethodGet, "/api/tasks", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
var tasks1 []taskResponse
json.Unmarshal(w.Body.Bytes(), &tasks1)
found := false
for _, task := range tasks1 {
if task.ID == "T08" && task.Status == "backlog" {
found = true
}
}
if !found {
t.Fatal("expected T08 in backlog initially")
}
// Move file on disk (simulating external change)
os.Rename(
filepath.Join(srv.Config.TasksDir, "backlog", "T08.md"),
filepath.Join(srv.Config.TasksDir, "ready", "T08.md"),
)
// Second request should reflect the change without server restart
req2 := httptest.NewRequest(http.MethodGet, "/api/tasks", nil)
w2 := httptest.NewRecorder()
srv.Router.ServeHTTP(w2, req2)
var tasks2 []taskResponse
json.Unmarshal(w2.Body.Bytes(), &tasks2)
found = false
for _, task := range tasks2 {
if task.ID == "T08" && task.Status == "ready" {
found = true
}
}
if !found {
t.Fatal("expected T08 in ready after disk move — server did not read fresh state")
}
}
func TestDashboardHTML_HasSortableScript(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "sortable.min.js") {
t.Error("expected sortable.min.js script tag")
}
if !containsStr(body, "initSortable") {
t.Error("expected initSortable function")
}
}
func TestDashboardHTML_HasDataFolderAttributes(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, `data-folder="backlog"`) {
t.Error("expected data-folder attribute on column-tasks")
}
if !containsStr(body, `data-folder="ready"`) {
t.Error("expected data-folder=ready attribute")
}
}
func TestTaskDetail_HasMoveButtons(t *testing.T) {
srv := setupTestServer(t)
// T08 is in backlog, should have "Odobri" button
req := httptest.NewRequest(http.MethodGet, "/task/T08", 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, "Odobri") {
t.Error("expected 'Odobri' button for backlog task")
}
}
func TestDashboardHTML_HasRunButton(t *testing.T) {
srv := setupTestServer(t)
// Move T08 to ready to test run button
os.Rename(
filepath.Join(srv.Config.TasksDir, "backlog", "T08.md"),
filepath.Join(srv.Config.TasksDir, "ready", "T08.md"),
)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "Pusti") {
t.Error("expected 'Pusti' button for ready task")
}
if !containsStr(body, "btn-run") {
t.Error("expected btn-run class")
}
}
func TestDashboardHTML_BlockedButton(t *testing.T) {
srv := setupTestServer(t)
// T08 in backlog with dep T07 not in done → blocked
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "Blokiran") {
t.Error("expected 'Blokiran' for backlog task with unmet deps")
}
if !containsStr(body, "btn-blocked") {
t.Error("expected btn-blocked class")
}
}
func TestDashboardHTML_DoneReportButton(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
// T01 is in done → should have "Izvestaj" button
if !containsStr(body, "btn-report") {
t.Error("expected btn-report for done task")
}
}
func TestDashboard_HasPrijavaNav(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, `href="/submit"`) {
t.Error("expected Prijava nav link in dashboard")
}
}