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