package session import ( "os" "path/filepath" "strings" "testing" "time" ) func TestRenderHTML_Good_BasicSession(t *testing.T) { dir := t.TempDir() out := filepath.Join(dir, "session.html") sess := &Session{ ID: "f3fb074c-8c72-4da6-a15a-85bae652ccaa", StartTime: time.Date(2026, 2, 24, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 2, 24, 10, 5, 0, 0, time.UTC), Events: []Event{ { Timestamp: time.Date(2026, 2, 24, 10, 0, 5, 0, time.UTC), Type: "tool_use", Tool: "Bash", Input: "go test ./...", Output: "ok forge.lthn.ai/core/go 1.2s", Duration: time.Second, Success: true, }, { Timestamp: time.Date(2026, 2, 24, 10, 1, 0, 0, time.UTC), Type: "tool_use", Tool: "Read", Input: "/tmp/test.go", Output: "package main", Duration: 200 * time.Millisecond, Success: true, }, { Timestamp: time.Date(2026, 2, 24, 10, 2, 0, 0, time.UTC), Type: "user", Input: "looks good", }, }, } if err := RenderHTML(sess, out); err != nil { t.Fatal(err) } data, err := os.ReadFile(out) if err != nil { t.Fatal(err) } html := string(data) if !strings.Contains(html, "f3fb074c") { t.Fatal("missing session ID") } if !strings.Contains(html, "go test ./...") { t.Fatal("missing bash command") } if !strings.Contains(html, "2 tool calls") { t.Fatal("missing tool count") } if !strings.Contains(html, "filterEvents") { t.Fatal("missing JS filter function") } } func TestRenderHTML_Good_WithErrors(t *testing.T) { dir := t.TempDir() out := filepath.Join(dir, "errors.html") sess := &Session{ ID: "err-session", StartTime: time.Date(2026, 2, 24, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 2, 24, 10, 1, 0, 0, time.UTC), Events: []Event{ { Type: "tool_use", Tool: "Bash", Timestamp: time.Date(2026, 2, 24, 10, 0, 0, 0, time.UTC), Input: "bad command", Output: "error", Success: false, }, }, } if err := RenderHTML(sess, out); err != nil { t.Fatal(err) } data, _ := os.ReadFile(out) html := string(data) if !strings.Contains(html, "1 errors") { t.Fatal("missing error count") } if !strings.Contains(html, `class="event error"`) { t.Fatal("missing error class") } if !strings.Contains(html, "✗") { t.Fatal("missing failure icon") } } func TestRenderHTML_Good_AssistantEvent(t *testing.T) { dir := t.TempDir() out := filepath.Join(dir, "asst.html") sess := &Session{ ID: "asst-test", StartTime: time.Date(2026, 2, 24, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 2, 24, 10, 0, 5, 0, time.UTC), Events: []Event{ { Type: "assistant", Timestamp: time.Date(2026, 2, 24, 10, 0, 0, 0, time.UTC), Input: "Let me check that.", }, }, } if err := RenderHTML(sess, out); err != nil { t.Fatal(err) } data, _ := os.ReadFile(out) if !strings.Contains(string(data), "Claude") { t.Fatal("missing Claude label for assistant") } } func TestRenderHTML_Good_EmptySession(t *testing.T) { dir := t.TempDir() out := filepath.Join(dir, "empty.html") sess := &Session{ ID: "empty", StartTime: time.Now(), EndTime: time.Now(), } if err := RenderHTML(sess, out); err != nil { t.Fatal(err) } info, err := os.Stat(out) if err != nil { t.Fatal(err) } if info.Size() == 0 { t.Fatal("HTML file is empty") } } func TestRenderHTML_Bad_InvalidPath(t *testing.T) { sess := &Session{ID: "test", StartTime: time.Now(), EndTime: time.Now()} err := RenderHTML(sess, "/nonexistent/dir/out.html") if err == nil { t.Fatal("expected error for invalid path") } } func TestRenderHTML_Good_XSSEscaping(t *testing.T) { dir := t.TempDir() out := filepath.Join(dir, "xss.html") sess := &Session{ ID: "xss-test", StartTime: time.Now(), EndTime: time.Now(), Events: []Event{ { Type: "tool_use", Tool: "Bash", Timestamp: time.Now(), Input: `echo ""`, Output: ``, Success: true, }, }, } if err := RenderHTML(sess, out); err != nil { t.Fatal(err) } data, _ := os.ReadFile(out) html := string(data) if strings.Contains(html, "