Brings 5 packages from low/zero coverage to solid test suites: - pkg/lab: 0% → 100% (Store pub/sub, Config env loading) - pkg/session: 0% → 89.9% (transcript parser, HTML renderer, search, video) - pkg/io/sigil: 43.8% → 98.5% (XOR/ShuffleMask obfuscators, ChaCha20-Poly1305) - pkg/repos: 18.9% → 81.9% (registry, topo sort, directory scan, org detection) - pkg/plugin: 54.8% → 67.1% (installer error paths, Remove, registry Load/Save) Co-Authored-By: Virgil <virgil@lethean.io>
194 lines
4.3 KiB
Go
194 lines
4.3 KiB
Go
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 "<script>alert('xss')</script>"`,
|
|
Output: `<img onerror=alert(1)>`,
|
|
Success: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := RenderHTML(sess, out); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := os.ReadFile(out)
|
|
html := string(data)
|
|
if strings.Contains(html, "<script>alert") {
|
|
t.Fatal("XSS: unescaped script tag in HTML output")
|
|
}
|
|
if strings.Contains(html, "<img onerror") {
|
|
t.Fatal("XSS: unescaped img tag in HTML output")
|
|
}
|
|
}
|