import Foundation @testable import VibeTunnel // This file combines the mock classes needed for BufferWebSocketClientTests /// Mock WebSocket implementation for testing @MainActor class MockWebSocket: WebSocketProtocol { weak var delegate: WebSocketDelegate? // State tracking private(set) var isConnected = false private(set) var lastConnectURL: URL? private(set) var lastConnectHeaders: [String: String]? private(set) var sentMessages: [WebSocketMessage] = [] private(set) var pingCount = 0 private(set) var disconnectCalled = false private(set) var lastDisconnectCode: URLSessionWebSocketTask.CloseCode? private(set) var lastDisconnectReason: Data? // Control test behavior var shouldFailConnection = false var connectionError: Error? var shouldFailSend = false var sendError: Error? var shouldFailPing = false var pingError: Error? // Message simulation private var messageQueue: [WebSocketMessage] = [] private var messageDeliveryTask: Task? func connect(to url: URL, with headers: [String: String]) async throws { lastConnectURL = url lastConnectHeaders = headers if shouldFailConnection { let error = connectionError ?? WebSocketError.connectionFailed throw error } isConnected = true delegate?.webSocketDidConnect(self) // Start delivering queued messages startMessageDelivery() } func send(_ message: WebSocketMessage) async throws { guard isConnected else { throw WebSocketError.connectionFailed } if shouldFailSend { throw sendError ?? WebSocketError.connectionFailed } sentMessages.append(message) } func sendPing() async throws { guard isConnected else { throw WebSocketError.connectionFailed } if shouldFailPing { throw pingError ?? WebSocketError.connectionFailed } pingCount += 1 } func disconnect(with code: URLSessionWebSocketTask.CloseCode, reason: Data?) { disconnectCalled = true lastDisconnectCode = code lastDisconnectReason = reason if isConnected { isConnected = false messageDeliveryTask?.cancel() messageDeliveryTask = nil delegate?.webSocketDidDisconnect(self, closeCode: code, reason: reason) } } // MARK: - Test Helpers /// Simulate receiving a message from the server func simulateMessage(_ message: WebSocketMessage) { guard isConnected else { return } messageQueue.append(message) } /// Simulate multiple messages func simulateMessages(_ messages: [WebSocketMessage]) { guard isConnected else { return } messageQueue.append(contentsOf: messages) } /// Simulate a connection error func simulateError(_ error: Error) { guard isConnected else { return } delegate?.webSocket(self, didFailWithError: error) } /// Simulate server disconnection func simulateDisconnection(closeCode: URLSessionWebSocketTask.CloseCode = .abnormalClosure, reason: Data? = nil) { guard isConnected else { return } isConnected = false messageDeliveryTask?.cancel() messageDeliveryTask = nil delegate?.webSocketDidDisconnect(self, closeCode: closeCode, reason: reason) } /// Clear all tracked state func reset() { isConnected = false lastConnectURL = nil lastConnectHeaders = nil sentMessages.removeAll() pingCount = 0 disconnectCalled = false lastDisconnectCode = nil lastDisconnectReason = nil messageQueue.removeAll() messageDeliveryTask?.cancel() messageDeliveryTask = nil } /// Find sent messages by type func sentStringMessages() -> [String] { sentMessages.compactMap { message in if case .string(let text) = message { return text } return nil } } func sentDataMessages() -> [Data] { sentMessages.compactMap { message in if case .data(let data) = message { return data } return nil } } /// Find sent JSON messages func sentJSONMessages() -> [[String: Any]] { sentStringMessages().compactMap { string in guard let data = string.data(using: .utf8), let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil } return json } } private func startMessageDelivery() { messageDeliveryTask = Task { [weak self] in while !Task.isCancelled { guard let self = self else { break } if !messageQueue.isEmpty { let message = messageQueue.removeFirst() await MainActor.run { self.delegate?.webSocket(self, didReceiveMessage: message) } } // Small delay to simulate network latency try? await Task.sleep(nanoseconds: 10_000_000) // 10ms } } } } /// Mock WebSocket factory for testing @MainActor class MockWebSocketFactory: WebSocketFactory { private(set) var createdWebSockets: [MockWebSocket] = [] func createWebSocket() -> WebSocketProtocol { let webSocket = MockWebSocket() createdWebSockets.append(webSocket) return webSocket } var lastCreatedWebSocket: MockWebSocket? { createdWebSockets.last } func reset() { createdWebSockets.forEach { $0.reset() } createdWebSockets.removeAll() } }