- 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>
122 lines
2.8 KiB
Go
122 lines
2.8 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/dal/kaos/internal/supervisor"
|
|
)
|
|
|
|
func TestSSE_EventsEndpoint(t *testing.T) {
|
|
srv := setupTestServer(t)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/events", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
// Use a context with cancel to stop the SSE handler
|
|
ctx, cancel := req.Context(), func() {}
|
|
_ = ctx
|
|
_ = cancel
|
|
|
|
// Just check the handler starts without error and sets correct headers
|
|
go func() {
|
|
srv.Router.ServeHTTP(w, req)
|
|
}()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
if ct := w.Header().Get("Content-Type"); ct != "text/event-stream" {
|
|
t.Errorf("expected Content-Type text/event-stream, got %s", ct)
|
|
}
|
|
}
|
|
|
|
func TestHashTaskState(t *testing.T) {
|
|
tasks1 := []supervisor.Task{
|
|
{ID: "T01", Status: "done"},
|
|
{ID: "T02", Status: "backlog"},
|
|
}
|
|
tasks2 := []supervisor.Task{
|
|
{ID: "T01", Status: "done"},
|
|
{ID: "T02", Status: "ready"}, // changed
|
|
}
|
|
tasks3 := []supervisor.Task{
|
|
{ID: "T02", Status: "backlog"},
|
|
{ID: "T01", Status: "done"}, // same as tasks1 but different order
|
|
}
|
|
|
|
h1 := hashTaskState(tasks1)
|
|
h2 := hashTaskState(tasks2)
|
|
h3 := hashTaskState(tasks3)
|
|
|
|
if h1 == h2 {
|
|
t.Error("hash should differ when task status changes")
|
|
}
|
|
if h1 != h3 {
|
|
t.Error("hash should be same regardless of task order")
|
|
}
|
|
}
|
|
|
|
func TestSSE_BroadcastOnChange(t *testing.T) {
|
|
srv := setupTestServer(t)
|
|
|
|
// Subscribe a client
|
|
ch := srv.events.subscribe()
|
|
defer srv.events.unsubscribe(ch)
|
|
|
|
// Trigger a check — first call sets the baseline hash and broadcasts
|
|
srv.events.checkAndBroadcast(func() string { return "board-html" })
|
|
|
|
// Drain initial broadcast
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(100 * time.Millisecond):
|
|
}
|
|
|
|
// Move a task to change state
|
|
os.Rename(
|
|
filepath.Join(srv.Config.TasksDir, "backlog", "T08.md"),
|
|
filepath.Join(srv.Config.TasksDir, "ready", "T08.md"),
|
|
)
|
|
|
|
// Trigger another check — state changed, should broadcast
|
|
srv.events.checkAndBroadcast(func() string { return "updated-board" })
|
|
|
|
select {
|
|
case data := <-ch:
|
|
if data != "updated-board" {
|
|
t.Errorf("expected 'updated-board', got %s", data)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Error("expected broadcast after state change, got nothing")
|
|
}
|
|
}
|
|
|
|
func TestSSE_NoBroadcastWithoutChange(t *testing.T) {
|
|
srv := setupTestServer(t)
|
|
|
|
ch := srv.events.subscribe()
|
|
defer srv.events.unsubscribe(ch)
|
|
|
|
// Two checks without changes — second should not broadcast
|
|
srv.events.checkAndBroadcast(func() string { return "board" })
|
|
|
|
// Drain the first broadcast (initial hash set)
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(100 * time.Millisecond):
|
|
}
|
|
|
|
srv.events.checkAndBroadcast(func() string { return "board" })
|
|
|
|
select {
|
|
case <-ch:
|
|
t.Error("should not broadcast when state hasn't changed")
|
|
case <-time.After(100 * time.Millisecond):
|
|
// Good — no broadcast
|
|
}
|
|
}
|