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

292 lines
6.8 KiB
Go

package server
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestMoveTask_AddsTimestamp(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())
}
// Read the moved file and check for timestamp
content, err := os.ReadFile(filepath.Join(srv.Config.TasksDir, "ready", "T08.md"))
if err != nil {
t.Fatalf("failed to read moved file: %v", err)
}
text := string(content)
if !containsStr(text, "## Vremena") {
t.Error("expected '## Vremena' section in task file")
}
if !containsStr(text, "Odobren (→ready)") {
t.Error("expected 'Odobren (→ready)' timestamp")
}
}
func TestMoveTask_AppendsMultipleTimestamps(t *testing.T) {
srv := setupTestServer(t)
// Move backlog → ready
req := httptest.NewRequest(http.MethodPost, "/task/T08/move?to=ready", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
// Move ready → backlog (return)
req2 := httptest.NewRequest(http.MethodPost, "/task/T08/move?to=backlog", nil)
w2 := httptest.NewRecorder()
// Need to re-fetch since task is now in ready/
srv.Router.ServeHTTP(w2, req2)
// Move backlog → ready again
req3 := httptest.NewRequest(http.MethodPost, "/task/T08/move?to=ready", nil)
w3 := httptest.NewRecorder()
srv.Router.ServeHTTP(w3, req3)
content, _ := os.ReadFile(filepath.Join(srv.Config.TasksDir, "ready", "T08.md"))
text := string(content)
// Should have two "Odobren (→ready)" entries
count := strings.Count(text, "Odobren (→ready)")
if count != 2 {
t.Errorf("expected 2 'Odobren (→ready)' timestamps, got %d", count)
}
}
func TestAppendTimestamp_CreatesTable(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.md")
os.WriteFile(path, []byte("# T01: Test\n\nSome content.\n"), 0644)
err := appendTimestamp(path, "Odobren (→ready)")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
content, _ := os.ReadFile(path)
text := string(content)
if !containsStr(text, "## Vremena") {
t.Error("expected Vremena section")
}
if !containsStr(text, "| Događaj | Vreme |") {
t.Error("expected table header")
}
if !containsStr(text, "Odobren (→ready)") {
t.Error("expected timestamp row")
}
}
func TestAppendTimestamp_AppendsToExisting(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.md")
os.WriteFile(path, []byte("# T01: Test\n\n## Vremena\n\n| Događaj | Vreme |\n|---------|-------|\n| Kreiran | 2026-02-20 14:00 |\n"), 0644)
err := appendTimestamp(path, "Odobren (→ready)")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
content, _ := os.ReadFile(path)
text := string(content)
if !containsStr(text, "Kreiran") {
t.Error("expected existing row preserved")
}
if !containsStr(text, "Odobren (→ready)") {
t.Error("expected new timestamp row")
}
}
func TestRunTask_AddsTimestamp(t *testing.T) {
srv := setupTestServer(t)
// Move T08 to ready
os.Rename(
filepath.Join(srv.Config.TasksDir, "backlog", "T08.md"),
filepath.Join(srv.Config.TasksDir, "ready", "T08.md"),
)
req := httptest.NewRequest(http.MethodPost, "/task/T08/run", 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())
}
// Task should now be in active/
content, _ := os.ReadFile(filepath.Join(srv.Config.TasksDir, "active", "T08.md"))
text := string(content)
if !containsStr(text, "Pokrenut") {
t.Error("expected 'Pokrenut' timestamp after run")
}
// Verify it's no longer in ready/
if _, err := os.Stat(filepath.Join(srv.Config.TasksDir, "ready", "T08.md")); err == nil {
t.Error("task should no longer be in ready/")
}
}
func TestStripAnsi(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"hello", "hello"},
{"\x1b[32mgreen\x1b[0m", "green"},
{"\x1b[1;31mbold red\x1b[0m", "bold red"},
{"\x1b[?25l\x1b[?25h", ""},
{"no \x1b[4munderline\x1b[24m here", "no underline here"},
{"\x1b]0;title\x07text", "text"},
}
for _, tt := range tests {
got := stripAnsi(tt.input)
if got != tt.expected {
t.Errorf("stripAnsi(%q) = %q, want %q", tt.input, got, tt.expected)
}
}
}
func TestReadPTY_SplitsLines(t *testing.T) {
r, w, _ := os.Pipe()
var lines []string
done := make(chan bool)
go func() {
readPTY(r, func(line string) {
lines = append(lines, line)
})
done <- true
}()
w.WriteString("line1\nline2\nline3\n")
w.Close()
<-done
if len(lines) != 3 {
t.Fatalf("expected 3 lines, got %d: %v", len(lines), lines)
}
if lines[0] != "line1" || lines[1] != "line2" || lines[2] != "line3" {
t.Errorf("unexpected lines: %v", lines)
}
}
func TestReadPTY_StripsAnsi(t *testing.T) {
r, w, _ := os.Pipe()
var lines []string
done := make(chan bool)
go func() {
readPTY(r, func(line string) {
lines = append(lines, line)
})
done <- true
}()
w.WriteString("\x1b[32mcolored\x1b[0m\n")
w.Close()
<-done
if len(lines) != 1 {
t.Fatalf("expected 1 line, got %d", len(lines))
}
if lines[0] != "colored" {
t.Errorf("expected 'colored', got %q", lines[0])
}
}
func TestReadPTY_HandlesPartialChunks(t *testing.T) {
r, w, _ := os.Pipe()
var lines []string
done := make(chan bool)
go func() {
readPTY(r, func(line string) {
lines = append(lines, line)
})
done <- true
}()
// Write partial, then complete
w.WriteString("partial")
w.Close()
<-done
if len(lines) != 1 {
t.Fatalf("expected 1 line for partial, got %d: %v", len(lines), lines)
}
if lines[0] != "partial" {
t.Errorf("expected 'partial', got %q", lines[0])
}
}
func TestReadPTY_HandlesCarriageReturn(t *testing.T) {
r, w, _ := os.Pipe()
var lines []string
done := make(chan bool)
go func() {
readPTY(r, func(line string) {
lines = append(lines, line)
})
done <- true
}()
w.WriteString("line1\r\nline2\r\n")
w.Close()
<-done
if len(lines) != 2 {
t.Fatalf("expected 2 lines, got %d: %v", len(lines), lines)
}
if lines[0] != "line1" || lines[1] != "line2" {
t.Errorf("unexpected lines: %v", lines)
}
}
func TestRingBuffer_WriteAndRead(t *testing.T) {
rb := NewRingBuffer(16)
rb.Write([]byte("hello"))
got := rb.Bytes()
if string(got) != "hello" {
t.Errorf("expected 'hello', got '%s'", got)
}
}
func TestRingBuffer_Overflow(t *testing.T) {
rb := NewRingBuffer(8)
rb.Write([]byte("abcdefgh")) // exactly fills
rb.Write([]byte("ij")) // wraps around
got := rb.Bytes()
// Should contain the last 8 bytes: "cdefghij"
if string(got) != "cdefghij" {
t.Errorf("expected 'cdefghij', got '%s'", got)
}
}
func TestRingBuffer_Reset(t *testing.T) {
rb := NewRingBuffer(16)
rb.Write([]byte("test"))
rb.Reset()
got := rb.Bytes()
if len(got) != 0 {
t.Errorf("expected empty after reset, got %d bytes", len(got))
}
}