Files
kportal/internal/ui/mocks_test.go
T
lukaszraczylo 23cd45a3d7 improvements nov2025 pt2 (#13)
* 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
2025-11-26 13:18:50 +00:00

279 lines
6.6 KiB
Go

package ui
import (
"context"
"sync"
"github.com/nvm/kportal/internal/config"
"github.com/nvm/kportal/internal/k8s"
)
// MockDiscovery is a mock implementation of DiscoveryInterface for testing
type MockDiscovery struct {
mu sync.Mutex
// Return values
Contexts []string
CurrentContext string
Namespaces []string
Pods []k8s.PodInfo
PodsWithSelector []k8s.PodInfo
Services []k8s.ServiceInfo
// Errors to return
ListContextsErr error
GetCurrentContextErr error
ListNamespacesErr error
ListPodsErr error
ListPodsWithSelectorErr error
ListServicesErr error
// Call tracking
ListContextsCalls int
GetCurrentContextCalls int
ListNamespacesCalls int
ListPodsCalls int
ListPodsWithSelectorCalls int
ListServicesCalls int
// Captured arguments
LastContextName string
LastNamespace string
LastSelector string
}
func NewMockDiscovery() *MockDiscovery {
return &MockDiscovery{
Contexts: []string{"default", "production", "staging"},
Namespaces: []string{"default", "kube-system"},
}
}
func (m *MockDiscovery) ListContexts() ([]string, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.ListContextsCalls++
return m.Contexts, m.ListContextsErr
}
func (m *MockDiscovery) GetCurrentContext() (string, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.GetCurrentContextCalls++
if m.CurrentContext == "" {
return "default", m.GetCurrentContextErr
}
return m.CurrentContext, m.GetCurrentContextErr
}
func (m *MockDiscovery) ListNamespaces(ctx context.Context, contextName string) ([]string, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.ListNamespacesCalls++
m.LastContextName = contextName
return m.Namespaces, m.ListNamespacesErr
}
func (m *MockDiscovery) ListPods(ctx context.Context, contextName, namespace string) ([]k8s.PodInfo, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.ListPodsCalls++
m.LastContextName = contextName
m.LastNamespace = namespace
return m.Pods, m.ListPodsErr
}
func (m *MockDiscovery) ListPodsWithSelector(ctx context.Context, contextName, namespace, selector string) ([]k8s.PodInfo, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.ListPodsWithSelectorCalls++
m.LastContextName = contextName
m.LastNamespace = namespace
m.LastSelector = selector
return m.PodsWithSelector, m.ListPodsWithSelectorErr
}
func (m *MockDiscovery) ListServices(ctx context.Context, contextName, namespace string) ([]k8s.ServiceInfo, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.ListServicesCalls++
m.LastContextName = contextName
m.LastNamespace = namespace
return m.Services, m.ListServicesErr
}
// MockMutator is a mock implementation of MutatorInterface for testing
type MockMutator struct {
mu sync.Mutex
// Errors to return
AddForwardErr error
RemoveForwardsErr error
RemoveForwardByIDErr error
UpdateForwardErr error
// Call tracking
AddForwardCalls int
RemoveForwardsCalls int
RemoveForwardByIDCalls int
UpdateForwardCalls int
// Captured arguments
LastContextName string
LastNamespaceName string
LastForward config.Forward
LastOldID string
LastRemovedID string
LastPredicate func(ctx, ns string, fwd config.Forward) bool
// Storage for testing
Forwards []struct {
Context string
Namespace string
Forward config.Forward
}
}
func NewMockMutator() *MockMutator {
return &MockMutator{}
}
func (m *MockMutator) AddForward(contextName, namespaceName string, fwd config.Forward) error {
m.mu.Lock()
defer m.mu.Unlock()
m.AddForwardCalls++
m.LastContextName = contextName
m.LastNamespaceName = namespaceName
m.LastForward = fwd
if m.AddForwardErr == nil {
m.Forwards = append(m.Forwards, struct {
Context string
Namespace string
Forward config.Forward
}{contextName, namespaceName, fwd})
}
return m.AddForwardErr
}
func (m *MockMutator) RemoveForwards(predicate func(ctx, ns string, fwd config.Forward) bool) error {
m.mu.Lock()
defer m.mu.Unlock()
m.RemoveForwardsCalls++
m.LastPredicate = predicate
return m.RemoveForwardsErr
}
func (m *MockMutator) RemoveForwardByID(id string) error {
m.mu.Lock()
defer m.mu.Unlock()
m.RemoveForwardByIDCalls++
m.LastRemovedID = id
return m.RemoveForwardByIDErr
}
func (m *MockMutator) UpdateForward(oldID, newContextName, newNamespaceName string, newFwd config.Forward) error {
m.mu.Lock()
defer m.mu.Unlock()
m.UpdateForwardCalls++
m.LastOldID = oldID
m.LastContextName = newContextName
m.LastNamespaceName = newNamespaceName
m.LastForward = newFwd
return m.UpdateForwardErr
}
// MockHTTPLogSubscriber is a mock for HTTP log subscription
type MockHTTPLogSubscriber struct {
mu sync.Mutex
// Subscription tracking
Subscriptions map[string]func(HTTPLogEntry)
CleanupCalls int
// Control
ShouldFail bool
}
func NewMockHTTPLogSubscriber() *MockHTTPLogSubscriber {
return &MockHTTPLogSubscriber{
Subscriptions: make(map[string]func(HTTPLogEntry)),
}
}
// Subscribe returns a cleanup function
func (m *MockHTTPLogSubscriber) Subscribe(forwardID string, callback func(HTTPLogEntry)) func() {
m.mu.Lock()
defer m.mu.Unlock()
m.Subscriptions[forwardID] = callback
return func() {
m.mu.Lock()
defer m.mu.Unlock()
m.CleanupCalls++
delete(m.Subscriptions, forwardID)
}
}
// SendEntry sends an entry to a subscribed callback (for testing)
func (m *MockHTTPLogSubscriber) SendEntry(forwardID string, entry HTTPLogEntry) {
m.mu.Lock()
callback, exists := m.Subscriptions[forwardID]
m.mu.Unlock()
if exists && callback != nil {
callback(entry)
}
}
// GetSubscriberFunc returns the function signature expected by the UI
func (m *MockHTTPLogSubscriber) GetSubscriberFunc() HTTPLogSubscriber {
return func(forwardID string, callback func(entry HTTPLogEntry)) func() {
return m.Subscribe(forwardID, callback)
}
}
// MockToggleCallback tracks toggle callback invocations
type MockToggleCallback struct {
mu sync.Mutex
Calls []struct {
ID string
Enable bool
}
}
func NewMockToggleCallback() *MockToggleCallback {
return &MockToggleCallback{}
}
func (m *MockToggleCallback) Callback(id string, enable bool) {
m.mu.Lock()
defer m.mu.Unlock()
m.Calls = append(m.Calls, struct {
ID string
Enable bool
}{id, enable})
}
func (m *MockToggleCallback) GetFunc() func(string, bool) {
return m.Callback
}
func (m *MockToggleCallback) CallCount() int {
m.mu.Lock()
defer m.mu.Unlock()
return len(m.Calls)
}
func (m *MockToggleCallback) LastCall() (string, bool, bool) {
m.mu.Lock()
defer m.mu.Unlock()
if len(m.Calls) == 0 {
return "", false, false
}
last := m.Calls[len(m.Calls)-1]
return last.ID, last.Enable, true
}