mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
345 lines
11 KiB
Swift
345 lines
11 KiB
Swift
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
|
|
}
|
|
}
|