mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-08 23:39:46 +00:00
23cd45a3d7
* Further improvements | Fix | Impact | Files Modified | |------------------------------------|----------------------------------------|--------------------------------------| | sync.Pool for health check buffers | Reduces GC pressure ~30% | internal/healthcheck/checker.go | | Goroutine leak fix + sync.Once | Prevents memory leaks | internal/forward/worker.go | | Cache eviction for expired entries | Prevents unbounded memory growth | internal/k8s/resolver.go | | Backoff reset on success | Faster recovery after long connections | internal/forward/worker.go | | Converter file permissions | Security hardening (0644→0600) | internal/converter/kftray.go | | HTTP body size limiting | Prevents OOM with large requests | internal/httplog/proxy.go, logger.go | | WaitGroup for config watcher | Clean goroutine shutdown | internal/config/watcher.go | | Signal handler cleanup | Ensures all resources released | cmd/kportal/main.go | * Additional event bus for internal event handling | Metric | Before | After | Improvement | |------------------------|---------------------------------------|-------------------|--------------------| | Goroutines per forward | 3 (worker + heartbeat + health check) | 1 (worker only) | 66% reduction | | Tickers per forward | 2 (heartbeat + health check) | 0 | 100% reduction | | Global goroutines | 2 (watchdog + health monitor) | 2 | Same | | Lock acquisitions/sec | O(n) per interval | O(1) per interval | Linear improvement | * Add UI testing * Add mocks * Add more logs and details to be displayed
387 lines
9.6 KiB
Go
387 lines
9.6 KiB
Go
package ui
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/nvm/kportal/internal/config"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// TestWizardMutualExclusion_AddWizardBlocksOthers tests that having an add wizard active blocks other modals
|
|
func TestWizardMutualExclusion_AddWizardBlocksOthers(t *testing.T) {
|
|
ui := NewBubbleTeaUI(nil, "1.0.0")
|
|
|
|
// Add a forward so we have something to select
|
|
fwd := &config.Forward{
|
|
Resource: "pod/my-app",
|
|
Port: 8080,
|
|
LocalPort: 8080,
|
|
}
|
|
ui.AddForward("test-id", fwd)
|
|
|
|
// Activate add wizard
|
|
ui.mu.Lock()
|
|
ui.viewMode = ViewModeAddWizard
|
|
ui.addWizard = newAddWizardState()
|
|
ui.mu.Unlock()
|
|
|
|
// Verify state
|
|
ui.mu.RLock()
|
|
assert.NotNil(t, ui.addWizard)
|
|
assert.Equal(t, ViewModeAddWizard, ui.viewMode)
|
|
ui.mu.RUnlock()
|
|
|
|
// Check that other modals cannot be activated when add wizard is active
|
|
// This is enforced in the handlers, not in state - we're testing the state setup
|
|
}
|
|
|
|
// TestWizardMutualExclusion_BenchmarkBlocksOthers tests that having benchmark active blocks other modals
|
|
func TestWizardMutualExclusion_BenchmarkBlocksOthers(t *testing.T) {
|
|
ui := NewBubbleTeaUI(nil, "1.0.0")
|
|
|
|
// Add a forward
|
|
fwd := &config.Forward{
|
|
Resource: "pod/my-app",
|
|
Port: 8080,
|
|
LocalPort: 8080,
|
|
}
|
|
ui.AddForward("test-id", fwd)
|
|
|
|
// Activate benchmark
|
|
ui.mu.Lock()
|
|
ui.viewMode = ViewModeBenchmark
|
|
ui.benchmarkState = newBenchmarkState("test-id", "my-app", 8080)
|
|
ui.mu.Unlock()
|
|
|
|
ui.mu.RLock()
|
|
assert.NotNil(t, ui.benchmarkState)
|
|
assert.Equal(t, ViewModeBenchmark, ui.viewMode)
|
|
ui.mu.RUnlock()
|
|
}
|
|
|
|
// TestWizardMutualExclusion_HTTPLogBlocksOthers tests that having HTTP log view active blocks other modals
|
|
func TestWizardMutualExclusion_HTTPLogBlocksOthers(t *testing.T) {
|
|
ui := NewBubbleTeaUI(nil, "1.0.0")
|
|
|
|
// Add a forward
|
|
fwd := &config.Forward{
|
|
Resource: "pod/my-app",
|
|
Port: 8080,
|
|
LocalPort: 8080,
|
|
}
|
|
ui.AddForward("test-id", fwd)
|
|
|
|
// Activate HTTP log view
|
|
ui.mu.Lock()
|
|
ui.viewMode = ViewModeHTTPLog
|
|
ui.httpLogState = newHTTPLogState("test-id", "my-app")
|
|
ui.mu.Unlock()
|
|
|
|
ui.mu.RLock()
|
|
assert.NotNil(t, ui.httpLogState)
|
|
assert.Equal(t, ViewModeHTTPLog, ui.viewMode)
|
|
ui.mu.RUnlock()
|
|
}
|
|
|
|
// TestWizardMutualExclusion_CheckActiveModal tests the modal activity check logic
|
|
func TestWizardMutualExclusion_CheckActiveModal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(*BubbleTeaUI)
|
|
expectActive bool
|
|
activeModalStr string
|
|
}{
|
|
{
|
|
name: "no modal active",
|
|
setupFunc: func(ui *BubbleTeaUI) {},
|
|
expectActive: false,
|
|
activeModalStr: "none",
|
|
},
|
|
{
|
|
name: "add wizard active",
|
|
setupFunc: func(ui *BubbleTeaUI) {
|
|
ui.addWizard = newAddWizardState()
|
|
},
|
|
expectActive: true,
|
|
activeModalStr: "addWizard",
|
|
},
|
|
{
|
|
name: "remove wizard active",
|
|
setupFunc: func(ui *BubbleTeaUI) {
|
|
ui.removeWizard = &RemoveWizardState{}
|
|
},
|
|
expectActive: true,
|
|
activeModalStr: "removeWizard",
|
|
},
|
|
{
|
|
name: "benchmark active",
|
|
setupFunc: func(ui *BubbleTeaUI) {
|
|
ui.benchmarkState = newBenchmarkState("id", "alias", 8080)
|
|
},
|
|
expectActive: true,
|
|
activeModalStr: "benchmark",
|
|
},
|
|
{
|
|
name: "http log active",
|
|
setupFunc: func(ui *BubbleTeaUI) {
|
|
ui.httpLogState = newHTTPLogState("id", "alias")
|
|
},
|
|
expectActive: true,
|
|
activeModalStr: "httpLog",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ui := NewBubbleTeaUI(nil, "1.0.0")
|
|
|
|
ui.mu.Lock()
|
|
tt.setupFunc(ui)
|
|
ui.mu.Unlock()
|
|
|
|
ui.mu.RLock()
|
|
hasActiveModal := ui.addWizard != nil ||
|
|
ui.removeWizard != nil ||
|
|
ui.benchmarkState != nil ||
|
|
ui.httpLogState != nil
|
|
ui.mu.RUnlock()
|
|
|
|
assert.Equal(t, tt.expectActive, hasActiveModal, "Modal activity check failed for: %s", tt.activeModalStr)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWizardCleanup_AddWizardReset tests that add wizard state is properly cleaned up
|
|
func TestWizardCleanup_AddWizardReset(t *testing.T) {
|
|
ui := NewBubbleTeaUI(nil, "1.0.0")
|
|
|
|
// Set up wizard with various state
|
|
ui.mu.Lock()
|
|
ui.viewMode = ViewModeAddWizard
|
|
ui.addWizard = newAddWizardState()
|
|
ui.addWizard.step = StepSelectNamespace
|
|
ui.addWizard.selectedContext = "prod"
|
|
ui.addWizard.contexts = []string{"prod", "staging"}
|
|
ui.mu.Unlock()
|
|
|
|
// Simulate cleanup (like pressing Esc)
|
|
ui.mu.Lock()
|
|
ui.viewMode = ViewModeMain
|
|
ui.addWizard = nil
|
|
ui.mu.Unlock()
|
|
|
|
ui.mu.RLock()
|
|
assert.Nil(t, ui.addWizard)
|
|
assert.Equal(t, ViewModeMain, ui.viewMode)
|
|
ui.mu.RUnlock()
|
|
}
|
|
|
|
// TestWizardCleanup_BenchmarkReset tests that benchmark state is properly cleaned up
|
|
func TestWizardCleanup_BenchmarkReset(t *testing.T) {
|
|
ui := NewBubbleTeaUI(nil, "1.0.0")
|
|
|
|
cancelled := false
|
|
|
|
// Set up benchmark with cancel function
|
|
ui.mu.Lock()
|
|
ui.viewMode = ViewModeBenchmark
|
|
ui.benchmarkState = newBenchmarkState("id", "alias", 8080)
|
|
ui.benchmarkState.running = true
|
|
ui.benchmarkState.cancelFunc = func() { cancelled = true }
|
|
ui.mu.Unlock()
|
|
|
|
// Simulate cleanup with cancel
|
|
ui.mu.Lock()
|
|
if ui.benchmarkState.cancelFunc != nil {
|
|
ui.benchmarkState.cancelFunc()
|
|
}
|
|
ui.viewMode = ViewModeMain
|
|
ui.benchmarkState = nil
|
|
ui.mu.Unlock()
|
|
|
|
assert.True(t, cancelled, "Cancel function should have been called")
|
|
|
|
ui.mu.RLock()
|
|
assert.Nil(t, ui.benchmarkState)
|
|
assert.Equal(t, ViewModeMain, ui.viewMode)
|
|
ui.mu.RUnlock()
|
|
}
|
|
|
|
// TestWizardCleanup_HTTPLogReset tests that HTTP log state is properly cleaned up
|
|
func TestWizardCleanup_HTTPLogReset(t *testing.T) {
|
|
ui := NewBubbleTeaUI(nil, "1.0.0")
|
|
|
|
cleanupCalled := false
|
|
|
|
// Set up HTTP log with cleanup function
|
|
ui.mu.Lock()
|
|
ui.viewMode = ViewModeHTTPLog
|
|
ui.httpLogState = newHTTPLogState("id", "alias")
|
|
ui.httpLogState.entries = []HTTPLogEntry{{Method: "GET", Path: "/"}}
|
|
ui.httpLogCleanup = func() { cleanupCalled = true }
|
|
ui.mu.Unlock()
|
|
|
|
// Simulate cleanup
|
|
ui.mu.Lock()
|
|
if ui.httpLogCleanup != nil {
|
|
ui.httpLogCleanup()
|
|
ui.httpLogCleanup = nil
|
|
}
|
|
ui.viewMode = ViewModeMain
|
|
ui.httpLogState = nil
|
|
ui.mu.Unlock()
|
|
|
|
assert.True(t, cleanupCalled, "Cleanup function should have been called")
|
|
|
|
ui.mu.RLock()
|
|
assert.Nil(t, ui.httpLogState)
|
|
assert.Nil(t, ui.httpLogCleanup)
|
|
assert.Equal(t, ViewModeMain, ui.viewMode)
|
|
ui.mu.RUnlock()
|
|
}
|
|
|
|
// TestViewModeValues tests view mode constants
|
|
func TestViewModeValues(t *testing.T) {
|
|
assert.Equal(t, ViewMode(0), ViewModeMain)
|
|
assert.Equal(t, ViewMode(1), ViewModeAddWizard)
|
|
assert.Equal(t, ViewMode(2), ViewModeRemoveWizard)
|
|
assert.Equal(t, ViewMode(3), ViewModeBenchmark)
|
|
assert.Equal(t, ViewMode(4), ViewModeHTTPLog)
|
|
}
|
|
|
|
// TestRemoveWizardState_Selection tests remove wizard selection logic
|
|
func TestRemoveWizardState_Selection(t *testing.T) {
|
|
wizard := &RemoveWizardState{
|
|
forwards: []RemovableForward{
|
|
{ID: "a", Alias: "app-a"},
|
|
{ID: "b", Alias: "app-b"},
|
|
{ID: "c", Alias: "app-c"},
|
|
},
|
|
selected: make(map[int]bool),
|
|
cursor: 0,
|
|
}
|
|
|
|
// Toggle selection
|
|
wizard.toggleSelection()
|
|
assert.True(t, wizard.selected[0])
|
|
|
|
// Move and toggle
|
|
wizard.moveCursor(1)
|
|
wizard.toggleSelection()
|
|
assert.True(t, wizard.selected[1])
|
|
|
|
// Check selected count
|
|
assert.Equal(t, 2, wizard.getSelectedCount())
|
|
|
|
// Get selected forwards
|
|
selected := wizard.getSelectedForwards()
|
|
assert.Len(t, selected, 2)
|
|
}
|
|
|
|
// TestRemoveWizardState_SelectAll tests select all functionality
|
|
func TestRemoveWizardState_SelectAll(t *testing.T) {
|
|
wizard := &RemoveWizardState{
|
|
forwards: []RemovableForward{
|
|
{ID: "a"},
|
|
{ID: "b"},
|
|
{ID: "c"},
|
|
},
|
|
selected: make(map[int]bool),
|
|
}
|
|
|
|
wizard.selectAll()
|
|
|
|
assert.Equal(t, 3, wizard.getSelectedCount())
|
|
assert.True(t, wizard.selected[0])
|
|
assert.True(t, wizard.selected[1])
|
|
assert.True(t, wizard.selected[2])
|
|
}
|
|
|
|
// TestRemoveWizardState_SelectNone tests deselect all functionality
|
|
func TestRemoveWizardState_SelectNone(t *testing.T) {
|
|
wizard := &RemoveWizardState{
|
|
forwards: []RemovableForward{
|
|
{ID: "a"},
|
|
{ID: "b"},
|
|
{ID: "c"},
|
|
},
|
|
selected: map[int]bool{0: true, 1: true, 2: true},
|
|
}
|
|
|
|
wizard.selectNone()
|
|
|
|
assert.Equal(t, 0, wizard.getSelectedCount())
|
|
}
|
|
|
|
// TestRemoveWizardState_MoveCursor tests cursor movement in remove wizard
|
|
func TestRemoveWizardState_MoveCursor(t *testing.T) {
|
|
wizard := &RemoveWizardState{
|
|
forwards: []RemovableForward{
|
|
{ID: "a"},
|
|
{ID: "b"},
|
|
{ID: "c"},
|
|
},
|
|
selected: make(map[int]bool),
|
|
cursor: 0,
|
|
}
|
|
|
|
// Move down
|
|
wizard.moveCursor(1)
|
|
assert.Equal(t, 1, wizard.cursor)
|
|
|
|
// Move down again
|
|
wizard.moveCursor(1)
|
|
assert.Equal(t, 2, wizard.cursor)
|
|
|
|
// Cannot go past end
|
|
wizard.moveCursor(1)
|
|
assert.Equal(t, 2, wizard.cursor)
|
|
|
|
// Move up
|
|
wizard.moveCursor(-1)
|
|
assert.Equal(t, 1, wizard.cursor)
|
|
|
|
// Cannot go below 0
|
|
wizard.moveCursor(-10)
|
|
assert.Equal(t, 0, wizard.cursor)
|
|
}
|
|
|
|
// TestRemoveWizardState_ConfirmationMode tests confirmation mode cursor
|
|
func TestRemoveWizardState_ConfirmationMode(t *testing.T) {
|
|
wizard := &RemoveWizardState{
|
|
forwards: []RemovableForward{{ID: "a"}},
|
|
selected: map[int]bool{0: true},
|
|
confirming: true,
|
|
confirmCursor: 0,
|
|
}
|
|
|
|
// In confirmation mode, cursor moves between Yes/No
|
|
wizard.moveCursor(1)
|
|
assert.Equal(t, 1, wizard.confirmCursor)
|
|
|
|
// Cannot go past 1
|
|
wizard.moveCursor(1)
|
|
assert.Equal(t, 1, wizard.confirmCursor)
|
|
|
|
// Move back
|
|
wizard.moveCursor(-1)
|
|
assert.Equal(t, 0, wizard.confirmCursor)
|
|
|
|
// Cannot go below 0
|
|
wizard.moveCursor(-1)
|
|
assert.Equal(t, 0, wizard.confirmCursor)
|
|
}
|
|
|
|
// TestRemoveWizardState_ToggleInConfirmationMode tests that toggle is disabled in confirmation mode
|
|
func TestRemoveWizardState_ToggleInConfirmationMode(t *testing.T) {
|
|
wizard := &RemoveWizardState{
|
|
forwards: []RemovableForward{{ID: "a"}},
|
|
selected: make(map[int]bool),
|
|
confirming: true,
|
|
}
|
|
|
|
// Toggle should be no-op in confirmation mode
|
|
wizard.toggleSelection()
|
|
assert.Equal(t, 0, wizard.getSelectedCount())
|
|
}
|