// SPDX-Licence-Identifier: EUPL-1.2 package session import ( "os" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRenderHTML_BasicSession_Good(t *testing.T) { dir := t.TempDir() outputPath := dir + "/output.html" sess := &Session{ ID: "test-session-12345678", Path: "/tmp/test.jsonl", StartTime: time.Date(2026, 2, 20, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 2, 20, 10, 5, 30, 0, time.UTC), Events: []Event{ { Timestamp: time.Date(2026, 2, 20, 10, 0, 0, 0, time.UTC), Type: "user", Input: "Hello, please help me", }, { Timestamp: time.Date(2026, 2, 20, 10, 0, 1, 0, time.UTC), Type: "assistant", Input: "Sure, let me check.", }, { Timestamp: time.Date(2026, 2, 20, 10, 0, 2, 0, time.UTC), Type: "tool_use", Tool: "Bash", ToolID: "t1", Input: "ls -la", Output: "total 42", Duration: time.Second, Success: true, }, { Timestamp: time.Date(2026, 2, 20, 10, 0, 4, 0, time.UTC), Type: "tool_use", Tool: "Read", ToolID: "t2", Input: "/tmp/file.go", Output: "package main", Duration: 500 * time.Millisecond, Success: true, }, }, } err := RenderHTML(sess, outputPath) require.NoError(t, err) content, err := os.ReadFile(outputPath) require.NoError(t, err) html := string(content) // Basic structure checks assert.Contains(t, html, "") assert.Contains(t, html, "test-ses") // shortID of "test-session-12345678" assert.Contains(t, html, "2026-02-20 10:00:00") assert.Contains(t, html, "5m30s") // duration assert.Contains(t, html, "2 tool calls") assert.Contains(t, html, "ls -la") assert.Contains(t, html, "total 42") assert.Contains(t, html, "/tmp/file.go") assert.Contains(t, html, "User") // user event label assert.Contains(t, html, "Claude") // assistant event label assert.Contains(t, html, "Bash") assert.Contains(t, html, "Read") // Should contain JS for toggle and filter assert.Contains(t, html, "function toggle") assert.Contains(t, html, "function filterEvents") } func TestRenderHTML_EmptySession_Good(t *testing.T) { dir := t.TempDir() outputPath := dir + "/empty.html" sess := &Session{ ID: "empty", Path: "/tmp/empty.jsonl", StartTime: time.Date(2026, 2, 20, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 2, 20, 10, 0, 0, 0, time.UTC), Events: nil, } err := RenderHTML(sess, outputPath) require.NoError(t, err) content, err := os.ReadFile(outputPath) require.NoError(t, err) html := string(content) assert.Contains(t, html, "") assert.Contains(t, html, "0 tool calls") // Should NOT contain error span assert.NotContains(t, html, "errors") } func TestRenderHTML_WithErrors_Good(t *testing.T) { dir := t.TempDir() outputPath := dir + "/errors.html" sess := &Session{ ID: "err-session", Path: "/tmp/err.jsonl", StartTime: time.Date(2026, 2, 20, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 2, 20, 10, 1, 0, 0, time.UTC), Events: []Event{ { Timestamp: time.Date(2026, 2, 20, 10, 0, 0, 0, time.UTC), Type: "tool_use", Tool: "Bash", Input: "cat /nonexistent", Output: "No such file", Duration: 100 * time.Millisecond, Success: false, ErrorMsg: "No such file", }, { Timestamp: time.Date(2026, 2, 20, 10, 0, 30, 0, time.UTC), Type: "tool_use", Tool: "Bash", Input: "echo ok", Output: "ok", Duration: 50 * time.Millisecond, Success: true, }, }, } err := RenderHTML(sess, outputPath) require.NoError(t, err) content, err := os.ReadFile(outputPath) require.NoError(t, err) html := string(content) assert.Contains(t, html, "1 errors") assert.Contains(t, html, `class="event error"`) assert.Contains(t, html, "✗") // cross mark for failed assert.Contains(t, html, "✓") // check mark for success } func TestRenderHTML_SpecialCharacters_Good(t *testing.T) { dir := t.TempDir() outputPath := dir + "/special.html" sess := &Session{ ID: "special", Path: "/tmp/special.jsonl", StartTime: time.Date(2026, 2, 20, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2026, 2, 20, 10, 0, 1, 0, time.UTC), Events: []Event{ { Timestamp: time.Date(2026, 2, 20, 10, 0, 0, 0, time.UTC), Type: "tool_use", Tool: "Bash", Input: `echo ""`, Output: ``, Duration: time.Second, Success: true, }, { Timestamp: time.Date(2026, 2, 20, 10, 0, 0, 0, time.UTC), Type: "user", Input: `User says: "quotes & "`, }, }, } err := RenderHTML(sess, outputPath) require.NoError(t, err) content, err := os.ReadFile(outputPath) require.NoError(t, err) html := string(content) // Script tags should be escaped, never raw assert.NotContains(t, html, "