import Foundation import Network import Testing @testable import VibeTunnel @Suite("BonjourDiscoveryService Tests") struct BonjourDiscoveryServiceTests { @Suite("Core Functionality") struct CoreFunctionality { @Test("Start discovery initiates service properly") @MainActor func testStartDiscovery() async { // Given let service = BonjourDiscoveryService.shared service.stopDiscovery() // Ensure clean state // When service.startDiscovery() // Allow time for service to initialize // Network browser state changes are asynchronous try? await Task.sleep(nanoseconds: 500_000_000) // 0.5 seconds // Then - In test environment, the browser may not reach "ready" state // without actual network discovery, so we just verify the service // can be started without crashing // The service creates a browser internally when startDiscovery is called // Cleanup service.stopDiscovery() #expect(service.isDiscovering == false) } @Test("Stop discovery cancels and clears state") @MainActor func testStopDiscovery() async { // Given let service = BonjourDiscoveryService.shared service.startDiscovery() try? await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds // When service.stopDiscovery() // Then #expect(service.isDiscovering == false) // Note: The service doesn't clear discoveredServers on stop, // it only clears them on the next startDiscovery call } @Test("Service handles discovery state correctly") @MainActor func discoveryStateManagement() async { // Given let service = BonjourDiscoveryService.shared service.stopDiscovery() // Ensure clean state // When - Start discovery service.startDiscovery() // Allow time for discovery to start try? await Task.sleep(nanoseconds: 500_000_000) // 0.5 seconds // Then - Verify service can manage discovery state // Note: In test environment without actual mDNS services, // we just verify the service can start discovery mode let initialServerCount = service.discoveredServers.count // Cleanup service.stopDiscovery() // Verify discovery was stopped #expect(service.isDiscovering == false) } } @Suite("Service Resolution") struct ServiceResolution { @Test("Resolve service can handle server data") @MainActor func resolveService() async { // Given let service = BonjourDiscoveryService.shared let testServer = DiscoveredServer( name: "TestServer", host: "192.168.1.100", port: 4_020, metadata: [:] ) // When - Test that DiscoveredServer structure works correctly let serverId = testServer.id let displayName = testServer.displayName // Then - Verify server data is correctly structured #expect(!serverId.uuidString.isEmpty) #expect(displayName == "TestServer") #expect(testServer.host == "192.168.1.100") #expect(testServer.port == 4_020) } @Test("Resolve service handles IPv6 addresses correctly") @MainActor func resolveServiceIPv6() async { // Given let ipv6Host = "fe80::1%en0" // IPv6 with interface // Simulate resolution with IPv6 let testServer = DiscoveredServer( name: "IPv6Server", host: ipv6Host, port: 4_020, metadata: [:] ) // When processing, the service should strip the interface let cleanedHost = ipv6Host.components(separatedBy: "%").first ?? ipv6Host // Then #expect(cleanedHost == "fe80::1") #expect(testServer.host == ipv6Host) // Original host preserved } @Test("Resolve service uses ID-based lookup to avoid race conditions") @MainActor func resolveServiceRaceCondition() async { // Given let server1 = DiscoveredServer(name: "Server1", host: "192.168.1.1", port: 4_020, metadata: [:]) let server2 = DiscoveredServer(name: "Server2", host: "192.168.1.2", port: 4_020, metadata: [:]) let server3 = DiscoveredServer(name: "Server3", host: "192.168.1.3", port: 4_020, metadata: [:]) // Create a test array simulating discovered servers let servers = [server1, server2, server3] let originalId = server2.id // Simulate removal of server2 (race condition scenario) let updatedServers = [server1, server3] // Then - Verify ID-based lookup handles race condition correctly let foundInOriginal = servers.firstIndex(where: { $0.id == originalId }) let foundInUpdated = updatedServers.firstIndex(where: { $0.id == originalId }) #expect(foundInOriginal != nil) // Server was in original list #expect(foundInUpdated == nil) // Server correctly not found after removal // Verify remaining servers are still accessible by ID #expect(updatedServers.contains(where: { $0.id == server1.id })) #expect(updatedServers.contains(where: { $0.id == server3.id })) } } @Suite("Error Handling") struct ErrorHandling { @Test("Service handles empty browse results") @MainActor func emptyBrowseResults() async { // Given let service = BonjourDiscoveryService.shared // When - Start and stop discovery service.startDiscovery() service.stopDiscovery() // Then - Service should handle empty results gracefully #expect(service.isDiscovering == false) } @Test("Multiple start calls are idempotent") @MainActor func multipleStartCalls() async { // Given let service = BonjourDiscoveryService.shared service.stopDiscovery() // Clean state // When service.startDiscovery() let firstState = service.isDiscovering service.startDiscovery() // Second call service.startDiscovery() // Third call // Then #expect(firstState == service.isDiscovering) // Cleanup service.stopDiscovery() } } }