Files
kportal/internal/forward/portcheck_test.go
T

230 lines
7.3 KiB
Go

package forward
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPortChecker_IsAvailable(t *testing.T) {
pc := NewPortChecker()
// Test that isPortAvailable returns a bool
// We use a high port that's likely to be available
result := pc.isPortAvailable(54321)
assert.IsType(t, false, result, "isPortAvailable should return bool")
}
func TestPortChecker_CheckAvailability_EmptyPorts(t *testing.T) {
pc := NewPortChecker()
// Test with empty ports slice
conflicts := pc.CheckAvailability([]int{}, nil)
assert.Empty(t, conflicts, "should return empty conflicts for empty ports")
// Test with nil exclude map
conflicts = pc.CheckAvailability([]int{}, nil)
assert.Empty(t, conflicts, "should return empty conflicts for nil exclude map")
}
func TestPortChecker_CheckAvailability_ExcludeMap(t *testing.T) {
pc := NewPortChecker()
// Create a listener to occupy a port
listener, err := net.Listen("tcp", ":0")
assert.NoError(t, err, "should create listener")
defer listener.Close()
// Get the port that's now occupied
addr := listener.Addr().(*net.TCPAddr)
occupiedPort := addr.Port
// Test that the occupied port shows as conflicted
conflicts := pc.CheckAvailability([]int{occupiedPort}, nil)
assert.Len(t, conflicts, 1, "should detect conflict for occupied port")
assert.Equal(t, occupiedPort, conflicts[0].Port)
// Test that skipPorts map excludes the port from conflict detection
skipPorts := map[int]bool{
occupiedPort: true,
}
conflicts = pc.CheckAvailability([]int{occupiedPort}, skipPorts)
assert.Empty(t, conflicts, "should skip ports in exclude map")
}
func TestPortChecker_CheckAvailability_MultipleSkipPorts(t *testing.T) {
pc := NewPortChecker()
// Create multiple listeners
listener1, err := net.Listen("tcp", ":0")
assert.NoError(t, err)
defer listener1.Close()
listener2, err := net.Listen("tcp", ":0")
assert.NoError(t, err)
defer listener2.Close()
port1 := listener1.Addr().(*net.TCPAddr).Port
port2 := listener2.Addr().(*net.TCPAddr).Port
// Test with both ports occupied
conflicts := pc.CheckAvailability([]int{port1, port2}, nil)
assert.Len(t, conflicts, 2, "should detect both conflicts")
// Test excluding one port
skipPorts := map[int]bool{port1: true}
conflicts = pc.CheckAvailability([]int{port1, port2}, skipPorts)
assert.Len(t, conflicts, 1, "should detect only non-excluded port")
assert.Equal(t, port2, conflicts[0].Port)
// Test excluding both ports
skipPorts = map[int]bool{port1: true, port2: true}
conflicts = pc.CheckAvailability([]int{port1, port2}, skipPorts)
assert.Empty(t, conflicts, "should skip all excluded ports")
}
func TestPortChecker_GetProcessInfo(t *testing.T) {
pc := NewPortChecker()
// Test that getProcessUsingPort returns a string
// We don't test actual process detection to avoid flakiness
result := pc.getProcessUsingPort(12345)
assert.IsType(t, "", result, "getProcessUsingPort should return string")
assert.NotEmpty(t, result, "should return some string (even if 'unknown')")
}
func TestFormatConflicts_Empty(t *testing.T) {
// Test with empty conflicts
output := FormatConflicts([]PortConflict{})
assert.Empty(t, output, "should return empty string for no conflicts")
}
func TestFormatConflicts_SingleConflict(t *testing.T) {
conflicts := []PortConflict{
{
Port: 8080,
Resource: "dev/default/pod/my-app:8080",
UsedBy: "nginx (PID 1234)",
},
}
output := FormatConflicts(conflicts)
assert.NotEmpty(t, output, "should return non-empty output")
assert.Contains(t, output, "Port Conflicts Detected", "should contain header")
assert.Contains(t, output, "Port 8080", "should contain port number")
assert.Contains(t, output, "dev/default/pod/my-app:8080", "should contain resource")
assert.Contains(t, output, "nginx (PID 1234)", "should contain process info")
}
func TestFormatConflicts_MultipleConflicts(t *testing.T) {
conflicts := []PortConflict{
{
Port: 8080,
Resource: "dev/default/pod/app1:8080",
UsedBy: "nginx (PID 1234)",
},
{
Port: 5432,
Resource: "prod/database/service/postgres:5432",
UsedBy: "postgres (PID 5678)",
},
}
output := FormatConflicts(conflicts)
assert.NotEmpty(t, output, "should return non-empty output")
assert.Contains(t, output, "Port Conflicts Detected", "should contain header")
assert.Contains(t, output, "Port 8080", "should contain first port")
assert.Contains(t, output, "Port 5432", "should contain second port")
assert.Contains(t, output, "nginx (PID 1234)", "should contain first process")
assert.Contains(t, output, "postgres (PID 5678)", "should contain second process")
assert.Contains(t, output, "Action:", "should contain action message")
}
func TestFormatConflicts_WithoutResource(t *testing.T) {
conflicts := []PortConflict{
{
Port: 8080,
UsedBy: "nginx (PID 1234)",
},
}
output := FormatConflicts(conflicts)
assert.NotEmpty(t, output, "should return non-empty output")
assert.Contains(t, output, "Port 8080", "should contain port")
assert.Contains(t, output, "nginx (PID 1234)", "should contain process info")
// Should not crash or include empty "Needed for:" line
assert.NotContains(t, output, "Needed for: \n", "should not have empty resource line")
}
func TestPortConflict_Structure(t *testing.T) {
// Test that PortConflict structure works correctly
conflict := PortConflict{
Port: 8080,
Resource: "dev/default/pod/app:8080",
UsedBy: "nginx (PID 1234)",
}
assert.Equal(t, 8080, conflict.Port)
assert.Equal(t, "dev/default/pod/app:8080", conflict.Resource)
assert.Equal(t, "nginx (PID 1234)", conflict.UsedBy)
}
func TestNewPortChecker(t *testing.T) {
pc := NewPortChecker()
assert.NotNil(t, pc, "NewPortChecker should return non-nil instance")
}
func TestPortChecker_PortAvailability_Integration(t *testing.T) {
pc := NewPortChecker()
// Create a listener to occupy a port
listener, err := net.Listen("tcp", ":0")
assert.NoError(t, err, "should create listener")
defer listener.Close()
// Get the occupied port
occupiedPort := listener.Addr().(*net.TCPAddr).Port
// Test that the port is correctly detected as unavailable
available := pc.isPortAvailable(occupiedPort)
assert.False(t, available, "occupied port should not be available")
// Close the listener
listener.Close()
// The port should now be available (though there might be a brief delay)
// We don't assert this to avoid flakiness in CI environments
}
func TestPortChecker_CheckAvailability_AvailablePorts(t *testing.T) {
pc := NewPortChecker()
// Use high port numbers that are very unlikely to be in use
// This test might be slightly flaky in unusual environments, but should be stable
unlikelyPorts := []int{54321, 54322, 54323}
conflicts := pc.CheckAvailability(unlikelyPorts, nil)
// Most likely all ports will be available
// The function returns nil or empty slice when there are no conflicts
// We just verify the function executes without panicking
_ = conflicts
}
func TestFormatConflicts_Formatting(t *testing.T) {
conflicts := []PortConflict{
{
Port: 8080,
Resource: "dev/default/pod/my-app:8080",
UsedBy: "nginx (PID 1234)",
},
}
output := FormatConflicts(conflicts)
// Check formatting details
assert.Contains(t, output, "==================================================", "should contain separator line")
assert.Contains(t, output, "\n", "should contain newlines")
}