- 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>
318 lines
7.8 KiB
Go
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")
|
|
}
|
|
}
|