Просмотр исходного кода

Cleanup Request and add descriptions.

Jon Shier 7 лет назад
Родитель
Сommit
76c43824a0
4 измененных файлов с 423 добавлено и 354 удалено
  1. 134 36
      Source/Request.swift
  2. 11 5
      Source/SessionDelegate.swift
  3. 4 4
      Source/SessionManager.swift
  4. 274 309
      Tests/RequestTests.swift

+ 134 - 36
Source/Request.swift

@@ -24,7 +24,9 @@
 
 import Foundation
 
-protocol RequestDelegate: AnyObject {
+public protocol RequestDelegate: AnyObject {
+    var sessionConfiguration: URLSessionConfiguration { get }
+    
     func isRetryingRequest(_ request: Request, ifNecessaryWithError error: Error) -> Bool
 
     func cancelRequest(_ request: Request)
@@ -54,22 +56,16 @@ open class Request {
 
     // MARK: - Initial State
 
-    let id: UUID
-    let underlyingQueue: DispatchQueue
-    let serializationQueue: DispatchQueue
-    let eventMonitor: EventMonitor?
-    weak var delegate: RequestDelegate?
+    public let id: UUID
+    public let underlyingQueue: DispatchQueue
+    public let serializationQueue: DispatchQueue
+    public let eventMonitor: EventMonitor?
+    public weak var delegate: RequestDelegate?
 
-    // TODO: Do we still want to expose the queue(s?) as public API?
-    open let internalQueue: OperationQueue
+    let internalQueue: OperationQueue
 
     // MARK: - Updated State
 
-    let uploadProgress = Progress()
-    let downloadProgress = Progress()
-    var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)?
-    var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)?
-
     private var protectedState: Protector<State> = Protector(.initialized)
     public fileprivate(set) var state: State {
         get { return protectedState.directValue }
@@ -80,21 +76,25 @@ open class Request {
     public var isSuspended: Bool { return state == .suspended }
     public var isInitialized: Bool { return state == .initialized }
 
-    public var retryCount: Int { return protectedTasks.read { max($0.count - 1, 0) } }
-    private(set) var initialRequest: URLRequest?
-    open var request: URLRequest? {
-        return finalTask?.currentRequest
-    }
-    open var response: HTTPURLResponse? {
-        return finalTask?.response as? HTTPURLResponse
-    }
+    // Progress
+
+    public let uploadProgress = Progress()
+    public let downloadProgress = Progress()
+    fileprivate var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)?
+    fileprivate var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)?
+
+    // Requests
+
+    private var protectedRequests = Protector<[URLRequest]>([])
+    public var allRequests: [URLRequest] { return protectedRequests.directValue }
+    public var firstRequest: URLRequest? { return allRequests.first }
+    public var lastRequest: URLRequest? { return allRequests.last }
+    public var request: URLRequest? { return lastRequest }
 
     // Metrics
 
     private var protectedMetrics = Protector<[URLSessionTaskMetrics]>([])
-    public var allMetrics: [URLSessionTaskMetrics] {
-        return protectedMetrics.directValue
-    }
+    public var allMetrics: [URLSessionTaskMetrics] { return protectedMetrics.directValue }
     public var firstMetrics: URLSessionTaskMetrics? { return allMetrics.first }
     public var lastMetrics: URLSessionTaskMetrics? { return allMetrics.last }
     public var metrics: URLSessionTaskMetrics? { return lastMetrics }
@@ -102,22 +102,28 @@ open class Request {
     // Tasks
 
     private var protectedTasks = Protector<[URLSessionTask]>([])
-    public var tasks: [URLSessionTask] {
-        return protectedTasks.directValue
-    }
+    public var tasks: [URLSessionTask] { return protectedTasks.directValue }
     public var initialTask: URLSessionTask? { return tasks.first }
     public var finalTask: URLSessionTask? { return tasks.last }
     public var task: URLSessionTask? { return finalTask }
 
+    public var performedRequests: [URLRequest] { return protectedTasks.read { $0.compactMap { $0.currentRequest } } }
+
+    public var retryCount: Int { return protectedTasks.read { max($0.count - 1, 0) } }
+
+    open var response: HTTPURLResponse? {
+        return finalTask?.response as? HTTPURLResponse
+    }
+
     fileprivate(set) public var error: Error?
     private(set) var credential: URLCredential?
     fileprivate(set) var validators: [() -> Void] = []
 
-    init(id: UUID = UUID(),
-         underlyingQueue: DispatchQueue,
-         serializationQueue: DispatchQueue? = nil,
-         eventMonitor: EventMonitor?,
-         delegate: RequestDelegate) {
+    public init(id: UUID = UUID(),
+              underlyingQueue: DispatchQueue,
+              serializationQueue: DispatchQueue? = nil,
+              eventMonitor: EventMonitor?,
+              delegate: RequestDelegate) {
         self.id = id
         self.underlyingQueue = underlyingQueue
         self.serializationQueue = serializationQueue ?? underlyingQueue
@@ -133,7 +139,7 @@ open class Request {
     // Called from internal queue.
 
     func didCreateURLRequest(_ request: URLRequest) {
-        initialRequest = request
+        protectedRequests.append(request)
 
         eventMonitor?.request(self, didCreateURLRequest: request)
     }
@@ -147,8 +153,8 @@ open class Request {
     }
 
     func didAdaptInitialRequest(_ initialRequest: URLRequest, to adaptedRequest: URLRequest) {
-        self.initialRequest = adaptedRequest
-        // Set initialRequest or something else?
+        protectedRequests.append(adaptedRequest)
+
         eventMonitor?.request(self, didAdaptInitialRequest: initialRequest, to: adaptedRequest)
     }
 
@@ -162,7 +168,7 @@ open class Request {
 
     func didCreateTask(_ task: URLSessionTask) {
         protectedTasks.append(task)
-        // TODO: Reset behavior?
+
         reset()
 
         eventMonitor?.request(self, didCreateTask: task)
@@ -343,6 +349,98 @@ extension Request: Hashable {
     }
 }
 
+extension Request: CustomStringConvertible {
+    public var description: String {
+        guard let request = performedRequests.last ?? lastRequest,
+            let url = request.url,
+            let method = request.httpMethod else { return "No request created yet." }
+
+        let requestDescription = "\(method) \(url.absoluteString)"
+
+        return response.map { "\(requestDescription) (\($0.statusCode))" } ?? requestDescription
+    }
+}
+
+extension Request: CustomDebugStringConvertible {
+    public var debugDescription: String {
+        return cURLRepresentation()
+    }
+    
+    func cURLRepresentation() -> String {
+        guard
+            let request = lastRequest,
+            let url = request.url,
+            let host = url.host,
+            let method = request.httpMethod else { return "$ curl command could not be created" }
+        
+        var components = ["$ curl -v"]
+        
+        components.append("-X \(method)")
+        
+        if let credentialStorage = delegate?.sessionConfiguration.urlCredentialStorage {
+            let protectionSpace = URLProtectionSpace(
+                host: host,
+                port: url.port ?? 0,
+                protocol: url.scheme,
+                realm: host,
+                authenticationMethod: NSURLAuthenticationMethodHTTPBasic
+            )
+            
+            if let credentials = credentialStorage.credentials(for: protectionSpace)?.values {
+                for credential in credentials {
+                    guard let user = credential.user, let password = credential.password else { continue }
+                    components.append("-u \(user):\(password)")
+                }
+            } else {
+                if let credential = credential, let user = credential.user, let password = credential.password {
+                    components.append("-u \(user):\(password)")
+                }
+            }
+        }
+        
+        if let configuration = delegate?.sessionConfiguration, configuration.httpShouldSetCookies {
+            if
+                let cookieStorage = configuration.httpCookieStorage,
+                let cookies = cookieStorage.cookies(for: url), !cookies.isEmpty
+            {
+                let allCookies = cookies.map { "\($0.name)=\($0.value)" }.joined(separator: ";")
+                
+                components.append("-b \"\(allCookies)\"")
+            }
+        }
+        
+        var headers: [String: String] = [:]
+        
+        if let additionalHeaders = delegate?.sessionConfiguration.httpAdditionalHeaders as? [String: String] {
+            for (field, value) in additionalHeaders where field != "Cookie" {
+                headers[field] = value
+            }
+        }
+        
+        if let headerFields = request.allHTTPHeaderFields {
+            for (field, value) in headerFields where field != "Cookie" {
+                headers[field] = value
+            }
+        }
+        
+        for (field, value) in headers {
+            let escapedValue = value.replacingOccurrences(of: "\"", with: "\\\"")
+            components.append("-H \"\(field): \(escapedValue)\"")
+        }
+        
+        if let httpBodyData = request.httpBody, let httpBody = String(data: httpBodyData, encoding: .utf8) {
+            var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"")
+            escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"")
+            
+            components.append("-d \"\(escapedBody)\"")
+        }
+        
+        components.append("\"\(url.absoluteString)\"")
+        
+        return components.joined(separator: " \\\n\t")
+    }
+}
+
 open class DataRequest: Request {
     let convertible: URLRequestConvertible
 

+ 11 - 5
Source/SessionDelegate.swift

@@ -81,7 +81,13 @@ open class SessionDelegate: NSObject {
 }
 
 extension SessionDelegate: RequestDelegate {
-    func isRetryingRequest(_ request: Request, ifNecessaryWithError error: Error) -> Bool {
+    public var sessionConfiguration: URLSessionConfiguration {
+        guard let manager = manager else { fatalError("Attempted to access sessionConfiguration without a manager.") }
+        
+        return manager.session.configuration
+    }
+    
+    public func isRetryingRequest(_ request: Request, ifNecessaryWithError error: Error) -> Bool {
         guard let manager = manager, let retrier = manager.retrier else { return false }
 
         retrier.should(manager, retry: request, with: error) { (shouldRetry, retryInterval) in
@@ -106,7 +112,7 @@ extension SessionDelegate: RequestDelegate {
         return true
     }
 
-    func cancelRequest(_ request: Request) {
+    public func cancelRequest(_ request: Request) {
         queue?.async {
 
             guard let task = self.requestTaskMap[request] else {
@@ -120,7 +126,7 @@ extension SessionDelegate: RequestDelegate {
         }
     }
 
-    func cancelDownloadRequest(_ request: DownloadRequest, byProducingResumeData: @escaping (Data?) -> Void) {
+    public func cancelDownloadRequest(_ request: DownloadRequest, byProducingResumeData: @escaping (Data?) -> Void) {
         queue?.async {
             guard let downloadTask = self.requestTaskMap[request] as? URLSessionDownloadTask else {
                 request.didCancel()
@@ -137,7 +143,7 @@ extension SessionDelegate: RequestDelegate {
         }
     }
 
-    func suspendRequest(_ request: Request) {
+    public func suspendRequest(_ request: Request) {
         queue?.async {
             defer { request.didSuspend() }
 
@@ -147,7 +153,7 @@ extension SessionDelegate: RequestDelegate {
         }
     }
 
-    func resumeRequest(_ request: Request) {
+    public func resumeRequest(_ request: Request) {
         queue?.async {
             defer { request.didResume() }
 

+ 4 - 4
Source/SessionManager.swift

@@ -34,9 +34,9 @@ open class SessionManager {
     open let retrier: RequestRetrier?
     open let serverTrustManager: ServerTrustManager?
 
-    let session: URLSession
-    let eventMonitor: CompositeEventMonitor
-    let defaultEventMonitors: [EventMonitor] = [] // TODO: Create notification event monitor, make default
+    open let session: URLSession
+    open let eventMonitor: CompositeEventMonitor
+    open let defaultEventMonitors: [EventMonitor] = [] // TODO: Create notification event monitor, make default
 
     public init(session: URLSession,
                 delegate: SessionDelegate,
@@ -286,7 +286,7 @@ open class SessionManager {
         case let r as DataRequest: perform(r)
         case let r as UploadRequest: perform(r)
         case let r as DownloadRequest: perform(r)
-        default: fatalError("Attempted to perform nsupported Request subclass: \(type(of: request))")
+        default: fatalError("Attempted to perform unsupported Request subclass: \(type(of: request))")
         }
     }
 

+ 274 - 309
Tests/RequestTests.swift

@@ -339,337 +339,302 @@ class RequestResponseTestCase: BaseTestCase {
 
 // MARK: -
 
-extension Request {
-    fileprivate func preValidate(operation: @escaping () -> Void) -> Self {
-        internalQueue.addOperation {
-            operation()
-        }
+class RequestDescriptionTestCase: BaseTestCase {
+    func testRequestDescription() {
+        // Given
+        let urlString = "https://httpbin.org/get"
+        let delegate = SessionDelegate(startRequestsImmediately: false)
+        let manager = SessionManager(delegate: delegate)
+        let request = manager.request(urlString)
+        let initialRequestDescription = request.description
 
-        return self
-    }
+        let expectation = self.expectation(description: "Request description should update: \(urlString)")
 
-    fileprivate func postValidate(operation: @escaping () -> Void) -> Self {
-        internalQueue.addOperation {
-            operation()
-        }
+        var response: HTTPURLResponse?
+
+        // When
+        request.response { resp in
+            response = resp.response
+
+            expectation.fulfill()
+        }.resume()
 
-        return self
+        waitForExpectations(timeout: timeout, handler: nil)
+        
+        let finalRequestDescription = request.description
+
+        // Then
+        XCTAssertEqual(initialRequestDescription, "No request created yet.")
+        XCTAssertEqual(finalRequestDescription, "GET https://httpbin.org/get (\(response?.statusCode ?? -1))")
     }
 }
 
 // MARK: -
 
-// TODO: Do we still want this API?
-class RequestExtensionTestCase: BaseTestCase {
-    func testThatRequestExtensionHasAccessToTaskDelegateQueue() {
+class RequestDebugDescriptionTestCase: BaseTestCase {
+    // MARK: Properties
+
+    let manager: SessionManager = {
+        let manager = SessionManager()
+
+        return manager
+    }()
+
+    let managerWithAcceptLanguageHeader: SessionManager = {
+        var headers = HTTPHeaders.defaultHTTPHeaders
+        headers["Accept-Language"] = "en-US"
+
+        let configuration = URLSessionConfiguration.alamofireDefault
+        configuration.httpAdditionalHeaders = headers
+        
+        let manager = SessionManager(configuration: configuration)
+
+        return manager
+    }()
+
+    let managerWithContentTypeHeader: SessionManager = {
+        var headers = HTTPHeaders.defaultHTTPHeaders
+        headers["Content-Type"] = "application/json"
+        
+        let configuration = URLSessionConfiguration.alamofireDefault
+        configuration.httpAdditionalHeaders = headers
+        
+        let manager = SessionManager(configuration: configuration)
+
+        return manager
+    }()
+    
+    func managerWithCookie(_ cookie: HTTPCookie) -> SessionManager {
+        let configuration = URLSessionConfiguration.alamofireDefault
+        configuration.httpCookieStorage?.setCookie(cookie)
+        
+        return SessionManager(configuration: configuration)
+    }
+
+    let managerDisallowingCookies: SessionManager = {
+        let configuration = URLSessionConfiguration.alamofireDefault
+        configuration.httpShouldSetCookies = false
+
+        let manager = SessionManager(configuration: configuration)
+
+        return manager
+    }()
+
+    // MARK: Tests
+
+    func testGETRequestDebugDescription() {
         // Given
         let urlString = "https://httpbin.org/get"
-        let expectation = self.expectation(description: "GET request should succeed: \(urlString)")
+        let expectation = self.expectation(description: "request should complete")
+
+        // When
+        let request = manager.request(urlString).response { _ in expectation.fulfill() }
+        
+        waitForExpectations(timeout: timeout, handler: nil)
+        
+        let components = cURLCommandComponents(for: request)
+
+        // Then
+        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
+        XCTAssertTrue(components.contains("-X"))
+        XCTAssertEqual(components.last, "\"\(urlString)\"")
+    }
 
-        var responses: [String] = []
+    func testGETRequestWithJSONHeaderDebugDescription() {
+        // Given
+        let urlString = "https://httpbin.org/get"
+        let expectation = self.expectation(description: "request should complete")
 
         // When
-        Alamofire.request(urlString)
-            .preValidate {
-                responses.append("preValidate")
-            }
-            .validate()
-            .postValidate {
-                responses.append("postValidate")
-            }
+        let headers: [String: String] = [ "X-Custom-Header": "{\"key\": \"value\"}" ]
+        let request = manager.request(urlString, headers: headers).response { _ in expectation.fulfill() }
+        
+        waitForExpectations(timeout: timeout, handler: nil)
+
+        // Then
+        XCTAssertNotNil(request.debugDescription.range(of: "-H \"X-Custom-Header: {\\\"key\\\": \\\"value\\\"}\""))
+    }
+
+    func testGETRequestWithDuplicateHeadersDebugDescription() {
+        // Given
+        let urlString = "https://httpbin.org/get"
+        let expectation = self.expectation(description: "request should complete")
+
+        // When
+        let headers = [ "Accept-Language": "en-GB" ]
+        let request = managerWithAcceptLanguageHeader.request(urlString, headers: headers).response { _ in expectation.fulfill() }
+        
+        waitForExpectations(timeout: timeout, handler: nil)
+        
+        let components = cURLCommandComponents(for: request)
+
+        // Then
+        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
+        XCTAssertTrue(components.contains("-X"))
+        XCTAssertEqual(components.last, "\"\(urlString)\"")
+
+        let tokens = request.debugDescription.components(separatedBy: "Accept-Language:")
+        XCTAssertTrue(tokens.count == 2, "command should contain a single Accept-Language header")
+
+        XCTAssertNotNil(request.debugDescription.range(of: "-H \"Accept-Language: en-GB\""))
+    }
+
+    func testPOSTRequestDebugDescription() {
+        // Given
+        let urlString = "https://httpbin.org/post"
+        let expectation = self.expectation(description: "request should complete")
+
+
+        // When
+        let request = manager.request(urlString, method: .post).response { _ in expectation.fulfill() }
+        
+        waitForExpectations(timeout: timeout, handler: nil)
+        
+        let components = cURLCommandComponents(for: request)
+
+        // Then
+        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
+        XCTAssertEqual(components[3..<5], ["-X", "POST"])
+        XCTAssertEqual(components.last, "\"\(urlString)\"")
+    }
+
+    func testPOSTRequestWithJSONParametersDebugDescription() {
+        // Given
+        let urlString = "https://httpbin.org/post"
+        let expectation = self.expectation(description: "request should complete")
+
+        let parameters = [
+            "foo": "bar",
+            "fo\"o": "b\"ar",
+            "f'oo": "ba'r"
+        ]
+
+        // When
+        let request = manager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default).response {
+            _ in expectation.fulfill()
+        }
+        
+        waitForExpectations(timeout: timeout, handler: nil)
+        
+        let components = cURLCommandComponents(for: request)
+
+        // Then
+        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
+        XCTAssertEqual(components[3..<5], ["-X", "POST"])
+
+        XCTAssertNotNil(request.debugDescription.range(of: "-H \"Content-Type: application/json\""))
+        XCTAssertNotNil(request.debugDescription.range(of: "-d \"{"))
+        XCTAssertNotNil(request.debugDescription.range(of: "\\\"f'oo\\\":\\\"ba'r\\\""))
+        XCTAssertNotNil(request.debugDescription.range(of: "\\\"fo\\\\\\\"o\\\":\\\"b\\\\\\\"ar\\\""))
+        XCTAssertNotNil(request.debugDescription.range(of: "\\\"foo\\\":\\\"bar\\"))
+
+        XCTAssertEqual(components.last, "\"\(urlString)\"")
+    }
+
+    func testPOSTRequestWithCookieDebugDescription() {
+        // Given
+        let urlString = "https://httpbin.org/post"
+
+        let properties = [
+            HTTPCookiePropertyKey.domain: "httpbin.org",
+            HTTPCookiePropertyKey.path: "/post",
+            HTTPCookiePropertyKey.name: "foo",
+            HTTPCookiePropertyKey.value: "bar",
+        ]
+
+        let cookie = HTTPCookie(properties: properties)!
+        let cookieManager = managerWithCookie(cookie)
+        let expectation = self.expectation(description: "request should complete")
+
+
+        // When
+        let request = cookieManager.request(urlString, method: .post).response { _ in expectation.fulfill() }
+        
+        waitForExpectations(timeout: timeout, handler: nil)
+        
+        let components = cURLCommandComponents(for: request)
+
+        // Then
+        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
+        XCTAssertEqual(components[3..<5], ["-X", "POST"])
+        XCTAssertEqual(components.last, "\"\(urlString)\"")
+        XCTAssertEqual(components[5..<6], ["-b"])
+    }
+
+    func testPOSTRequestWithCookiesDisabledDebugDescription() {
+        // Given
+        let urlString = "https://httpbin.org/post"
+
+        let properties = [
+            HTTPCookiePropertyKey.domain: "httpbin.org",
+            HTTPCookiePropertyKey.path: "/post",
+            HTTPCookiePropertyKey.name: "foo",
+            HTTPCookiePropertyKey.value: "bar",
+        ]
+
+        let cookie = HTTPCookie(properties: properties)!
+        managerDisallowingCookies.session.configuration.httpCookieStorage?.setCookie(cookie)
+
+        // When
+        let request = managerDisallowingCookies.request(urlString, method: .post)
+        let components = cURLCommandComponents(for: request)
+
+        // Then
+        let cookieComponents = components.filter { $0 == "-b" }
+        XCTAssertTrue(cookieComponents.isEmpty)
+    }
+
+    func testMultipartFormDataRequestWithDuplicateHeadersDebugDescription() {
+        // Given
+        let urlString = "https://httpbin.org/post"
+        let japaneseData = Data("日本語".utf8)
+        let expectation = self.expectation(description: "multipart form data encoding should succeed")
+
+        // When
+        let request = managerWithContentTypeHeader.upload(multipartFormData: { (data) in
+            data.append(japaneseData, withName: "japanese")
+        }, to: urlString)
             .response { _ in
-                responses.append("response")
                 expectation.fulfill()
             }
 
         waitForExpectations(timeout: timeout, handler: nil)
 
+        let components = cURLCommandComponents(for: request)
+        
         // Then
-        if responses.count == 3 {
-            XCTAssertEqual(responses[0], "preValidate")
-            XCTAssertEqual(responses[1], "postValidate")
-            XCTAssertEqual(responses[2], "response")
-        } else {
-            XCTFail("responses count should be equal to 3")
-        }
+        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
+        XCTAssertTrue(components.contains("-X"))
+        XCTAssertEqual(components.last, "\"\(urlString)\"")
+
+        let tokens = request.debugDescription.components(separatedBy: "Content-Type:")
+        XCTAssertTrue(tokens.count == 2, "command should contain a single Content-Type header")
+
+        XCTAssertNotNil(request.debugDescription.range(of: "-H \"Content-Type: multipart/form-data;"))
     }
-}
 
-// MARK: -
+    func testThatRequestWithInvalidURLDebugDescription() {
+        // Given
+        let urlString = "invalid_url"
+        let expectation = self.expectation(description: "request should complete")
+        
+        // When
+        let request = manager.request(urlString).response { _ in expectation.fulfill() }
+        
+        waitForExpectations(timeout: timeout, handler: nil)
+        
+        let debugDescription = request.debugDescription
 
-//class RequestDescriptionTestCase: BaseTestCase {
-//    func testRequestDescription() {
-//        // Given
-//        let urlString = "https://httpbin.org/get"
-//        let request = Alamofire.request(urlString)
-//        let initialRequestDescription = request.description
-//
-//        let expectation = self.expectation(description: "Request description should update: \(urlString)")
-//
-//        var finalRequestDescription: String?
-//        var response: HTTPURLResponse?
-//
-//        // When
-//        request.response { resp in
-//            finalRequestDescription = request.description
-//            response = resp.response
-//
-//            expectation.fulfill()
-//        }
-//
-//        waitForExpectations(timeout: timeout, handler: nil)
-//
-//        // Then
-//        XCTAssertEqual(initialRequestDescription, "GET https://httpbin.org/get")
-//        XCTAssertEqual(finalRequestDescription, "GET https://httpbin.org/get (\(response?.statusCode ?? -1))")
-//    }
-//}
+        // Then
+        XCTAssertNotNil(debugDescription, "debugDescription should not crash")
+    }
 
-// MARK: -
+    // MARK: Test Helper Methods
 
-//class RequestDebugDescriptionTestCase: BaseTestCase {
-//    // MARK: Properties
-//
-//    let manager: SessionManager = {
-//        let manager = SessionManager(configuration: .default)
-//        manager.startRequestsImmediately = false
-//        return manager
-//    }()
-//
-//    let managerWithAcceptLanguageHeader: SessionManager = {
-//        var headers = SessionManager.default.session.configuration.httpAdditionalHeaders ?? [:]
-//        headers["Accept-Language"] = "en-US"
-//
-//        let configuration = URLSessionConfiguration.default
-//        configuration.httpAdditionalHeaders = headers
-//
-//        let manager = SessionManager(configuration: configuration)
-//        manager.startRequestsImmediately = false
-//
-//        return manager
-//    }()
-//
-//    let managerWithContentTypeHeader: SessionManager = {
-//        var headers = SessionManager.default.session.configuration.httpAdditionalHeaders ?? [:]
-//        headers["Content-Type"] = "application/json"
-//
-//        let configuration = URLSessionConfiguration.default
-//        configuration.httpAdditionalHeaders = headers
-//
-//        let manager = SessionManager(configuration: configuration)
-//        manager.startRequestsImmediately = false
-//
-//        return manager
-//    }()
-//
-//    let managerDisallowingCookies: SessionManager = {
-//        let configuration = URLSessionConfiguration.default
-//        configuration.httpShouldSetCookies = false
-//
-//        let manager = SessionManager(configuration: configuration)
-//        manager.startRequestsImmediately = false
-//
-//        return manager
-//    }()
-//
-//    // MARK: Tests
-//
-//    func testGETRequestDebugDescription() {
-//        // Given
-//        let urlString = "https://httpbin.org/get"
-//
-//        // When
-//        let request = manager.request(urlString)
-//        let components = cURLCommandComponents(for: request)
-//
-//        // Then
-//        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
-//        XCTAssertFalse(components.contains("-X"))
-//        XCTAssertEqual(components.last, "\"\(urlString)\"")
-//    }
-//
-//    func testGETRequestWithJSONHeaderDebugDescription() {
-//        // Given
-//        let urlString = "https://httpbin.org/get"
-//
-//        // When
-//        let headers: [String: String] = [ "X-Custom-Header": "{\"key\": \"value\"}" ]
-//        let request = manager.request(urlString, headers: headers)
-//
-//        // Then
-//        XCTAssertNotNil(request.debugDescription.range(of: "-H \"X-Custom-Header: {\\\"key\\\": \\\"value\\\"}\""))
-//    }
-//
-//    func testGETRequestWithDuplicateHeadersDebugDescription() {
-//        // Given
-//        let urlString = "https://httpbin.org/get"
-//
-//        // When
-//        let headers = [ "Accept-Language": "en-GB" ]
-//        let request = managerWithAcceptLanguageHeader.request(urlString, headers: headers)
-//        let components = cURLCommandComponents(for: request)
-//
-//        // Then
-//        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
-//        XCTAssertFalse(components.contains("-X"))
-//        XCTAssertEqual(components.last, "\"\(urlString)\"")
-//
-//        let tokens = request.debugDescription.components(separatedBy: "Accept-Language:")
-//        XCTAssertTrue(tokens.count == 2, "command should contain a single Accept-Language header")
-//
-//        XCTAssertNotNil(request.debugDescription.range(of: "-H \"Accept-Language: en-GB\""))
-//    }
-//
-//    func testPOSTRequestDebugDescription() {
-//        // Given
-//        let urlString = "https://httpbin.org/post"
-//
-//        // When
-//        let request = manager.request(urlString, method: .post)
-//        let components = cURLCommandComponents(for: request)
-//
-//        // Then
-//        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
-//        XCTAssertEqual(components[3..<5], ["-X", "POST"])
-//        XCTAssertEqual(components.last, "\"\(urlString)\"")
-//    }
-//
-//    func testPOSTRequestWithJSONParametersDebugDescription() {
-//        // Given
-//        let urlString = "https://httpbin.org/post"
-//
-//        let parameters = [
-//            "foo": "bar",
-//            "fo\"o": "b\"ar",
-//            "f'oo": "ba'r"
-//        ]
-//
-//        // When
-//        let request = manager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default)
-//        let components = cURLCommandComponents(for: request)
-//
-//        // Then
-//        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
-//        XCTAssertEqual(components[3..<5], ["-X", "POST"])
-//
-//        XCTAssertNotNil(request.debugDescription.range(of: "-H \"Content-Type: application/json\""))
-//        XCTAssertNotNil(request.debugDescription.range(of: "-d \"{"))
-//        XCTAssertNotNil(request.debugDescription.range(of: "\\\"f'oo\\\":\\\"ba'r\\\""))
-//        XCTAssertNotNil(request.debugDescription.range(of: "\\\"fo\\\\\\\"o\\\":\\\"b\\\\\\\"ar\\\""))
-//        XCTAssertNotNil(request.debugDescription.range(of: "\\\"foo\\\":\\\"bar\\"))
-//
-//        XCTAssertEqual(components.last, "\"\(urlString)\"")
-//    }
-//
-//    func testPOSTRequestWithCookieDebugDescription() {
-//        // Given
-//        let urlString = "https://httpbin.org/post"
-//
-//        let properties = [
-//            HTTPCookiePropertyKey.domain: "httpbin.org",
-//            HTTPCookiePropertyKey.path: "/post",
-//            HTTPCookiePropertyKey.name: "foo",
-//            HTTPCookiePropertyKey.value: "bar",
-//        ]
-//
-//        let cookie = HTTPCookie(properties: properties)!
-//        manager.session.configuration.httpCookieStorage?.setCookie(cookie)
-//
-//        // When
-//        let request = manager.request(urlString, method: .post)
-//        let components = cURLCommandComponents(for: request)
-//
-//        // Then
-//        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
-//        XCTAssertEqual(components[3..<5], ["-X", "POST"])
-//        XCTAssertEqual(components.last, "\"\(urlString)\"")
-//        XCTAssertEqual(components[5..<6], ["-b"])
-//    }
-//
-//    func testPOSTRequestWithCookiesDisabledDebugDescription() {
-//        // Given
-//        let urlString = "https://httpbin.org/post"
-//
-//        let properties = [
-//            HTTPCookiePropertyKey.domain: "httpbin.org",
-//            HTTPCookiePropertyKey.path: "/post",
-//            HTTPCookiePropertyKey.name: "foo",
-//            HTTPCookiePropertyKey.value: "bar",
-//        ]
-//
-//        let cookie = HTTPCookie(properties: properties)!
-//        managerDisallowingCookies.session.configuration.httpCookieStorage?.setCookie(cookie)
-//
-//        // When
-//        let request = managerDisallowingCookies.request(urlString, method: .post)
-//        let components = cURLCommandComponents(for: request)
-//
-//        // Then
-//        let cookieComponents = components.filter { $0 == "-b" }
-//        XCTAssertTrue(cookieComponents.isEmpty)
-//    }
-//
-//    func testMultipartFormDataRequestWithDuplicateHeadersDebugDescription() {
-//        // Given
-//        let urlString = "https://httpbin.org/post"
-//        let japaneseData = Data("日本語".utf8)
-//        let expectation = self.expectation(description: "multipart form data encoding should succeed")
-//
-//        var request: Request?
-//        var components: [String] = []
-//
-//        // When
-//        managerWithContentTypeHeader.upload(
-//            multipartFormData: { multipartFormData in
-//                multipartFormData.append(japaneseData, withName: "japanese")
-//            },
-//            to: urlString,
-//            encodingCompletion: { result in
-//                switch result {
-//                case .success(let upload, _, _):
-//                    request = upload
-//                    components = self.cURLCommandComponents(for: upload)
-//
-//                    expectation.fulfill()
-//                case .failure:
-//                    expectation.fulfill()
-//                }
-//            }
-//        )
-//
-//        waitForExpectations(timeout: timeout, handler: nil)
-//
-//        debugPrint(request!)
-//
-//        // Then
-//        XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
-//        XCTAssertTrue(components.contains("-X"))
-//        XCTAssertEqual(components.last, "\"\(urlString)\"")
-//
-//        let tokens = request.debugDescription.components(separatedBy: "Content-Type:")
-//        XCTAssertTrue(tokens.count == 2, "command should contain a single Content-Type header")
-//
-//        XCTAssertNotNil(request.debugDescription.range(of: "-H \"Content-Type: multipart/form-data;"))
-//    }
-//
-//    func testThatRequestWithInvalidURLDebugDescription() {
-//        // Given
-//        let urlString = "invalid_url"
-//
-//        // When
-//        let request = manager.request(urlString)
-//        let debugDescription = request.debugDescription
-//
-//        // Then
-//        XCTAssertNotNil(debugDescription, "debugDescription should not crash")
-//    }
-//
-//    // MARK: Test Helper Methods
-//
-//    private func cURLCommandComponents(for request: Request) -> [String] {
-//        let whitespaceCharacterSet = CharacterSet.whitespacesAndNewlines
-//        return request.debugDescription
-//            .components(separatedBy: whitespaceCharacterSet)
-//            .filter { $0 != "" && $0 != "\\" }
-//    }
-//}
+    private func cURLCommandComponents(for request: Request) -> [String] {
+        let whitespaceCharacterSet = CharacterSet.whitespacesAndNewlines
+        return request.debugDescription
+            .components(separatedBy: whitespaceCharacterSet)
+            .filter { $0 != "" && $0 != "\\" }
+    }
+}