import Foundation import Testing @testable import VibeTunnel @Suite("APIClient Tests", .tags(.critical, .networking), .disabled("Needs URL session mocking setup")) struct APIClientTests { let baseURL = URL(string: "http://localhost:8888")! var mockSession: URLSession! init() { // Set up mock URLSession let configuration = URLSessionConfiguration.mockConfiguration mockSession = URLSession(configuration: configuration) } // MARK: - Session Management Tests @Test("Get sessions returns parsed sessions") @MainActor func testGetSessions() async throws { // Arrange MockURLProtocol.requestHandler = { request in #expect(request.url?.path == "/api/sessions") #expect(request.httpMethod == "GET") let data = TestFixtures.sessionsJSON.data(using: .utf8)! return MockURLProtocol.successResponse(for: request.url!, data: data) } // Act let client = createTestClient() let sessions = try await client.getSessions() // Assert #expect(sessions.count == 2) #expect(sessions[0].id == "test-session-123") #expect(sessions[0].isRunning == true) #expect(sessions[1].id == "exited-session-456") #expect(sessions[1].isRunning == false) } @Test("Get sessions handles empty response") @MainActor func getSessionsEmpty() async throws { // Arrange MockURLProtocol.requestHandler = { request in let data = "[]".data(using: .utf8)! return MockURLProtocol.successResponse(for: request.url!, data: data) } // Act let client = createTestClient() let sessions = try await client.getSessions() // Assert #expect(sessions.isEmpty) } @Test("Get sessions handles network error", .tags(.networking)) @MainActor func getSessionsNetworkError() async throws { // Arrange MockURLProtocol.requestHandler = { _ in throw URLError(.notConnectedToInternet) } // Act & Assert let client = createTestClient() do { _ = try await client.getSessions() Issue.record("Expected network error") } catch let error as APIError { guard case .networkError = error else { Issue.record("Expected network error, got \(error)") return } } catch { Issue.record("Unexpected error type: \(error)") } } @Test("Create session sends correct request") @MainActor func testCreateSession() async throws { // Arrange let sessionData = SessionCreateData( command: "/bin/bash", workingDir: "/Users/test", name: "Test Session", cols: 80, rows: 24 ) MockURLProtocol.requestHandler = { request in #expect(request.url?.path == "/api/sessions") #expect(request.httpMethod == "POST") #expect(request.value(forHTTPHeaderField: "Content-Type") == "application/json") // Verify request body if let body = request.httpBody, let json = try? JSONSerialization.jsonObject(with: body) as? [String: Any] { #expect(json["command"] as? String == "/bin/bash") #expect(json["workingDir"] as? String == "/Users/test") #expect(json["name"] as? String == "Test Session") #expect(json["cols"] as? Int == 80) #expect(json["rows"] as? Int == 24) } else { Issue.record("Failed to parse request body") } let responseData = TestFixtures.createSessionJSON.data(using: .utf8)! return MockURLProtocol.successResponse(for: request.url!, data: responseData) } // Act let client = createTestClient() let sessionId = try await client.createSession(sessionData) // Assert #expect(sessionId == "new-session-789") } @Test("Kill session sends DELETE request") @MainActor func testKillSession() async throws { // Arrange let sessionId = "test-session-123" MockURLProtocol.requestHandler = { request in #expect(request.url?.path == "/api/sessions/\(sessionId)") #expect(request.httpMethod == "DELETE") return MockURLProtocol.successResponse(for: request.url!, statusCode: 204) } // Act & Assert (should not throw) let client = createTestClient() try await client.killSession(sessionId) } @Test("Send input posts correct data") @MainActor func testSendInput() async throws { // Arrange let sessionId = "test-session-123" let inputText = "ls -la\n" MockURLProtocol.requestHandler = { request in #expect(request.url?.path == "/api/sessions/\(sessionId)/input") #expect(request.httpMethod == "POST") if let body = request.httpBody, let json = try? JSONSerialization.jsonObject(with: body) as? [String: Any] { #expect(json["data"] as? String == inputText) } else { Issue.record("Failed to parse input request body") } return MockURLProtocol.successResponse(for: request.url!, statusCode: 204) } // Act & Assert (should not throw) let client = createTestClient() try await client.sendInput(sessionId: sessionId, text: inputText) } @Test("Resize terminal sends correct dimensions") @MainActor func testResizeTerminal() async throws { // Arrange let sessionId = "test-session-123" let cols = 120 let rows = 40 MockURLProtocol.requestHandler = { request in #expect(request.url?.path == "/api/sessions/\(sessionId)/resize") #expect(request.httpMethod == "POST") if let body = request.httpBody, let json = try? JSONSerialization.jsonObject(with: body) as? [String: Any] { #expect(json["cols"] as? Int == cols) #expect(json["rows"] as? Int == rows) } else { Issue.record("Failed to parse resize request body") } return MockURLProtocol.successResponse(for: request.url!, statusCode: 204) } // Act & Assert (should not throw) let client = createTestClient() try await client.resizeTerminal(sessionId: sessionId, cols: cols, rows: rows) } // MARK: - Error Handling Tests @Test("Handles 404 error correctly") @MainActor func handle404Error() async throws { // Arrange MockURLProtocol.requestHandler = { request in _ = TestFixtures.errorResponseJSON.data(using: .utf8)! return MockURLProtocol.errorResponse( for: request.url!, statusCode: 404, message: "Session not found" ) } // Act & Assert let client = createTestClient() do { _ = try await client.getSession("nonexistent") Issue.record("Expected error to be thrown") } catch let error as APIError { guard case .serverError(let code, let message) = error else { Issue.record("Expected server error, got \(error)") return } #expect(code == 404) #expect(message == "Session not found") } catch { Issue.record("Unexpected error type: \(error)") } } @Test("Handles 401 unauthorized error") @MainActor func handle401Error() async throws { // Arrange MockURLProtocol.requestHandler = { request in MockURLProtocol.errorResponse(for: request.url!, statusCode: 401) } // Act & Assert let client = createTestClient() do { _ = try await client.getSessions() Issue.record("Expected error to be thrown") } catch let error as APIError { guard case .serverError(let code, _) = error else { Issue.record("Expected server error, got \(error)") return } #expect(code == 401) } catch { Issue.record("Unexpected error type: \(error)") } } @Test("Handles invalid JSON response") @MainActor func handleInvalidJSON() async throws { // Arrange MockURLProtocol.requestHandler = { request in let invalidData = "not json".data(using: .utf8)! return MockURLProtocol.successResponse(for: request.url!, data: invalidData) } // Act & Assert let client = createTestClient() do { _ = try await client.getSessions() Issue.record("Expected decoding error") } catch let error as APIError { guard case .decodingError = error else { Issue.record("Expected decoding error, got \(error)") return } } catch { Issue.record("Unexpected error type: \(error)") } } @Test("Handles connection timeout") @MainActor func connectionTimeout() async throws { // Arrange MockURLProtocol.requestHandler = { _ in throw URLError(.timedOut) } // Act & Assert let client = createTestClient() do { _ = try await client.getSessions() Issue.record("Expected network error") } catch let error as APIError { guard case .networkError = error else { Issue.record("Expected network error, got \(error)") return } } catch { Issue.record("Unexpected error type: \(error)") } } // MARK: - Health Check Tests @Test("Health check returns true for 200 response") @MainActor func healthCheckSuccess() async throws { // Arrange MockURLProtocol.requestHandler = { request in #expect(request.url?.path == "/api/health") return MockURLProtocol.successResponse(for: request.url!) } // Act let client = createTestClient() let isHealthy = try await client.checkHealth() // Assert #expect(isHealthy == true) } @Test("Health check returns false for error response") @MainActor func healthCheckFailure() async throws { // Arrange MockURLProtocol.requestHandler = { request in MockURLProtocol.errorResponse(for: request.url!, statusCode: 500) } // Act let client = createTestClient() let isHealthy = try await client.checkHealth() // Assert #expect(isHealthy == false) } // MARK: - Helper Methods @MainActor private func createTestClient() -> APIClient { // Create a test client with our mock session // Note: This requires modifying APIClient to accept a custom URLSession // For now, we'll use the shared instance and rely on MockURLProtocol APIClient.shared } }