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

313 lines
7.9 KiB
Go

package server
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestConsolePage(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", 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") {
t.Error("expected 'KAOS' in console page")
}
if !containsStr(body, "Konzola") {
t.Error("expected 'Konzola' in console page")
}
}
func TestConsoleSessions_Empty(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console/sessions", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var sessions []taskSessionResponse
if err := json.Unmarshal(w.Body.Bytes(), &sessions); err != nil {
t.Fatalf("invalid JSON: %v", err)
}
if len(sessions) != 0 {
t.Fatalf("expected 0 sessions, got %d", len(sessions))
}
}
func TestConsoleKill_NotFound(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodPost, "/console/kill/T99", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
if w.Code != http.StatusNotFound {
t.Fatalf("expected 404, got %d", w.Code)
}
}
func TestSessionKey(t *testing.T) {
if got := sessionKey("T01", "work"); got != "T01" {
t.Errorf("expected T01, got %s", got)
}
if got := sessionKey("T01", "review"); got != "T01-review" {
t.Errorf("expected T01-review, got %s", got)
}
}
func TestTaskSessionManager_ListEmpty(t *testing.T) {
sm := newTaskSessionManager()
sessions := sm.listSessions()
if len(sessions) != 0 {
t.Errorf("expected 0 sessions, got %d", len(sessions))
}
}
func TestTaskSessionManager_KillNotFound(t *testing.T) {
sm := newTaskSessionManager()
if sm.killSession("T99", "work") {
t.Error("expected false for non-existent session")
}
}
func TestTaskSessionManager_GetNotFound(t *testing.T) {
sm := newTaskSessionManager()
if sm.getSessionByKey("T99") != nil {
t.Error("expected nil for non-existent session")
}
}
func TestBuildWorkPrompt(t *testing.T) {
prompt := buildWorkPrompt("T08", []byte("# T08: Test task\n\nOpis."))
if !containsStr(prompt, "T08") {
t.Error("expected task ID in prompt")
}
if !containsStr(prompt, "Test task") {
t.Error("expected task content in prompt")
}
if !containsStr(prompt, "agents/coder/CLAUDE.md") {
t.Error("expected coder CLAUDE.md reference")
}
if !containsStr(prompt, "go test") {
t.Error("expected test instruction")
}
if !containsStr(prompt, "report") {
t.Error("expected report instruction")
}
}
func TestBuildReviewPrompt(t *testing.T) {
prompt := buildReviewPrompt("T08", []byte("# T08: Test\n"), []byte("# Report\nSve ok."))
if !containsStr(prompt, "T08") {
t.Error("expected task ID in review prompt")
}
if !containsStr(prompt, "Sve ok") {
t.Error("expected report content in review prompt")
}
if !containsStr(prompt, "agents/checker/CLAUDE.md") {
t.Error("expected checker CLAUDE.md reference")
}
}
func TestBuildReviewPrompt_NoReport(t *testing.T) {
prompt := buildReviewPrompt("T08", []byte("# T08: Test\n"), nil)
if !containsStr(prompt, "T08") {
t.Error("expected task ID")
}
if containsStr(prompt, "Izveštaj agenta") {
t.Error("should not include report section when no report")
}
}
func TestReviewTask_NotFound(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodPost, "/task/T99/review", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
if w.Code != http.StatusNotFound {
t.Fatalf("expected 404, got %d", w.Code)
}
}
func TestConsolePage_HasKillButton(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "killSession") {
t.Error("expected killSession function in console page")
}
}
func TestTaskDetail_HasProveriButton(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.MethodGet, "/task/T08", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "Proveri") {
t.Error("expected 'Proveri' button for review task")
}
if !containsStr(body, "/review") {
t.Error("expected /review endpoint in Proveri button")
}
}
func TestConsolePage_ToolbarAbovePanels(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
// Toolbar should appear before console-panels in the HTML
toolbarIdx := strings.Index(body, "console-toolbar")
panelsIdx := strings.Index(body, "console-panels")
if toolbarIdx == -1 {
t.Fatal("expected console-toolbar in console page")
}
if panelsIdx == -1 {
t.Fatal("expected console-panels in console page")
}
if toolbarIdx > panelsIdx {
t.Error("expected toolbar before panels in console HTML")
}
}
func TestConsolePage_HasDynamicSessions(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "refreshSessions") {
t.Error("expected refreshSessions function in console page")
}
if !containsStr(body, "/console/sessions") {
t.Error("expected /console/sessions API call")
}
}
func TestConsolePage_HasXtermJS(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "xterm.min.js") {
t.Error("expected xterm.min.js CDN link in console page")
}
if !containsStr(body, "addon-fit") {
t.Error("expected addon-fit CDN link in console page")
}
if !containsStr(body, "xterm.css") {
t.Error("expected xterm.css CDN link in console page")
}
}
func TestConsolePage_HasWebSocket(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "console/ws/") {
t.Error("expected WebSocket URL console/ws/ in console page")
}
if !containsStr(body, "new WebSocket") {
t.Error("expected WebSocket constructor in console page")
}
}
func TestConsolePage_HasEmptyState(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "empty-state") {
t.Error("expected empty-state element")
}
if !containsStr(body, "Pusti") {
t.Error("expected 'Pusti' instruction in empty state")
}
if !containsStr(body, "console-terminal") {
t.Error("expected console-terminal class in JS code")
}
}
func TestConsolePage_HasBinaryMessageSupport(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "arraybuffer") {
t.Error("expected arraybuffer binary type for WebSocket")
}
if !containsStr(body, "Uint8Array") {
t.Error("expected Uint8Array handling for binary messages")
}
}
func TestConsolePage_HasResizeHandler(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "fitAddon.fit()") {
t.Error("expected fitAddon.fit() for resize handling")
}
if !containsStr(body, `'resize'`) {
t.Error("expected resize message type in WebSocket handler")
}
}