vibetunnel/mac/VibeTunnelTests/NetworkUtilityTests.swift
Peter Steinberger 0b4f03ef78 test: modernize Mac tests to follow Swift Testing best practices
- Convert TerminalLaunchTests from XCTest to Swift Testing framework
- Update ServerManagerTests to handle test environment limitations
- Modernize SessionMonitorTests with class-based suite and proper lifecycle
- Fix test compilation issues and improve test resilience
- Use #expect and #require macros instead of XCTAssert
- Add proper tags for test organization
- Implement parameterized tests where appropriate
- Handle missing server binary gracefully in test environment

All tests now pass except NgrokServiceTests which requires auth token.
2025-06-22 07:42:28 +02:00

282 lines
8.7 KiB
Swift

import Foundation
import Network
import Testing
@testable import VibeTunnel
// MARK: - Mock Network Utility for Testing
@MainActor
enum MockNetworkUtility {
static var mockLocalIP: String?
static var mockAllIPs: [String] = []
static var shouldFailGetAddresses = false
static func reset() {
mockLocalIP = nil
mockAllIPs = []
shouldFailGetAddresses = false
}
static func getLocalIPAddress() -> String? {
if shouldFailGetAddresses { return nil }
return mockLocalIP
}
static func getAllIPAddresses() -> [String] {
if shouldFailGetAddresses { return [] }
return mockAllIPs
}
}
// MARK: - Network Utility Tests
@Suite("Network Utility Tests", .tags(.networking))
struct NetworkUtilityTests {
// MARK: - Local IP Address Tests
@Test("Get local IP address")
func testGetLocalIPAddress() throws {
// Test real implementation
let localIP = NetworkUtility.getLocalIPAddress()
// On a real system, we should get some IP address
// It might be nil in some test environments
if let ip = localIP {
#expect(!ip.isEmpty)
// Should be a valid IPv4 address format
let components = ip.split(separator: ".")
#expect(components.count == 4)
// Each component should be a valid number 0-255
for component in components {
if let num = Int(component) {
#expect(num >= 0 && num <= 255)
} else {
Issue.record("Invalid IP component: \(component)")
}
}
}
}
@Test("Local IP address preferences")
func localIPPreferences() throws {
// Test that we prefer local network addresses
let mockIPs = [
"192.168.1.100", // Preferred - local network
"10.0.0.50", // Preferred - local network
"172.16.0.10", // Preferred - local network
"8.8.8.8", // Not preferred - public IP
"127.0.0.1" // Should be filtered out - loopback
]
// Verify our preference logic
for ip in mockIPs {
if ip.hasPrefix("192.168.") || ip.hasPrefix("10.") || ip.hasPrefix("172.") {
#expect(Bool(true), "IP \(ip) should be preferred")
}
}
}
@Test("Get all IP addresses")
func testGetAllIPAddresses() throws {
let allIPs = NetworkUtility.getAllIPAddresses()
// Should return array (might be empty in test environment)
#expect(allIPs.count >= 0)
// If we have IPs, verify they're valid
for ip in allIPs {
#expect(!ip.isEmpty)
// Should not contain loopback
#expect(!ip.hasPrefix("127."))
// Should be valid IPv4 format
let components = ip.split(separator: ".")
#expect(components.count == 4)
}
}
// MARK: - Network Interface Tests
@Test("Network interface filtering")
func interfaceFiltering() throws {
// Test that we filter interfaces correctly
let allIPs = NetworkUtility.getAllIPAddresses()
// Should not contain any loopback addresses
for ip in allIPs {
#expect(!ip.hasPrefix("127.0.0"))
#expect(ip != "::1") // IPv6 loopback
}
}
@Test("IPv4 address validation")
func iPv4Validation() throws {
let testIPs = [
("192.168.1.1", true),
("10.0.0.1", true),
("172.16.0.1", true),
("256.1.1.1", false), // Invalid - component > 255
("1.1.1", false), // Invalid - only 3 components
("1.1.1.1.1", false), // Invalid - too many components
("a.b.c.d", false), // Invalid - non-numeric
("", false) // Invalid - empty
]
for (ip, shouldBeValid) in testIPs {
let components = ip.split(separator: ".")
let isValid = components.count == 4 && components.allSatisfy { component in
if let num = Int(component) {
return num >= 0 && num <= 255
}
return false
}
#expect(isValid == shouldBeValid, "IP \(ip) validation failed")
}
}
// MARK: - Edge Cases Tests
@Test("Handle no network interfaces")
@MainActor
func noNetworkInterfaces() throws {
// In a real scenario where no interfaces are available
// the functions should return nil/empty array gracefully
MockNetworkUtility.shouldFailGetAddresses = true
#expect(MockNetworkUtility.getLocalIPAddress() == nil)
#expect(MockNetworkUtility.getAllIPAddresses().isEmpty)
MockNetworkUtility.reset()
}
@Test("Multiple network interfaces")
@MainActor
func multipleInterfaces() throws {
// When multiple interfaces exist, we should get all of them
MockNetworkUtility.mockAllIPs = [
"192.168.1.100", // Wi-Fi
"192.168.2.50", // Ethernet
"10.0.0.100" // VPN
]
let allIPs = MockNetworkUtility.getAllIPAddresses()
#expect(allIPs.count == 3)
#expect(Set(allIPs).count == 3) // All unique
MockNetworkUtility.reset()
}
// MARK: - Platform-Specific Tests
@Test("macOS network interface names")
func macOSInterfaceNames() throws {
// On macOS, typical interface names are:
// en0 - Primary network interface (often Wi-Fi)
// en1 - Secondary network interface (often Ethernet)
// en2, en3, etc. - Additional interfaces
// This test documents expected behavior
let expectedPrefixes = ["en"]
for prefix in expectedPrefixes {
#expect(prefix.hasPrefix("en"), "Network interfaces should start with 'en' on macOS")
}
}
// MARK: - Performance Tests
@Test("Performance of IP address retrieval", .tags(.performance))
func iPRetrievalPerformance() async throws {
// Measure time to get IP addresses
let start = Date()
for _ in 0..<10 {
_ = NetworkUtility.getLocalIPAddress()
}
let elapsed = Date().timeIntervalSince(start)
// Should be reasonably fast (< 1 second for 10 calls)
#expect(elapsed < 1.0, "IP retrieval took too long: \(elapsed) seconds")
}
// MARK: - Concurrent Access Tests
@Test("Concurrent IP address retrieval", .tags(.concurrency))
func concurrentAccess() async throws {
await withTaskGroup(of: String?.self) { group in
// Multiple concurrent calls
for _ in 0..<10 {
group.addTask {
NetworkUtility.getLocalIPAddress()
}
}
var results: [String?] = []
for await result in group {
results.append(result)
}
// All calls should return the same value
let uniqueResults = Set(results.compactMap(\.self))
#expect(uniqueResults.count <= 1, "Concurrent calls returned different IPs")
}
}
// MARK: - Integration Tests
@Test("Network utility with system network state", .tags(.integration))
func systemNetworkState() throws {
let localIP = NetworkUtility.getLocalIPAddress()
let allIPs = NetworkUtility.getAllIPAddresses()
// If we have a local IP, it should be in the all IPs list
if let localIP {
#expect(allIPs.contains(localIP), "Local IP should be in all IPs list")
}
// All IPs should be unique
#expect(Set(allIPs).count == allIPs.count, "IP addresses should be unique")
}
@Test("IP address format consistency")
func iPAddressFormat() throws {
let allIPs = NetworkUtility.getAllIPAddresses()
for ip in allIPs {
// Should not have leading/trailing whitespace
#expect(ip == ip.trimmingCharacters(in: .whitespacesAndNewlines))
// Should not contain port numbers
#expect(!ip.contains(":"))
// Should be standard dotted decimal notation
#expect(ip.contains("."))
}
}
// MARK: - Mock Tests
@Test("Mock network utility behavior")
@MainActor
func mockUtility() throws {
// Set up mock
MockNetworkUtility.mockLocalIP = "192.168.1.100"
MockNetworkUtility.mockAllIPs = ["192.168.1.100", "10.0.0.50"]
#expect(MockNetworkUtility.getLocalIPAddress() == "192.168.1.100")
#expect(MockNetworkUtility.getAllIPAddresses().count == 2)
// Test failure scenario
MockNetworkUtility.shouldFailGetAddresses = true
#expect(MockNetworkUtility.getLocalIPAddress() == nil)
#expect(MockNetworkUtility.getAllIPAddresses().isEmpty)
MockNetworkUtility.reset()
}
}