mirror of
https://github.com/usestrix/strix.git
synced 2026-08-16 09:26:39 +02:00
118 lines
4.2 KiB
Go
118 lines
4.2 KiB
Go
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/muesli/termenv"
|
|
"github.com/usestrix/strix/tui/internal/protocol"
|
|
"github.com/usestrix/strix/tui/internal/render"
|
|
)
|
|
|
|
// Retrying a launch that cannot succeed yet must not fill the log with copies of
|
|
// the same two lines.
|
|
func TestSetupLogCollapsesRepeatedAttempts(t *testing.T) {
|
|
m := New(nil)
|
|
m.snapshot.SetupMode = true
|
|
for range 4 {
|
|
m.setupMsg("Verifying model connection...", render.Col(amber))
|
|
m.setupMsg("No model configured. Set STRIX_LLM first.", render.Col(red))
|
|
}
|
|
if got := len(m.setupLog); got != 2 {
|
|
t.Fatalf("setup log holds %d lines after 4 identical attempts, want 2: %#v", got, m.setupLog)
|
|
}
|
|
// The newest line stays last so the log still reads chronologically.
|
|
if !strings.Contains(m.setupLog[1], "No model configured") {
|
|
t.Fatalf("most recent line is not last: %#v", m.setupLog)
|
|
}
|
|
m.setupMsg("\u2713 Added target: https://example.com", render.Col(green))
|
|
if got := len(m.setupLog); got != 3 {
|
|
t.Fatalf("a distinct line did not append: %#v", m.setupLog)
|
|
}
|
|
}
|
|
|
|
// The same collapse must hold for the path a real misconfiguration takes: a
|
|
// failing setup.start arriving as a command_result.
|
|
func TestRepeatedSetupStartFailureLogsOnce(t *testing.T) {
|
|
model, _ := newCommandTestModel(t)
|
|
model.snapshot.SetupMode = true
|
|
model.client.pending = map[string]string{}
|
|
model.client.pendingByKey = map[string]string{}
|
|
model.client.requestKeyByID = map[string]string{}
|
|
for i := range 3 {
|
|
requestID := "req-" + string(rune('a'+i))
|
|
model.client.pending[requestID] = "setup.start"
|
|
model.client.pendingByKey["setup.start"] = requestID
|
|
model.client.requestKeyByID[requestID] = "setup.start"
|
|
payload, err := json.Marshal(protocol.CommandResult{
|
|
OK: false,
|
|
Command: "setup.start",
|
|
Error: &protocol.CommandError{
|
|
Code: "invalid_state",
|
|
Message: "No model configured. Set STRIX_LLM first.",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
model.handleEnvelope(protocol.Envelope{
|
|
Version: protocol.Version, Type: "command_result", RequestID: requestID, Payload: payload,
|
|
})
|
|
}
|
|
if got := len(model.setupLog); got != 1 {
|
|
t.Fatalf("three identical launch failures logged %d lines, want 1: %#v", got, model.setupLog)
|
|
}
|
|
}
|
|
|
|
// Every Tab-reachable panel shows focus with the same green border.
|
|
func TestFocusedPanelsCarryTheGreenBorder(t *testing.T) {
|
|
// The profile is global; restore it so later tests still render unstyled.
|
|
previous := lipgloss.ColorProfile()
|
|
t.Cleanup(func() { lipgloss.SetColorProfile(previous) })
|
|
lipgloss.SetColorProfile(termenv.TrueColor)
|
|
borderColorsOf := func(focus focusMode) string {
|
|
m := New(nil)
|
|
m.width, m.height = 130, 30
|
|
m.showSplash = false
|
|
m.snapshot.ScanState = "running"
|
|
m.snapshot.Agents = []protocol.Agent{{ID: "a0", Name: "Strix", Status: "running"}}
|
|
m.snapshot.Vulnerabilities = []map[string]any{{"title": "XSS", "severity": "high"}}
|
|
m.focus = focus
|
|
m.resizeViewport()
|
|
return m.sidebarView(26, m.height)
|
|
}
|
|
idle := borderColorsOf(focusInput)
|
|
if strings.Contains(idle, "34;197;94") {
|
|
t.Fatal("an unfocused sidebar panel drew a green border")
|
|
}
|
|
for _, focus := range []focusMode{focusAgents, focusVulnerabilities} {
|
|
if !strings.Contains(borderColorsOf(focus), "34;197;94") {
|
|
t.Fatalf("focus %v did not draw a green border", focus)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A wrapped exception is several lines. The log budgets rows by entry, so it has
|
|
// to become one row or the launch column grows past the terminal.
|
|
func TestSetupLogKeepsMultiLineErrorsToOneRow(t *testing.T) {
|
|
model := New(nil)
|
|
model.width, model.height = 100, 26
|
|
model.showSplash = false
|
|
model.handleEnvelope(stateEnvelope(t, 1, protocol.Snapshot{SetupMode: true, ScanState: "setup"}))
|
|
model.setupMsg("boom\nTraceback (most recent call last):\n File \"x.py\", line 1\n raise", render.Col(red))
|
|
model.resizeViewport()
|
|
|
|
if entries := len(model.setupLog); entries != 1 {
|
|
t.Fatalf("one message became %d log entries", entries)
|
|
}
|
|
if strings.Contains(model.setupLog[0], "\n") {
|
|
t.Fatalf("log entry spans rows: %q", model.setupLog[0])
|
|
}
|
|
lines := strings.Split(model.View(), "\n")
|
|
if len(lines) > model.height {
|
|
t.Fatalf("start screen is %d rows in a %d-row terminal", len(lines), model.height)
|
|
}
|
|
}
|