From a02bc55445ebeed6bd07de95839583a59ed269f7 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 6 Sep 2024 16:14:45 +1000 Subject: [PATCH] Fixed an endless loading state when clearing data with no network --- LibSession-Util | 2 +- Session/Settings/NukeDataModal.swift | 17 ++++++++++-- .../Open Groups/OpenGroupAPI.swift | 10 +++++-- .../Notifications/PushNotificationAPI.swift | 6 +++-- .../LibSession/LibSession+Networking.swift | 12 ++++++--- .../Networking/Network+OnionRequest.swift | 18 ++++++++----- .../PreparedRequest+OnionRequest.swift | 19 +++++++++---- SessionSnodeKit/Networking/SnodeAPI.swift | 24 ++++++++++++----- .../Models/SnodeRequestSpec.swift | 2 +- .../Networking/PreparedRequest.swift | 27 ++++++++++++------- .../Networking/BatchRequestSpec.swift | 16 +++++------ .../Networking/PreparedRequestSpec.swift | 4 +-- 12 files changed, 109 insertions(+), 48 deletions(-) diff --git a/LibSession-Util b/LibSession-Util index ac34aa26a..c3aa3b99e 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit ac34aa26aa78066aeaa443fe30f384821d4ae36e +Subproject commit c3aa3b99e05d7e7568cdd1dc2e0f1200e3ec01f1 diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index 4ca61915f..93c5ac819 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -179,7 +179,17 @@ final class NukeDataModal: Modal { .distinct() .asRequest(of: String.self) .fetchSet(db) - .map { ($0, try OpenGroupAPI.preparedClearInbox(db, on: $0, using: dependencies))} + .map { server in + ( + server, + try OpenGroupAPI.preparedClearInbox( + db, + on: server, + requestAndPathBuildTimeout: Network.defaultTimeout, + using: dependencies + ) + ) + } } .defaulting(to: []) .compactMap { server, preparedRequest in @@ -193,7 +203,10 @@ final class NukeDataModal: Modal { .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .flatMap { results in SnodeAPI - .deleteAllMessages(namespace: .all) + .deleteAllMessages( + namespace: .all, + requestAndPathBuildTimeout: Network.defaultTimeout + ) .map { results.reduce($0) { result, next in result.updated(with: next) } } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index f58e20195..fa049e79a 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -975,6 +975,8 @@ public enum OpenGroupAPI { public static func preparedClearInbox( _ db: Database, on server: String, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, using dependencies: Dependencies ) throws -> Network.PreparedRequest { return try OpenGroupAPI @@ -986,6 +988,8 @@ public enum OpenGroupAPI { endpoint: .inbox ), responseType: DeleteInboxResponse.self, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, using: dependencies ) .signed(db, with: OpenGroupAPI.signRequest, using: dependencies) @@ -1463,14 +1467,16 @@ public enum OpenGroupAPI { private static func prepareRequest( request: Request, responseType: R.Type, - timeout: TimeInterval = Network.defaultTimeout, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, using dependencies: Dependencies ) throws -> Network.PreparedRequest { return Network.PreparedRequest( request: request, urlRequest: try request.generateUrlRequest(using: dependencies), responseType: responseType, - timeout: timeout + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout ) } } diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 54266d2a9..f09744547 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -496,7 +496,8 @@ public enum PushNotificationAPI { request: Request, responseType: R.Type, retryCount: Int = 0, - timeout: TimeInterval = Network.defaultTimeout, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, using dependencies: Dependencies ) throws -> Network.PreparedRequest { return Network.PreparedRequest( @@ -504,7 +505,8 @@ public enum PushNotificationAPI { urlRequest: try request.generateUrlRequest(using: dependencies), responseType: responseType, retryCount: retryCount, - timeout: timeout + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout ) } } diff --git a/SessionSnodeKit/LibSession/LibSession+Networking.swift b/SessionSnodeKit/LibSession/LibSession+Networking.swift index fc208163c..bc9bdbb81 100644 --- a/SessionSnodeKit/LibSession/LibSession+Networking.swift +++ b/SessionSnodeKit/LibSession/LibSession+Networking.swift @@ -230,7 +230,8 @@ public extension LibSession { to destination: Network.Destination, body: T?, swarmPublicKey: String?, - timeout: TimeInterval, + requestTimeout: TimeInterval, + requestAndPathBuildTimeout: TimeInterval?, using dependencies: Dependencies ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { typealias Output = (success: Bool, timeout: Bool, statusCode: Int, data: Data?) @@ -269,7 +270,8 @@ public extension LibSession { cPayloadBytes, cPayloadBytes.count, cSwarmPublicKey, - Int64(floor(timeout * 1000)), + Int64(floor(requestTimeout * 1000)), + Int64(floor((requestAndPathBuildTimeout ?? 0) * 1000)), { success, timeout, statusCode, dataPtr, dataLen, ctx in let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } CallbackWrapper.run(ctx, (success, timeout, Int(statusCode), data)) @@ -283,7 +285,8 @@ public extension LibSession { try wrapper.cServerDestination(destination), cPayloadBytes, cPayloadBytes.count, - Int64(floor(timeout * 1000)), + Int64(floor(requestTimeout * 1000)), + Int64(floor((requestAndPathBuildTimeout ?? 0) * 1000)), { success, timeout, statusCode, dataPtr, dataLen, ctx in let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } CallbackWrapper.run(ctx, (success, timeout, Int(statusCode), data)) @@ -319,6 +322,7 @@ public extension LibSession { data.count, fileName?.cString(using: .utf8), Int64(floor(Network.fileUploadTimeout * 1000)), + 0, { success, timeout, statusCode, dataPtr, dataLen, ctx in let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } CallbackWrapper.run(ctx, (success, timeout, Int(statusCode), data)) @@ -350,6 +354,7 @@ public extension LibSession { network, try wrapper.cServerDestination(server), Int64(floor(Network.fileDownloadTimeout * 1000)), + 0, { success, timeout, statusCode, dataPtr, dataLen, ctx in let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } CallbackWrapper.run(ctx, (success, timeout, Int(statusCode), data)) @@ -387,6 +392,7 @@ public extension LibSession { CLIENT_PLATFORM_IOS, &cEd25519SecretKey, Int64(floor(Network.fileDownloadTimeout * 1000)), + 0, { success, timeout, statusCode, dataPtr, dataLen, ctx in let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } CallbackWrapper.run(ctx, (success, timeout, Int(statusCode), data)) diff --git a/SessionSnodeKit/Networking/Network+OnionRequest.swift b/SessionSnodeKit/Networking/Network+OnionRequest.swift index 25058c7f8..2a56efa25 100644 --- a/SessionSnodeKit/Networking/Network+OnionRequest.swift +++ b/SessionSnodeKit/Networking/Network+OnionRequest.swift @@ -11,20 +11,22 @@ public extension Network.RequestType { _ payload: Data, to snode: LibSession.Snode, swarmPublicKey: String?, - timeout: TimeInterval = Network.defaultTimeout + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil ) -> Network.RequestType { return Network.RequestType( id: "onionRequest", url: "quic://\(snode.address)", method: "POST", body: payload, - args: [payload, snode, swarmPublicKey, timeout] + args: [payload, snode, swarmPublicKey, requestTimeout, requestAndPathBuildTimeout] ) { dependencies in LibSession.sendOnionRequest( to: Network.Destination.snode(snode), body: payload, swarmPublicKey: swarmPublicKey, - timeout: timeout, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, using: dependencies ) } @@ -34,7 +36,8 @@ public extension Network.RequestType { _ request: URLRequest, to server: String, with x25519PublicKey: String, - timeout: TimeInterval = Network.defaultTimeout + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil ) -> Network.RequestType { return Network.RequestType( id: "onionRequest", @@ -42,9 +45,9 @@ public extension Network.RequestType { method: request.httpMethod, headers: request.allHTTPHeaderFields, body: request.httpBody, - args: [request, server, x25519PublicKey, timeout] + args: [request, server, x25519PublicKey, requestTimeout, requestAndPathBuildTimeout] ) { dependencies in - guard let url = request.url, let host = request.url?.host else { + guard let url = request.url else { return Fail(error: NetworkError.invalidURL).eraseToAnyPublisher() } @@ -57,7 +60,8 @@ public extension Network.RequestType { ), body: request.httpBody, swarmPublicKey: nil, - timeout: timeout, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, using: dependencies ) } diff --git a/SessionSnodeKit/Networking/PreparedRequest+OnionRequest.swift b/SessionSnodeKit/Networking/PreparedRequest+OnionRequest.swift index 6cad6c491..5d057c95d 100644 --- a/SessionSnodeKit/Networking/PreparedRequest+OnionRequest.swift +++ b/SessionSnodeKit/Networking/PreparedRequest+OnionRequest.swift @@ -32,7 +32,8 @@ public extension Network.PreparedRequest { request, to: serverTarget.server, with: serverTarget.x25519PublicKey, - timeout: timeout + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout ), using: dependencies ) @@ -46,7 +47,8 @@ public extension Network.PreparedRequest { payload, to: snodeTarget.snode, swarmPublicKey: snodeTarget.swarmPublicKey, - timeout: timeout + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout ), using: dependencies ) @@ -62,7 +64,8 @@ public extension Network.PreparedRequest { payload, to: snode, swarmPublicKey: randomTarget.swarmPublicKey, - timeout: timeout + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout ), using: dependencies ) @@ -74,7 +77,12 @@ public extension Network.PreparedRequest { return LibSession.getSwarm(swarmPublicKey: randomTarget.swarmPublicKey) .tryFlatMapWithRandomSnode(retry: randomTarget.retryCount, using: dependencies) { snode in SnodeAPI - .getNetworkTime(from: snode, using: dependencies) + .getNetworkTime( + from: snode, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) .tryFlatMap { timestampMs in guard let updatedRequest: URLRequest = try? randomTarget @@ -88,7 +96,8 @@ public extension Network.PreparedRequest { payload, to: snode, swarmPublicKey: randomTarget.swarmPublicKey, - timeout: timeout + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout ), using: dependencies ) diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index ede95b9aa..335dad11b 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -740,7 +740,7 @@ public final class SnodeAPI { ) .send(using: dependencies) .tryMap { _, response -> Void in - try response.validResultMap( + _ = try response.validResultMap( swarmPublicKey: getUserHexEncodedPublicKey(), validationData: subkeyToRevoke, using: dependencies @@ -860,6 +860,8 @@ public final class SnodeAPI { /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. public static func deleteAllMessages( namespace: SnodeAPI.Namespace, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, using dependencies: Dependencies = Dependencies() ) -> AnyPublisher<[String: Bool], Error> { guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { @@ -882,9 +884,12 @@ public final class SnodeAPI { timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()), ed25519PublicKey: userED25519KeyPair.publicKey, ed25519SecretKey: userED25519KeyPair.secretKey - ) + ), + retryCount: 0 // Don't auto retry this request (user can manually retry on failure) ), responseType: DeleteAllMessagesResponse.self, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, using: dependencies ) .send(using: dependencies) @@ -931,7 +936,8 @@ public final class SnodeAPI { timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()), ed25519PublicKey: userED25519KeyPair.publicKey, ed25519SecretKey: userED25519KeyPair.secretKey - ) + ), + retryCount: 0 // Don't auto retry this request (user can manually retry on failure) ), responseType: DeleteAllBeforeResponse.self, using: dependencies @@ -953,6 +959,8 @@ public final class SnodeAPI { public static func getNetworkTime( from snode: LibSession.Snode, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, using dependencies: Dependencies ) -> AnyPublisher { do { @@ -964,6 +972,8 @@ public final class SnodeAPI { body: [String: String]() ), responseType: GetNetworkTimestampResponse.self, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, using: dependencies ) .send(using: dependencies) @@ -987,7 +997,8 @@ public final class SnodeAPI { responseType: R.Type, requireAllBatchResponses: Bool = true, retryCount: Int = 0, - timeout: TimeInterval = Network.defaultTimeout, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, using dependencies: Dependencies ) throws -> Network.PreparedRequest { return Network.PreparedRequest( @@ -996,7 +1007,8 @@ public final class SnodeAPI { responseType: responseType, requireAllBatchResponses: requireAllBatchResponses, retryCount: retryCount, - timeout: timeout + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout ) .handleEvents( receiveOutput: { _, response in @@ -1140,7 +1152,7 @@ private extension Request { swarmPublicKey: String, requiresLatestNetworkTime: Bool, body: B, - retryCount: Int = SnodeAPI.maxRetryCount + retryCount: Int ) where T == SnodeRequest, Endpoint == SnodeAPI.Endpoint, B: Encodable & UpdatableTimestamp { self = Request( method: .post, diff --git a/SessionSnodeKitTests/Models/SnodeRequestSpec.swift b/SessionSnodeKitTests/Models/SnodeRequestSpec.swift index 8890d9d93..f4a5df89d 100644 --- a/SessionSnodeKitTests/Models/SnodeRequestSpec.swift +++ b/SessionSnodeKitTests/Models/SnodeRequestSpec.swift @@ -40,7 +40,7 @@ class SnodeRequestSpec: QuickSpec { ), urlRequest: URLRequest(url: URL(string: "https://www.oxen.io")!), responseType: NoResponse.self, - timeout: 0 + requestTimeout: 0 ) ] ) diff --git a/SessionUtilitiesKit/Networking/PreparedRequest.swift b/SessionUtilitiesKit/Networking/PreparedRequest.swift index aac24022a..c77502e7b 100644 --- a/SessionUtilitiesKit/Networking/PreparedRequest.swift +++ b/SessionUtilitiesKit/Networking/PreparedRequest.swift @@ -19,7 +19,8 @@ public extension Network { public let originalType: Decodable.Type public let responseType: R.Type public let retryCount: Int - public let timeout: TimeInterval + public let requestTimeout: TimeInterval + public let requestAndPathBuildTimeout: TimeInterval? public let cachedResponse: CachedResponse? fileprivate let responseConverter: ((ResponseInfoType, Any) throws -> R) public let subscriptionHandler: (() -> Void)? @@ -49,7 +50,8 @@ public extension Network { responseType: R.Type, requireAllBatchResponses: Bool = true, retryCount: Int = 0, - timeout: TimeInterval + requestTimeout: TimeInterval, + requestAndPathBuildTimeout: TimeInterval? = nil ) where R: Decodable { let batchRequests: [Network.BatchRequest.Child]? = (request.body as? BatchRequestChildRetrievable)?.requests let batchEndpoints: [E] = (batchRequests? @@ -68,7 +70,8 @@ public extension Network { self.originalType = R.self self.responseType = responseType self.retryCount = retryCount - self.timeout = timeout + self.requestTimeout = requestTimeout + self.requestAndPathBuildTimeout = requestAndPathBuildTimeout self.cachedResponse = nil // When we are making a batch request we also want to call though any sub request event @@ -246,7 +249,8 @@ public extension Network { originalType: U.Type, responseType: R.Type, retryCount: Int, - timeout: TimeInterval, + requestTimeout: TimeInterval, + requestAndPathBuildTimeout: TimeInterval?, cachedResponse: CachedResponse?, responseConverter: @escaping (ResponseInfoType, Any) throws -> R, subscriptionHandler: (() -> Void)?, @@ -272,7 +276,8 @@ public extension Network { self.originalType = originalType self.responseType = responseType self.retryCount = retryCount - self.timeout = timeout + self.requestTimeout = requestTimeout + self.requestAndPathBuildTimeout = requestAndPathBuildTimeout self.cachedResponse = cachedResponse self.responseConverter = responseConverter self.subscriptionHandler = subscriptionHandler @@ -420,7 +425,8 @@ public extension Network.PreparedRequest { originalType: originalType, responseType: responseType, retryCount: retryCount, - timeout: timeout, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, cachedResponse: cachedResponse, responseConverter: responseConverter, subscriptionHandler: subscriptionHandler, @@ -464,7 +470,8 @@ public extension Network.PreparedRequest { originalType: originalType, responseType: O.self, retryCount: retryCount, - timeout: timeout, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, cachedResponse: cachedResponse.map { data in (try? responseConverter(data.info, data.convertedData)) .map { convertedData in @@ -573,7 +580,8 @@ public extension Network.PreparedRequest { originalType: originalType, responseType: responseType, retryCount: retryCount, - timeout: timeout, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, cachedResponse: cachedResponse, responseConverter: responseConverter, subscriptionHandler: subscriptionHandler, @@ -610,7 +618,8 @@ public extension Network.PreparedRequest { originalType: R.self, responseType: R.self, retryCount: 0, - timeout: 0, + requestTimeout: 0, + requestAndPathBuildTimeout: nil, cachedResponse: Network.PreparedRequest.CachedResponse( info: Network.ResponseInfo(code: 0, headers: [:]), originalData: cachedResponse, diff --git a/SessionUtilitiesKitTests/Networking/BatchRequestSpec.swift b/SessionUtilitiesKitTests/Networking/BatchRequestSpec.swift index 10cf74e90..d581d61c9 100644 --- a/SessionUtilitiesKitTests/Networking/BatchRequestSpec.swift +++ b/SessionUtilitiesKitTests/Networking/BatchRequestSpec.swift @@ -39,7 +39,7 @@ class BatchRequestSpec: QuickSpec { request: httpRequest, urlRequest: try! httpRequest.generateUrlRequest(using: dependencies), responseType: NoResponse.self, - timeout: 0 + requestTimeout: 0 ) ] ) @@ -73,7 +73,7 @@ class BatchRequestSpec: QuickSpec { request: httpRequest, urlRequest: try! httpRequest.generateUrlRequest(using: dependencies), responseType: NoResponse.self, - timeout: 0 + requestTimeout: 0 ) ] ) @@ -104,7 +104,7 @@ class BatchRequestSpec: QuickSpec { ), urlRequest: URLRequest(url: URL(string: "https://www.oxen.io")!), responseType: NoResponse.self, - timeout: 0 + requestTimeout: 0 ) ] ) @@ -133,7 +133,7 @@ class BatchRequestSpec: QuickSpec { ), urlRequest: URLRequest(url: URL(string: "https://www.oxen.io")!), responseType: NoResponse.self, - timeout: 0 + requestTimeout: 0 ) ] ) @@ -162,7 +162,7 @@ class BatchRequestSpec: QuickSpec { ), urlRequest: URLRequest(url: URL(string: "https://www.oxen.io")!), responseType: NoResponse.self, - timeout: 0 + requestTimeout: 0 ) ] ) @@ -195,7 +195,7 @@ class BatchRequestSpec: QuickSpec { ), urlRequest: URLRequest(url: URL(string: "https://www.oxen.io")!), responseType: NoResponse.self, - timeout: 0 + requestTimeout: 0 ) ] ) @@ -225,7 +225,7 @@ class BatchRequestSpec: QuickSpec { ), urlRequest: URLRequest(url: URL(string: "https://www.oxen.io")!), responseType: NoResponse.self, - timeout: 0 + requestTimeout: 0 ) ] ) @@ -255,7 +255,7 @@ class BatchRequestSpec: QuickSpec { ), urlRequest: URLRequest(url: URL(string: "https://www.oxen.io")!), responseType: NoResponse.self, - timeout: 0 + requestTimeout: 0 ) ] ) diff --git a/SessionUtilitiesKitTests/Networking/PreparedRequestSpec.swift b/SessionUtilitiesKitTests/Networking/PreparedRequestSpec.swift index 519ed8926..9d3ad0432 100644 --- a/SessionUtilitiesKitTests/Networking/PreparedRequestSpec.swift +++ b/SessionUtilitiesKitTests/Networking/PreparedRequestSpec.swift @@ -41,7 +41,7 @@ class PreparedRequestSpec: QuickSpec { request: request, urlRequest: try! request.generateUrlRequest(using: dependencies), responseType: TestType.self, - timeout: 10 + requestTimeout: 10 ) expect(preparedRequest.request.url?.absoluteString).to(equal("testServer/endpoint")) @@ -70,7 +70,7 @@ class PreparedRequestSpec: QuickSpec { request: request, urlRequest: try! request.generateUrlRequest(using: dependencies), responseType: TestType.self, - timeout: 10 + requestTimeout: 10 ) expect(TestEndpoint.excludedSubRequestHeaders).to(equal([HTTPHeader.testHeader]))