vet/agent/ui_test.go
Oleksandr Redko 4e39cebe61
chore: add formatters to golangci-lint config (#643)
Signed-off-by: Oleksandr Redko <oleksandr.red+github@gmail.com>
2025-11-27 14:58:24 +05:30

281 lines
9.2 KiB
Go

package agent
import (
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/stretchr/testify/assert"
)
func TestAgentUICreation(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
assert.NotNil(t, ui, "Failed to create AgentUI")
assert.Empty(t, ui.statusMessage, "Expected empty status message")
assert.False(t, ui.isThinking, "UI should not be thinking initially")
assert.Equal(t, 0, ui.thinkingFrame, "Thinking frame should be 0 initially")
// Check that system message was added if config has one
if config.InitialSystemMessage != "" {
assert.NotEmpty(t, ui.messages, "Expected system message to be added if InitialSystemMessage is set")
}
}
func TestDefaultAgentUIConfig(t *testing.T) {
config := DefaultAgentUIConfig()
assert.Equal(t, 80, config.Width, "Expected default width 80")
assert.Equal(t, 20, config.Height, "Expected default height 20")
assert.Equal(t, "Security Agent", config.TitleText, "Expected title 'Security Agent'")
assert.Equal(t, "Ask me anything...", config.TextInputPlaceholder, "Expected placeholder 'Ask me anything...'")
}
func TestMessageManagement(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
initialCount := len(ui.messages)
// Test adding user message
ui.addUserMessage("Test user message")
assert.Equal(t, initialCount+1, len(ui.messages), "Expected message count to increase")
lastMessage := ui.messages[len(ui.messages)-1]
assert.Equal(t, "user", lastMessage.Role, "Expected last message role to be 'user'")
assert.Equal(t, "Test user message", lastMessage.Content, "Expected last message content to be 'Test user message'")
// Test adding agent message
ui.addAgentMessage("Test agent response")
assert.Equal(t, initialCount+2, len(ui.messages), "Expected message count to increase")
lastMessage = ui.messages[len(ui.messages)-1]
assert.Equal(t, "agent", lastMessage.Role, "Expected last message role to be 'agent'")
// Test adding system message
ui.addSystemMessage("System notification")
assert.Equal(t, initialCount+3, len(ui.messages), "Expected message count to increase")
lastMessage = ui.messages[len(ui.messages)-1]
assert.Equal(t, "system", lastMessage.Role, "Expected last message role to be 'system'")
// Test adding tool call message
ui.addToolCallMessage("ScanVulnerabilities", `{"path": "/app"}`)
assert.Equal(t, initialCount+4, len(ui.messages), "Expected message count to increase")
lastMessage = ui.messages[len(ui.messages)-1]
assert.Equal(t, "tool", lastMessage.Role, "Expected last message role to be 'tool'")
}
func TestMessageRendering(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
// Set up viewport dimensions for rendering
ui.viewport.Width = 80
ui.viewport.Height = 20
ui.addUserMessage("How many vulnerabilities?")
ui.addAgentMessage("Found 5 critical vulnerabilities")
rendered := ui.renderMessages()
assert.NotEmpty(t, rendered, "Expected non-empty rendered output")
assert.Contains(t, rendered, "How many vulnerabilities?", "Rendered output should contain user message")
assert.Contains(t, rendered, "Found 5 critical vulnerabilities", "Rendered output should contain agent message")
assert.Contains(t, rendered, "You:", "Rendered output should contain user label")
assert.Contains(t, rendered, "Agent:", "Rendered output should contain agent label")
}
func TestViewportDimensions(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
// Test window resize handling
resizeMsg := tea.WindowSizeMsg{Width: 100, Height: 30}
ui.Update(resizeMsg)
assert.Equal(t, 100, ui.width, "Expected width 100")
assert.Equal(t, 30, ui.height, "Expected height 30")
// Test minimum dimensions enforcement
resizeMsg = tea.WindowSizeMsg{Width: 10, Height: 5}
ui.Update(resizeMsg)
assert.GreaterOrEqual(t, ui.viewport.Width, 50, "Viewport width should be enforced to minimum 50")
assert.GreaterOrEqual(t, ui.viewport.Height, 10, "Viewport height should be enforced to minimum 10")
}
func TestViewRendering(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
config.ModelName = "gpt-4"
config.ModelVendor = "openai"
config.ModelFast = false
ui := NewAgentUI(mockAgent, mockSession, config)
ui.width = 80
ui.height = 24
ui.ready = true
view := ui.View()
assert.Contains(t, view, "Security Agent", "View should contain title")
assert.Contains(t, view, "openai/gpt-4", "View should contain model information")
assert.Contains(t, view, ">", "View should contain input prompt")
assert.Contains(t, view, "ctrl+c to exit", "View should contain exit instruction")
}
func TestThinkingState(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
ui.width = 80
ui.height = 24
ui.ready = true
// Initially not thinking
assert.False(t, ui.isThinking, "UI should not be thinking initially")
// Set thinking state
thinkingMsg := agentThinkingMsg{thinking: true}
ui.Update(thinkingMsg)
assert.True(t, ui.isThinking, "UI should be thinking after agentThinkingMsg")
// Check view contains thinking indicator
view := ui.View()
assert.Contains(t, view, "thinking...", "View should contain thinking indicator when thinking")
// Stop thinking
thinkingMsg = agentThinkingMsg{thinking: false}
ui.Update(thinkingMsg)
assert.False(t, ui.isThinking, "UI should not be thinking after agentThinkingMsg with false")
}
func TestKeyboardHandling(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
ui.width = 80
ui.height = 24
ui.ready = true
var keyMsg tea.KeyMsg
var model tea.Model
var cmd tea.Cmd
// Test Ctrl+C exits immediately
keyMsg = tea.KeyMsg{Type: tea.KeyCtrlC}
_, cmd = ui.Update(keyMsg)
assert.NotNil(t, cmd, "Ctrl+C should return quit command")
// Test Tab key for focus switching when not thinking
ui.textInput.Focus()
keyMsg = tea.KeyMsg{Type: tea.KeyTab}
model, _ = ui.Update(keyMsg)
ui = model.(*agentUI)
assert.False(t, ui.textInput.Focused(), "Tab should blur text input when it's focused")
// Test Enter key handling when not thinking
ui.textInput.Focus()
ui.textInput.SetValue("test message")
initialMessageCount := len(ui.messages)
keyMsg = tea.KeyMsg{Type: tea.KeyEnter}
model, _ = ui.Update(keyMsg)
ui = model.(*agentUI)
assert.Equal(t, initialMessageCount+1, len(ui.messages), "Enter should add user message when input is not empty")
// Note: Input field reset happens when thinking starts, not immediately
// The resetInputField() is called, but the UI state may not reflect it immediately in tests
}
func TestInputFieldReset(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
// Set some input text
ui.textInput.SetValue("test input")
assert.Equal(t, "test input", ui.textInput.Value(), "Input should contain test text")
// Reset input field
ui.resetInputField()
assert.Empty(t, ui.textInput.Value(), "Input should be empty after reset")
}
func TestCommandCreation(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
// Test status update command
cmd := ui.updateStatus("Testing status")
assert.NotNil(t, cmd, "updateStatus should return a non-nil command")
// Test thinking command
cmd = ui.setThinking(true)
assert.NotNil(t, cmd, "setThinking should return a non-nil command")
// Test execute agent query command
cmd = ui.executeAgentQuery("test query")
assert.NotNil(t, cmd, "executeAgentQuery should return a non-nil command")
}
func TestMessageTimestamps(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
before := time.Now()
ui.addUserMessage("Test message")
after := time.Now()
message := ui.messages[len(ui.messages)-1]
assert.True(t, message.Timestamp.After(before) || message.Timestamp.Equal(before), "Message timestamp should be after or equal to before time")
assert.True(t, message.Timestamp.Before(after) || message.Timestamp.Equal(after), "Message timestamp should be before or equal to after time")
}
func TestUIInitialization(t *testing.T) {
mockAgent := NewMockAgent()
mockSession := NewMockSession()
config := DefaultAgentUIConfig()
ui := NewAgentUI(mockAgent, mockSession, config)
// Test Init command
cmd := ui.Init()
assert.NotNil(t, cmd, "Init should return a non-nil command")
// Test initial state before ready
view := ui.View()
assert.Equal(t, "Loading...", view, "View should show loading before ready")
// Test with zero dimensions
ui.ready = true
ui.width = 0
ui.height = 0
view = ui.View()
assert.Equal(t, "Initializing...", view, "View should show initializing with zero dimensions")
}